From ad43e17155803123613d6a9d676955a09801da30 Mon Sep 17 00:00:00 2001 From: Claudio Salvatore Arcidiacono <22871978+ClaudioSalvatoreArcidiacono@users.noreply.github.com> Date: Fri, 15 Sep 2023 08:55:32 +0000 Subject: [PATCH 01/24] Enable Auto Early Stopping --- docs/Python-Intro.rst | 5 + python-package/lightgbm/basic.py | 4 +- python-package/lightgbm/dask.py | 3 + python-package/lightgbm/engine.py | 37 +++--- python-package/lightgbm/sklearn.py | 138 ++++++++++++++-------- tests/python_package_test/test_sklearn.py | 105 ++++++++++++++++ 6 files changed, 226 insertions(+), 66 deletions(-) diff --git a/docs/Python-Intro.rst b/docs/Python-Intro.rst index 3c1cb1557e3f..556ec6f93f94 100644 --- a/docs/Python-Intro.rst +++ b/docs/Python-Intro.rst @@ -241,6 +241,11 @@ This works with both metrics to minimize (L2, log loss, etc.) and to maximize (N Note that if you specify more than one evaluation metric, all of them will be used for early stopping. However, you can change this behavior and make LightGBM check only the first metric for early stopping by passing ``first_metric_only=True`` in ``early_stopping`` callback constructor. +In the scikit-learn API of lightgbm, early stopping is enabled by default if the training set passed during ``fit()`` has more than 10000 rows. +This behavior can be controlled by explicitly setting the parameter ``early_stopping`` to ``True`` or ``False`` in the class constructor. +When auto early stopping is enabled, a portion of the training data will be used as validation set. The amount of data to use for validation +is controlled by the parameter ``validation_fraction`` and defaults to 0.1. + Prediction ---------- diff --git a/python-package/lightgbm/basic.py b/python-package/lightgbm/basic.py index 8256dc026973..caf795342552 100644 --- a/python-package/lightgbm/basic.py +++ b/python-package/lightgbm/basic.py @@ -2539,14 +2539,12 @@ def set_categorical_feature( self : Dataset Dataset with set categorical features. """ - if self.categorical_feature == categorical_feature: + if self.categorical_feature == categorical_feature or categorical_feature == 'auto': return self if self.data is not None: if self.categorical_feature is None: self.categorical_feature = categorical_feature return self._free_handle() - elif categorical_feature == 'auto': - return self else: if self.categorical_feature != 'auto': _log_warning('categorical_feature in Dataset is overridden.\n' diff --git a/python-package/lightgbm/dask.py b/python-package/lightgbm/dask.py index 8aeeac09eed2..ce5f7137b20c 100644 --- a/python-package/lightgbm/dask.py +++ b/python-package/lightgbm/dask.py @@ -1145,6 +1145,7 @@ def __init__( random_state: Optional[Union[int, np.random.RandomState]] = None, n_jobs: Optional[int] = None, importance_type: str = 'split', + validation_fraction: Optional[float] = 0.1, client: Optional[Client] = None, **kwargs: Any ): @@ -1350,6 +1351,7 @@ def __init__( random_state: Optional[Union[int, np.random.RandomState]] = None, n_jobs: Optional[int] = None, importance_type: str = 'split', + validation_fraction: Optional[float] = 0.1, client: Optional[Client] = None, **kwargs: Any ): @@ -1520,6 +1522,7 @@ def __init__( random_state: Optional[Union[int, np.random.RandomState]] = None, n_jobs: Optional[int] = None, importance_type: str = 'split', + validation_fraction: Optional[float] = 0.1, client: Optional[Client] = None, **kwargs: Any ): diff --git a/python-package/lightgbm/engine.py b/python-package/lightgbm/engine.py index 822aa3b35017..4a97a9173e7a 100644 --- a/python-package/lightgbm/engine.py +++ b/python-package/lightgbm/engine.py @@ -455,11 +455,9 @@ def _make_n_folds( nfold: int, params: Dict[str, Any], seed: int, - fpreproc: Optional[_LGBM_PreprocFunction], stratified: bool, shuffle: bool, - eval_train_metric: bool -) -> CVBooster: +) -> Iterable[Tuple[np.ndarray, np.ndarray]]: """Make a n-fold list of Booster from random indices.""" full_data = full_data.construct() num_data = full_data.num_data() @@ -500,7 +498,16 @@ def _make_n_folds( test_id = [randidx[i: i + kstep] for i in range(0, num_data, kstep)] train_id = [np.concatenate([test_id[i] for i in range(nfold) if k != i]) for k in range(nfold)] folds = zip(train_id, test_id) + return folds + +def _make_cvbooster( + full_data: Dataset, + params: Dict[str, Any], + folds: Iterable[Tuple[np.ndarray, np.ndarray]], + fpreproc: Optional[_LGBM_PreprocFunction], + eval_train_metric: bool, +) -> CVBooster: ret = CVBooster() for train_idx, test_idx in folds: train_set = full_data.subset(sorted(train_idx)) @@ -720,8 +727,10 @@ def cv( results = defaultdict(list) cvfolds = _make_n_folds(full_data=train_set, folds=folds, nfold=nfold, - params=params, seed=seed, fpreproc=fpreproc, - stratified=stratified, shuffle=shuffle, + params=params, seed=seed, + stratified=stratified, shuffle=shuffle) + cvbooster = _make_cvbooster(full_data=train_set, folds=cvfolds, + params=params, fpreproc=fpreproc, eval_train_metric=eval_train_metric) # setup callbacks @@ -752,34 +761,34 @@ def cv( for i in range(num_boost_round): for cb in callbacks_before_iter: - cb(callback.CallbackEnv(model=cvfolds, + cb(callback.CallbackEnv(model=cvbooster, params=params, iteration=i, begin_iteration=0, end_iteration=num_boost_round, evaluation_result_list=None)) - cvfolds.update(fobj=fobj) # type: ignore[call-arg] - res = _agg_cv_result(cvfolds.eval_valid(feval)) # type: ignore[call-arg] + cvbooster.update(fobj=fobj) # type: ignore[call-arg] + res = _agg_cv_result(cvbooster.eval_valid(feval)) # type: ignore[call-arg] for _, key, mean, _, std in res: results[f'{key}-mean'].append(mean) results[f'{key}-stdv'].append(std) try: for cb in callbacks_after_iter: - cb(callback.CallbackEnv(model=cvfolds, + cb(callback.CallbackEnv(model=cvbooster, params=params, iteration=i, begin_iteration=0, end_iteration=num_boost_round, evaluation_result_list=res)) except callback.EarlyStopException as earlyStopException: - cvfolds.best_iteration = earlyStopException.best_iteration + 1 - for bst in cvfolds.boosters: - bst.best_iteration = cvfolds.best_iteration + cvbooster.best_iteration = earlyStopException.best_iteration + 1 + for bst in cvbooster.boosters: + bst.best_iteration = cvbooster.best_iteration for k in results: - results[k] = results[k][:cvfolds.best_iteration] + results[k] = results[k][:cvbooster.best_iteration] break if return_cvbooster: - results['cvbooster'] = cvfolds # type: ignore[assignment] + results['cvbooster'] = cvbooster # type: ignore[assignment] return dict(results) diff --git a/python-package/lightgbm/sklearn.py b/python-package/lightgbm/sklearn.py index c71c233df908..f1a132637aa1 100644 --- a/python-package/lightgbm/sklearn.py +++ b/python-package/lightgbm/sklearn.py @@ -16,7 +16,7 @@ _LGBMCheckClassificationTargets, _LGBMCheckSampleWeight, _LGBMCheckXY, _LGBMClassifierBase, _LGBMComputeSampleWeight, _LGBMCpuCount, _LGBMLabelEncoder, _LGBMModelBase, _LGBMRegressorBase, dt_DataTable, pd_DataFrame) -from .engine import train +from .engine import _make_n_folds, train __all__ = [ 'LGBMClassifier', @@ -412,6 +412,7 @@ def __init__( random_state: Optional[Union[int, np.random.RandomState]] = None, n_jobs: Optional[int] = None, importance_type: str = 'split', + validation_fraction: Optional[float] = 0.1, **kwargs ): r"""Construct a gradient boosting model. @@ -491,6 +492,10 @@ def __init__( The type of feature importance to be filled into ``feature_importances_``. If 'split', result contains numbers of times the feature is used in a model. If 'gain', result contains total gains of splits which use the feature. + validation_fraction : float or None, optional (default=0.1) + Proportion of training data to set aside as + validation data for early stopping. If None, early stopping is done on + the training data. Only used if early stopping is performed. **kwargs Other parameters for the model. Check http://lightgbm.readthedocs.io/en/latest/Parameters.html for more parameters. @@ -553,6 +558,7 @@ def __init__( self.random_state = random_state self.n_jobs = n_jobs self.importance_type = importance_type + self.validation_fraction = validation_fraction self._Booster: Optional[Booster] = None self._evals_result: _EvalResultDict = {} self._best_score: _LGBM_BoosterBestScoreType = {} @@ -668,9 +674,27 @@ def _process_params(self, stage: str) -> Dict[str, Any]: params.pop('importance_type', None) params.pop('n_estimators', None) params.pop('class_weight', None) + params.pop("validation_fraction", None) if isinstance(params['random_state'], np.random.RandomState): params['random_state'] = params['random_state'].randint(np.iinfo(np.int32).max) + + params = _choose_param_value( + main_param_name="early_stopping_round", + params=params, + default_value="auto", + ) + if params["early_stopping_round"] == "auto": + if hasattr(self, "_n_rows_train") and self._n_rows_train > 10_000: + params["early_stopping_round"] = 10 + else: + params["early_stopping_round"] = None + + if params["early_stopping_round"] is True: + params["early_stopping_round"] = 10 + elif params["early_stopping_round"] is False: + params["early_stopping_round"] = None + if self._n_classes > 2: for alias in _ConfigAliases.get('num_class'): params.pop(alias, None) @@ -745,6 +769,19 @@ def fit( init_model: Optional[Union[str, Path, Booster, "LGBMModel"]] = None ) -> "LGBMModel": """Docstring is set after definition, using a template.""" + if not isinstance(X, (pd_DataFrame, dt_DataTable)): + _X, _y = _LGBMCheckXY(X, y, accept_sparse=True, force_all_finite=False, ensure_min_samples=2) + if sample_weight is not None: + sample_weight = _LGBMCheckSampleWeight(sample_weight, _X) + else: + _X, _y = X, y + + self._n_features = _X.shape[1] + # copy for consistency + self._n_features_in = self._n_features + + self._n_rows_train = _X.shape[0] + params = self._process_params(stage="fit") # Do not modify original args in fit function @@ -766,13 +803,6 @@ def fit( params['metric'] = [e for e in eval_metrics_builtin if e not in params['metric']] + params['metric'] params['metric'] = [metric for metric in params['metric'] if metric is not None] - if not isinstance(X, (pd_DataFrame, dt_DataTable)): - _X, _y = _LGBMCheckXY(X, y, accept_sparse=True, force_all_finite=False, ensure_min_samples=2) - if sample_weight is not None: - sample_weight = _LGBMCheckSampleWeight(sample_weight, _X) - else: - _X, _y = X, y - if self._class_weight is None: self._class_weight = self.class_weight if self._class_weight is not None: @@ -782,51 +812,61 @@ def fit( else: sample_weight = np.multiply(sample_weight, class_sample_weight) - self._n_features = _X.shape[1] - # copy for consistency - self._n_features_in = self._n_features - train_set = Dataset(data=_X, label=_y, weight=sample_weight, group=group, init_score=init_score, categorical_feature=categorical_feature, params=params) + if params["early_stopping_round"] is not None and eval_set is None: + if self.validation_fraction is not None: + n_splits = max(int(np.ceil(1 / self.validation_fraction)), 2) + stratified = isinstance(self, LGBMClassifier) + cvfolds = _make_n_folds(full_data=train_set, folds=None, nfold=n_splits, + params=params, seed=self.random_state, + stratified=stratified, shuffle=True) + train_idx, val_idx = next(cvfolds) + valid_set = train_set.subset(sorted(val_idx)) + train_set = train_set.subset(sorted(train_idx)) + else: + valid_set = train_set + valid_set = valid_set.construct() + valid_sets = [valid_set] - valid_sets: List[Dataset] = [] - if eval_set is not None: - - def _get_meta_data(collection, name, i): - if collection is None: - return None - elif isinstance(collection, list): - return collection[i] if len(collection) > i else None - elif isinstance(collection, dict): - return collection.get(i, None) - else: - raise TypeError(f"{name} should be dict or list") - - if isinstance(eval_set, tuple): - eval_set = [eval_set] - for i, valid_data in enumerate(eval_set): - # reduce cost for prediction training data - if valid_data[0] is X and valid_data[1] is y: - valid_set = train_set - else: - valid_weight = _get_meta_data(eval_sample_weight, 'eval_sample_weight', i) - valid_class_weight = _get_meta_data(eval_class_weight, 'eval_class_weight', i) - if valid_class_weight is not None: - if isinstance(valid_class_weight, dict) and self._class_map is not None: - valid_class_weight = {self._class_map[k]: v for k, v in valid_class_weight.items()} - valid_class_sample_weight = _LGBMComputeSampleWeight(valid_class_weight, valid_data[1]) - if valid_weight is None or len(valid_weight) == 0: - valid_weight = valid_class_sample_weight - else: - valid_weight = np.multiply(valid_weight, valid_class_sample_weight) - valid_init_score = _get_meta_data(eval_init_score, 'eval_init_score', i) - valid_group = _get_meta_data(eval_group, 'eval_group', i) - valid_set = Dataset(data=valid_data[0], label=valid_data[1], weight=valid_weight, - group=valid_group, init_score=valid_init_score, - categorical_feature='auto', params=params) - - valid_sets.append(valid_set) + else: + valid_sets: List[Dataset] = [] + if eval_set is not None: + def _get_meta_data(collection, name, i): + if collection is None: + return None + elif isinstance(collection, list): + return collection[i] if len(collection) > i else None + elif isinstance(collection, dict): + return collection.get(i, None) + else: + raise TypeError(f"{name} should be dict or list") + + if isinstance(eval_set, tuple): + eval_set = [eval_set] + for i, valid_data in enumerate(eval_set): + # reduce cost for prediction training data + if valid_data[0] is X and valid_data[1] is y: + valid_set = train_set + else: + valid_weight = _get_meta_data(eval_sample_weight, 'eval_sample_weight', i) + valid_class_weight = _get_meta_data(eval_class_weight, 'eval_class_weight', i) + if valid_class_weight is not None: + if isinstance(valid_class_weight, dict) and self._class_map is not None: + valid_class_weight = {self._class_map[k]: v for k, v in valid_class_weight.items()} + valid_class_sample_weight = _LGBMComputeSampleWeight(valid_class_weight, valid_data[1]) + if valid_weight is None or len(valid_weight) == 0: + valid_weight = valid_class_sample_weight + else: + valid_weight = np.multiply(valid_weight, valid_class_sample_weight) + valid_init_score = _get_meta_data(eval_init_score, 'eval_init_score', i) + valid_group = _get_meta_data(eval_group, 'eval_group', i) + valid_set = Dataset(data=valid_data[0], label=valid_data[1], weight=valid_weight, + group=valid_group, init_score=valid_init_score, + categorical_feature='auto', params=params) + + valid_sets.append(valid_set) if isinstance(init_model, LGBMModel): init_model = init_model.booster_ diff --git a/tests/python_package_test/test_sklearn.py b/tests/python_package_test/test_sklearn.py index 2247c9a512d2..b6aedbc415e0 100644 --- a/tests/python_package_test/test_sklearn.py +++ b/tests/python_package_test/test_sklearn.py @@ -257,6 +257,111 @@ def test_binary_classification_with_custom_objective(): assert ret < 0.05 +def test_auto_early_stopping_binary_classification(): + + X, y = load_breast_cancer(return_X_y=True) + n_estimators = 200 + gbm = lgb.LGBMClassifier( + n_estimators=n_estimators, + random_state=42, + verbose=-1, + early_stopping=True, + num_leaves=5 + ) + gbm.fit(X, y) + assert gbm._Booster.params['early_stopping_round'] == 10 + assert gbm._Booster.num_trees() == 143 + assert gbm.best_iteration_ == 143 + + +def test_auto_early_stopping_setting_early_stopping_round(): + early_stopping_round = 15 + X, y = make_synthetic_regression(n_samples=100) + n_estimators = 50 + gbm = lgb.LGBMRegressor( + n_estimators=n_estimators, + random_state=42, + early_stopping=early_stopping_round, + verbose=-1, + ) + gbm.fit(X, y) + assert gbm._Booster.params['early_stopping_round'] == early_stopping_round + assert gbm._Booster.num_trees() == 25 + assert gbm.best_iteration_ == 25 + + +def test_auto_early_stopping_categorical_features_set_during_fit(): + pd = pytest.importorskip("pandas") + np.random.seed(42) + X = pd.DataFrame({"A": pd.Categorical(np.random.permutation(['z', 'y', 'x', 'w', 'v'] * 60), + ordered=True), # str and ordered categorical + "B": np.random.permutation([1, 2, 3] * 100), # int + "C": np.random.permutation([0.1, 0.2, -0.1, -0.1, 0.2] * 60), # float + "D": np.random.permutation([True, False] * 150), # bool + }) + # np.random.seed() + cat_cols_actual = ["A", "B", "C", "D"] + y = np.random.permutation([0, 1] * 150) + n_estimators = 5 + gbm = lgb.LGBMClassifier( + n_estimators=n_estimators, + random_state=42, + verbose=-1, + early_stopping=True, + num_leaves=5 + ) + gbm.fit(X, y, categorical_feature=cat_cols_actual) + assert gbm._Booster.params['early_stopping_round'] == 10 + assert gbm._Booster.num_trees() == 1 + assert gbm.best_iteration_ == 1 + + +@pytest.mark.parametrize('n_samples, expected_n_trees', [(100, 25), (10_001, 92)]) +@pytest.mark.parametrize('early_stopping', [True, False, 'auto', 10]) +def test_auto_early_stopping_is_triggered_correctly(n_samples, expected_n_trees, early_stopping): + + X, y = make_synthetic_regression(n_samples=n_samples) + n_estimators = 100 + gbm = lgb.LGBMRegressor( + n_estimators=n_estimators, + random_state=42, + early_stopping=early_stopping, + verbose=-1, + ) + gbm.fit(X, y) + if early_stopping == 'auto' and n_samples > 10_000 or early_stopping in (True, 10): + # Check that early stopping actually kicked in + assert gbm._Booster.params['early_stopping_round'] == 10 + assert gbm._Booster.num_trees() == expected_n_trees + assert gbm.best_iteration_ == expected_n_trees + else: + # Check that early stopping did not kick in + assert gbm.best_iteration_ == 0 + assert gbm._Booster.num_trees() == n_estimators + + +@pytest.mark.skipif(getenv('TASK', '') == 'cuda', reason='Skip due to differences in implementation details of CUDA version') +def test_auto_early_stopping_lambdarank(): + rank_example_dir = Path(__file__).absolute().parents[2] / 'examples' / 'lambdarank' + X_train, y_train = load_svmlight_file(str(rank_example_dir / 'rank.train')) + q_train = np.loadtxt(str(rank_example_dir / 'rank.train.query')) + gbm = lgb.LGBMRanker( + n_estimators=5, random_state=42, early_stopping=True, num_leaves=5 + ) + gbm.fit( + X_train, + y_train, + group=q_train, + eval_at=[1, 3], + callbacks=[ + lgb.reset_parameter(learning_rate=lambda x: max(0.01, 0.1 - 0.01 * x)) + ] + ) + assert gbm._Booster.params['early_stopping_round'] == 10 + assert gbm._Booster.num_trees() == 3 + assert gbm.best_iteration_ == 3 + + def test_dart(): X, y = make_synthetic_regression() X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.1, random_state=42) From f05e5e0e550a4a65e607ce8e3b843356979eb50c Mon Sep 17 00:00:00 2001 From: Claudio Salvatore Arcidiacono <22871978+ClaudioSalvatoreArcidiacono@users.noreply.github.com> Date: Mon, 18 Sep 2023 07:16:22 +0000 Subject: [PATCH 02/24] Relax test conditions Final number of trees changes in CUDA and linux --- tests/python_package_test/test_sklearn.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/tests/python_package_test/test_sklearn.py b/tests/python_package_test/test_sklearn.py index b6aedbc415e0..9c6036ad510c 100644 --- a/tests/python_package_test/test_sklearn.py +++ b/tests/python_package_test/test_sklearn.py @@ -270,8 +270,8 @@ def test_auto_early_stopping_binary_classification(): ) gbm.fit(X, y) assert gbm._Booster.params['early_stopping_round'] == 10 - assert gbm._Booster.num_trees() == 143 - assert gbm.best_iteration_ == 143 + assert gbm._Booster.num_trees() < n_estimators + assert gbm.best_iteration_ < n_estimators def test_auto_early_stopping_setting_early_stopping_round(): @@ -345,8 +345,9 @@ def test_auto_early_stopping_lambdarank(): rank_example_dir = Path(__file__).absolute().parents[2] / 'examples' / 'lambdarank' X_train, y_train = load_svmlight_file(str(rank_example_dir / 'rank.train')) q_train = np.loadtxt(str(rank_example_dir / 'rank.train.query')) + n_estimators = 5 gbm = lgb.LGBMRanker( - n_estimators=5, random_state=42, early_stopping=True, num_leaves=5 + n_estimators=n_estimators, random_state=42, early_stopping=True, num_leaves=5 ) gbm.fit( X_train, @@ -358,8 +359,8 @@ def test_auto_early_stopping_lambdarank(): ] ) assert gbm._Booster.params['early_stopping_round'] == 10 - assert gbm._Booster.num_trees() == 3 - assert gbm.best_iteration_ == 3 + assert gbm._Booster.num_trees() < n_estimators + assert gbm.best_iteration_ < n_estimators def test_dart(): From 457c7f6b35b954d8726f078000a2cb522990eb89 Mon Sep 17 00:00:00 2001 From: Claudio Salvatore Arcidiacono <22871978+ClaudioSalvatoreArcidiacono@users.noreply.github.com> Date: Tue, 16 Jan 2024 10:39:01 +0100 Subject: [PATCH 03/24] Merge master --- .appveyor.yml | 2 +- .ci/check_python_dists.sh | 2 +- .ci/lint-cpp.sh | 24 + .ci/lint_r_code.R | 17 +- .ci/setup.sh | 8 + .ci/test-python-oldest.sh | 4 +- .ci/test.sh | 19 +- .ci/test_r_package.sh | 6 +- .ci/test_r_package_valgrind.sh | 6 +- .ci/test_r_package_windows.ps1 | 23 +- .ci/test_windows.ps1 | 2 + .github/CODEOWNERS | 2 +- .github/workflows/cuda.yml | 12 +- .github/workflows/lock.yml | 9 +- .github/workflows/r_package.yml | 10 +- .github/workflows/static_analysis.yml | 2 +- .gitignore | 2 +- .vsts-ci.yml | 8 +- CMakeLists.txt | 91 ++- R-package/DESCRIPTION | 6 +- R-package/NAMESPACE | 4 +- R-package/R/callback.R | 18 +- R-package/R/lgb.Booster.R | 47 +- R-package/R/lgb.DataProcessor.R | 2 +- R-package/R/lgb.Dataset.R | 76 +- R-package/R/lgb.Predictor.R | 10 +- R-package/R/lgb.cv.R | 40 +- R-package/R/lgb.drop_serialized.R | 2 +- R-package/R/lgb.importance.R | 4 +- R-package/R/lgb.interprete.R | 10 +- R-package/R/lgb.make_serializable.R | 2 +- R-package/R/lgb.model.dt.tree.R | 9 +- R-package/R/lgb.plot.importance.R | 2 + R-package/R/lgb.plot.interpretation.R | 8 +- R-package/R/lgb.restore_handle.R | 6 +- R-package/R/lgb.train.R | 36 +- R-package/R/lightgbm.R | 10 +- R-package/R/multithreading.R | 51 ++ R-package/R/readRDS.lgb.Booster.R | 48 -- R-package/R/saveRDS.lgb.Booster.R | 78 -- R-package/R/utils.R | 20 +- R-package/README.md | 9 +- R-package/configure | 18 +- R-package/cran-comments.md | 11 + R-package/demo/basic_walkthrough.R | 1 - R-package/demo/boost_from_prediction.R | 1 - R-package/demo/early_stopping.R | 1 - R-package/man/dim.Rd | 2 + R-package/man/dimnames.lgb.Dataset.Rd | 2 + R-package/man/getLGBMThreads.Rd | 26 + R-package/man/get_field.Rd | 2 + R-package/man/lgb.Dataset.Rd | 2 + R-package/man/lgb.Dataset.construct.Rd | 2 + R-package/man/lgb.Dataset.create.valid.Rd | 2 + R-package/man/lgb.Dataset.save.Rd | 2 + R-package/man/lgb.Dataset.set.categorical.Rd | 2 + R-package/man/lgb.Dataset.set.reference.Rd | 2 + R-package/man/lgb.configure_fast_predict.Rd | 2 + R-package/man/lgb.cv.Rd | 2 + R-package/man/lgb.dump.Rd | 2 + R-package/man/lgb.get.eval.result.Rd | 2 + R-package/man/lgb.importance.Rd | 2 + R-package/man/lgb.interprete.Rd | 2 + R-package/man/lgb.load.Rd | 2 + R-package/man/lgb.model.dt.tree.Rd | 2 + R-package/man/lgb.plot.importance.Rd | 2 + R-package/man/lgb.plot.interpretation.Rd | 2 + R-package/man/lgb.restore_handle.Rd | 4 + R-package/man/lgb.save.Rd | 2 + R-package/man/lgb.train.Rd | 2 + R-package/man/predict.lgb.Booster.Rd | 2 + R-package/man/readRDS.lgb.Booster.Rd | 51 -- R-package/man/saveRDS.lgb.Booster.Rd | 73 -- R-package/man/setLGBMThreads.Rd | 32 + R-package/man/set_field.Rd | 2 + R-package/man/slice.Rd | 2 + R-package/pkgdown/_pkgdown.yml | 2 - R-package/src/Makevars.in | 1 + R-package/src/Makevars.win.in | 1 + R-package/src/lightgbm_R.cpp | 82 ++- R-package/src/lightgbm_R.h | 19 + R-package/tests/testthat.R | 2 +- R-package/tests/testthat/helper.R | 5 + R-package/tests/testthat/test_Predictor.R | 10 +- R-package/tests/testthat/test_basic.R | 45 +- R-package/tests/testthat/test_dataset.R | 20 +- .../tests/testthat/test_learning_to_rank.R | 11 +- R-package/tests/testthat/test_lgb.Booster.R | 115 +-- .../tests/testthat/test_multithreading.R | 16 + R-package/tests/testthat/test_utils.R | 66 +- R-package/vignettes/basic_walkthrough.Rmd | 16 +- README.md | 3 +- VERSION.txt | 2 +- build-cran-package.sh | 3 +- docs/.linkcheckerrc | 2 +- docs/Experiments.rst | 26 +- docs/FAQ.rst | 49 +- docs/Features.rst | 2 +- docs/GPU-Performance.rst | 8 +- docs/GPU-Targets.rst | 2 +- docs/GPU-Tutorial.rst | 2 +- docs/GPU-Windows.rst | 4 +- docs/Installation-Guide.rst | 42 +- docs/Parallel-Learning-Guide.rst | 10 +- docs/Parameters.rst | 22 +- docs/Quick-Start.rst | 4 +- docs/env.yml | 2 +- docs/gcc-Tips.rst | 2 - include/LightGBM/arrow.h | 259 +++++++ include/LightGBM/arrow.tpp | 190 +++++ include/LightGBM/bin.h | 6 +- include/LightGBM/c_api.h | 85 +++ include/LightGBM/config.h | 16 +- include/LightGBM/cuda/cuda_algorithms.hpp | 2 +- include/LightGBM/cuda/cuda_column_data.hpp | 2 +- include/LightGBM/cuda/cuda_metadata.hpp | 2 +- include/LightGBM/cuda/cuda_metric.hpp | 2 +- .../LightGBM/cuda/cuda_objective_function.hpp | 2 +- include/LightGBM/cuda/cuda_row_data.hpp | 2 +- include/LightGBM/cuda/cuda_split_info.hpp | 2 + .../cuda/{cuda_utils.h => cuda_utils.hu} | 18 +- include/LightGBM/dataset.h | 50 +- include/LightGBM/feature_group.h | 2 +- include/LightGBM/sample_strategy.h | 2 +- include/LightGBM/tree.h | 6 +- include/LightGBM/utils/common.h | 4 +- include/LightGBM/utils/openmp_wrapper.h | 47 +- include/LightGBM/utils/threading.h | 2 +- python-package/lightgbm/basic.py | 454 +++++++++--- python-package/lightgbm/callback.py | 64 +- python-package/lightgbm/compat.py | 63 ++ python-package/lightgbm/dask.py | 6 +- python-package/lightgbm/libpath.py | 3 +- python-package/lightgbm/sklearn.py | 81 +- python-package/pyproject.toml | 8 +- src/application/application.cpp | 2 +- src/application/predictor.hpp | 2 +- src/boosting/cuda/cuda_score_updater.hpp | 2 +- src/boosting/gbdt.cpp | 10 +- src/boosting/gbdt.h | 13 +- src/boosting/gbdt_model_text.cpp | 4 +- src/boosting/rf.hpp | 2 +- src/boosting/score_updater.hpp | 6 +- src/c_api.cpp | 204 +++++- src/cuda/cuda_utils.cpp | 2 +- src/io/config.cpp | 4 - src/io/dataset.cpp | 33 +- src/io/dataset_loader.cpp | 14 +- src/io/metadata.cpp | 142 ++-- src/io/multi_val_dense_bin.hpp | 2 +- src/io/multi_val_sparse_bin.hpp | 4 +- src/io/train_share_states.cpp | 8 +- src/io/tree.cpp | 4 +- src/metric/binary_metric.hpp | 8 +- src/metric/cuda/cuda_binary_metric.hpp | 2 +- src/metric/cuda/cuda_pointwise_metric.hpp | 2 +- src/metric/cuda/cuda_regression_metric.hpp | 2 +- src/metric/map_metric.hpp | 4 +- src/metric/multiclass_metric.hpp | 8 +- src/metric/rank_metric.hpp | 6 +- src/metric/regression_metric.hpp | 8 +- src/metric/xentropy_metric.hpp | 24 +- src/objective/binary_objective.hpp | 10 +- src/objective/multiclass_objective.hpp | 4 +- src/objective/rank_objective.hpp | 15 +- src/objective/regression_objective.hpp | 46 +- src/objective/xentropy_objective.hpp | 16 +- src/treelearner/col_sampler.hpp | 6 +- .../cuda/cuda_best_split_finder.cpp | 19 +- .../cuda/cuda_best_split_finder.cu | 434 +++++++++++ .../cuda/cuda_best_split_finder.hpp | 31 +- src/treelearner/cuda/cuda_data_partition.cpp | 6 + src/treelearner/cuda/cuda_data_partition.cu | 47 ++ src/treelearner/cuda/cuda_data_partition.hpp | 8 + .../cuda/cuda_gradient_discretizer.cu | 171 +++++ .../cuda/cuda_gradient_discretizer.hpp | 118 +++ .../cuda/cuda_histogram_constructor.cpp | 110 ++- .../cuda/cuda_histogram_constructor.cu | 692 +++++++++++++++--- .../cuda/cuda_histogram_constructor.hpp | 61 +- src/treelearner/cuda/cuda_leaf_splits.cpp | 56 +- src/treelearner/cuda/cuda_leaf_splits.cu | 132 +++- src/treelearner/cuda/cuda_leaf_splits.hpp | 33 +- .../cuda/cuda_single_gpu_tree_learner.cpp | 156 +++- .../cuda/cuda_single_gpu_tree_learner.cu | 38 +- .../cuda/cuda_single_gpu_tree_learner.hpp | 13 +- .../data_parallel_tree_learner.cpp | 8 +- src/treelearner/data_partition.hpp | 2 +- src/treelearner/feature_histogram.hpp | 8 +- src/treelearner/gpu_tree_learner.cpp | 36 +- src/treelearner/gradient_discretizer.cpp | 4 +- src/treelearner/leaf_splits.hpp | 8 +- src/treelearner/linear_tree_learner.cpp | 16 +- src/treelearner/linear_tree_learner.h | 2 +- src/treelearner/serial_tree_learner.cpp | 6 +- src/treelearner/serial_tree_learner.h | 2 +- .../voting_parallel_tree_learner.cpp | 8 +- src/utils/openmp_wrapper.cpp | 44 ++ tests/c_api_test/test_.py | 33 + tests/cpp_tests/test_arrow.cpp | 212 ++++++ tests/cpp_tests/test_byte_buffer.cpp | 6 +- tests/cpp_tests/testutils.cpp | 2 +- tests/python_package_test/test_arrow.py | 387 ++++++++++ tests/python_package_test/test_basic.py | 109 ++- tests/python_package_test/test_callback.py | 11 + tests/python_package_test/test_engine.py | 39 +- tests/python_package_test/test_sklearn.py | 7 +- tests/python_package_test/utils.py | 20 + windows/LightGBM.vcxproj | 1 + windows/LightGBM.vcxproj.filters | 3 + 209 files changed, 5209 insertions(+), 1498 deletions(-) create mode 100644 R-package/R/multithreading.R delete mode 100644 R-package/R/readRDS.lgb.Booster.R delete mode 100644 R-package/R/saveRDS.lgb.Booster.R create mode 100644 R-package/man/getLGBMThreads.Rd delete mode 100644 R-package/man/readRDS.lgb.Booster.Rd delete mode 100644 R-package/man/saveRDS.lgb.Booster.Rd create mode 100644 R-package/man/setLGBMThreads.Rd create mode 100644 R-package/tests/testthat/test_multithreading.R create mode 100644 include/LightGBM/arrow.h create mode 100644 include/LightGBM/arrow.tpp rename include/LightGBM/cuda/{cuda_utils.h => cuda_utils.hu} (91%) create mode 100644 src/treelearner/cuda/cuda_gradient_discretizer.cu create mode 100644 src/treelearner/cuda/cuda_gradient_discretizer.hpp create mode 100644 src/utils/openmp_wrapper.cpp create mode 100644 tests/cpp_tests/test_arrow.cpp create mode 100644 tests/python_package_test/test_arrow.py diff --git a/.appveyor.yml b/.appveyor.yml index 4cff03d571a1..58a536d17a98 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -1,4 +1,4 @@ -version: 4.1.0.99.{build} +version: 4.2.0.99.{build} image: Visual Studio 2015 platform: x64 diff --git a/.ci/check_python_dists.sh b/.ci/check_python_dists.sh index cb0bbae79fa9..1dd19679daae 100644 --- a/.ci/check_python_dists.sh +++ b/.ci/check_python_dists.sh @@ -25,7 +25,7 @@ if [ $PY_MINOR_VER -gt 7 ]; then pydistcheck \ --inspect \ --ignore 'compiled-objects-have-debug-symbols,distro-too-large-compressed' \ - --max-allowed-size-uncompressed '60M' \ + --max-allowed-size-uncompressed '100M' \ --max-allowed-files 800 \ ${DIST_DIR}/* || exit -1 elif { test $(uname -m) = "aarch64"; }; then diff --git a/.ci/lint-cpp.sh b/.ci/lint-cpp.sh index ef9fff683731..2d91f8e85f00 100755 --- a/.ci/lint-cpp.sh +++ b/.ci/lint-cpp.sh @@ -18,3 +18,27 @@ cmakelint \ ${cmake_files} \ || exit -1 echo "done running cmakelint" + +echo "checking that all OpenMP pragmas specify num_threads()" +get_omp_pragmas_without_num_threads() { + grep \ + -n \ + -R \ + --include='*.c' \ + --include='*.cc' \ + --include='*.cpp' \ + --include='*.h' \ + --include='*.hpp' \ + 'pragma omp parallel' \ + | grep -v ' num_threads' +} +PROBLEMATIC_LINES=$( + get_omp_pragmas_without_num_threads +) +if test "${PROBLEMATIC_LINES}" != ""; then + get_omp_pragmas_without_num_threads + echo "Found '#pragma omp parallel' not using explicit num_threads() configuration. Fix those." + echo "For details, see https://www.openmp.org/spec-html/5.0/openmpse14.html#x54-800002.6" + exit -1 +fi +echo "done checking OpenMP pragmas" diff --git a/.ci/lint_r_code.R b/.ci/lint_r_code.R index 12116104ef6d..d5b1217a5b04 100755 --- a/.ci/lint_r_code.R +++ b/.ci/lint_r_code.R @@ -1,5 +1,5 @@ -library(lintr) +loadNamespace("lintr") args <- commandArgs( trailingOnly = TRUE @@ -33,30 +33,34 @@ LINTERS_TO_USE <- list( , "any_duplicated" = lintr::any_duplicated_linter() , "any_is_na" = lintr::any_is_na_linter() , "assignment" = lintr::assignment_linter() + , "backport" = lintr::backport_linter() , "boolean_arithmetic" = lintr::boolean_arithmetic_linter() , "braces" = lintr::brace_linter() , "class_equals" = lintr::class_equals_linter() , "commas" = lintr::commas_linter() + , "conjunct_test" = lintr::conjunct_test_linter() , "duplicate_argument" = lintr::duplicate_argument_linter() , "empty_assignment" = lintr::empty_assignment_linter() , "equals_na" = lintr::equals_na_linter() + , "fixed_regex" = lintr::fixed_regex_linter() , "for_loop_index" = lintr::for_loop_index_linter() , "function_left" = lintr::function_left_parentheses_linter() + , "function_return" = lintr::function_return_linter() + , "implicit_assignment" = lintr::implicit_assignment_linter() , "implicit_integers" = lintr::implicit_integer_linter() , "infix_spaces" = lintr::infix_spaces_linter() , "inner_combine" = lintr::inner_combine_linter() , "is_numeric" = lintr::is_numeric_linter() - , "fixed_regex" = lintr::fixed_regex_linter() - , "function_return" = lintr::function_return_linter() , "lengths" = lintr::lengths_linter() + , "line_length" = lintr::line_length_linter(length = 120L) , "literal_coercion" = lintr::literal_coercion_linter() - , "long_lines" = lintr::line_length_linter(length = 120L) , "matrix" = lintr::matrix_apply_linter() , "missing_argument" = lintr::missing_argument_linter() , "non_portable_path" = lintr::nonportable_path_linter() , "numeric_leading_zero" = lintr::numeric_leading_zero_linter() , "outer_negation" = lintr::outer_negation_linter() , "package_hooks" = lintr::package_hooks_linter() + , "paren_body" = lintr::paren_body_linter() , "paste" = lintr::paste_linter() , "quotes" = lintr::quotes_linter() , "redundant_equals" = lintr::redundant_equals_linter() @@ -74,8 +78,7 @@ LINTERS_TO_USE <- list( , "true_false" = lintr::T_and_F_symbol_linter() , "undesirable_function" = lintr::undesirable_function_linter( fun = c( - "cat" = "CRAN forbids the use of cat() in packages except in special cases. Use message() or warning()." - , "cbind" = paste0( + "cbind" = paste0( "cbind is an unsafe way to build up a data frame. merge() or direct " , "column assignment is preferred." ) @@ -100,6 +103,7 @@ LINTERS_TO_USE <- list( "%>%" = pipe_text , "%.%" = pipe_text , "%..%" = pipe_text + , "|>" = pipe_text , "?" = interactive_text , "??" = interactive_text ) @@ -107,6 +111,7 @@ LINTERS_TO_USE <- list( , "unnecessary_concatenation" = lintr::unnecessary_concatenation_linter() , "unnecessary_lambda" = lintr::unnecessary_lambda_linter() , "unreachable_code" = lintr::unreachable_code_linter() + , "unused_import" = lintr::unused_import_linter() , "vector_logic" = lintr::vector_logic_linter() , "whitespace" = lintr::whitespace_linter() ) diff --git a/.ci/setup.sh b/.ci/setup.sh index f7da21286d7d..bc17fee03308 100755 --- a/.ci/setup.sh +++ b/.ci/setup.sh @@ -54,6 +54,14 @@ else # Linux sudo apt-get install --no-install-recommends -y \ clang \ libomp-dev + elif [[ $COMPILER == "clang-17" ]]; then + sudo apt-get install wget + wget -qO- https://apt.llvm.org/llvm-snapshot.gpg.key | sudo tee /etc/apt/trusted.gpg.d/apt.llvm.org.asc + sudo apt-add-repository deb http://apt.llvm.org/jammy/ llvm-toolchain-jammy-17 main + sudo apt-add-repository deb-src http://apt.llvm.org/jammy/ llvm-toolchain-jammy-17 main + sudo apt-get update + sudo apt-get install -y clang-17 + sudo apt-get install --no-install-recommends -y libomp-17-dev fi export LANG="en_US.UTF-8" diff --git a/.ci/test-python-oldest.sh b/.ci/test-python-oldest.sh index 3a0ea08dddda..40dfd393f1fe 100644 --- a/.ci/test-python-oldest.sh +++ b/.ci/test-python-oldest.sh @@ -7,9 +7,11 @@ # echo "installing lightgbm's dependencies" pip install \ + 'cffi==1.15.1' \ 'dataclasses' \ - 'numpy==1.12.0' \ + 'numpy==1.16.6' \ 'pandas==0.24.0' \ + 'pyarrow==6.0.1' \ 'scikit-learn==0.18.2' \ 'scipy==0.19.0' \ || exit -1 diff --git a/.ci/test.sh b/.ci/test.sh index af7cae2e3858..472fd7d8c6e5 100755 --- a/.ci/test.sh +++ b/.ci/test.sh @@ -6,6 +6,9 @@ if [[ $OS_NAME == "macos" ]] && [[ $COMPILER == "gcc" ]]; then elif [[ $OS_NAME == "linux" ]] && [[ $COMPILER == "clang" ]]; then export CXX=clang++ export CC=clang +elif [[ $OS_NAME == "linux" ]] && [[ $COMPILER == "clang-17" ]]; then + export CXX=clang++-17 + export CC=clang-17 fi if [[ $IN_UBUNTU_BASE_CONTAINER == "true" ]]; then @@ -37,7 +40,7 @@ fi CONDA_PYTHON_REQUIREMENT="python=$PYTHON_VERSION[build=*cpython]" if [[ $TASK == "if-else" ]]; then - conda create -q -y -n $CONDA_ENV ${CONDA_PYTHON_REQUIREMENT} numpy + mamba create -q -y -n $CONDA_ENV ${CONDA_PYTHON_REQUIREMENT} numpy source activate $CONDA_ENV mkdir $BUILD_DIRECTORY/build && cd $BUILD_DIRECTORY/build && cmake .. && make lightgbm -j4 || exit -1 cd $BUILD_DIRECTORY/tests/cpp_tests && ../../lightgbm config=train.conf convert_model_language=cpp convert_model=../../src/boosting/gbdt_prediction.cpp && ../../lightgbm config=predict.conf output_result=origin.pred || exit -1 @@ -67,7 +70,7 @@ fi if [[ $TASK == "lint" ]]; then cd ${BUILD_DIRECTORY} - conda create -q -y -n $CONDA_ENV \ + mamba create -q -y -n $CONDA_ENV \ ${CONDA_PYTHON_REQUIREMENT} \ cmakelint \ cpplint \ @@ -87,10 +90,10 @@ fi if [[ $TASK == "check-docs" ]] || [[ $TASK == "check-links" ]]; then cd $BUILD_DIRECTORY/docs - conda env create \ + mamba env create \ -n $CONDA_ENV \ --file ./env.yml || exit -1 - conda install \ + mamba install \ -q \ -y \ -n $CONDA_ENV \ @@ -128,13 +131,15 @@ if [[ $PYTHON_VERSION == "3.7" ]]; then fi # including python=version[build=*cpython] to ensure that conda doesn't fall back to pypy -conda create -q -y -n $CONDA_ENV \ +mamba create -q -y -n $CONDA_ENV \ ${CONSTRAINED_DEPENDENCIES} \ + cffi \ cloudpickle \ joblib \ matplotlib \ numpy \ psutil \ + pyarrow \ pytest \ ${CONDA_PYTHON_REQUIREMENT} \ python-graphviz \ @@ -304,7 +309,7 @@ matplotlib.use\(\"Agg\"\)\ ' plot_example.py # prevent interactive window mode sed -i'.bak' 's/graph.render(view=True)/graph.render(view=False)/' plot_example.py # requirements for examples - conda install -q -y -n $CONDA_ENV \ + mamba install -q -y -n $CONDA_ENV \ h5py \ ipywidgets \ notebook @@ -315,11 +320,13 @@ matplotlib.use\(\"Agg\"\)\ # importing the library should succeed even if all optional dependencies are not present conda uninstall --force --yes \ + cffi \ dask \ distributed \ joblib \ matplotlib \ psutil \ + pyarrow \ python-graphviz \ scikit-learn || exit -1 python -c "import lightgbm" || exit -1 diff --git a/.ci/test_r_package.sh b/.ci/test_r_package.sh index e4d70261aa36..6d421f16be56 100755 --- a/.ci/test_r_package.sh +++ b/.ci/test_r_package.sh @@ -127,13 +127,13 @@ if [[ "${R_MAJOR_VERSION}" == "3" ]]; then Rscript --vanilla -e "install.packages('https://cran.r-project.org/src/contrib/Archive/lattice/lattice_0.20-41.tar.gz', repos = NULL, lib = '${R_LIB_PATH}')" fi -# Manually install Depends and Imports libraries + 'knitr', 'RhpcBLASctl', 'rmarkdown', 'testthat' +# Manually install Depends and Imports libraries + 'knitr', 'markdown', 'RhpcBLASctl', 'testthat' # to avoid a CI-time dependency on devtools (for devtools::install_deps()) # NOTE: testthat is not required when running rchk if [[ "${TASK}" == "r-rchk" ]]; then - packages="c('data.table', 'jsonlite', 'knitr', 'Matrix', 'R6', 'RhpcBLASctl', 'rmarkdown')" + packages="c('data.table', 'jsonlite', 'knitr', 'markdown', 'Matrix', 'R6', 'RhpcBLASctl')" else - packages="c('data.table', 'jsonlite', 'knitr', 'Matrix', 'R6', 'RhpcBLASctl', 'rmarkdown', 'testthat')" + packages="c('data.table', 'jsonlite', 'knitr', 'markdown', 'Matrix', 'R6', 'RhpcBLASctl', 'testthat')" fi compile_from_source="both" if [[ $OS_NAME == "macos" ]]; then diff --git a/.ci/test_r_package_valgrind.sh b/.ci/test_r_package_valgrind.sh index 9eddec2deed1..bd8f1f71f9f1 100755 --- a/.ci/test_r_package_valgrind.sh +++ b/.ci/test_r_package_valgrind.sh @@ -1,9 +1,11 @@ #!/bin/bash -RDscriptvalgrind -e "install.packages(c('R6', 'data.table', 'jsonlite', 'knitr', 'Matrix', 'RhpcBLASctl', 'rmarkdown', 'testthat'), repos = 'https://cran.rstudio.com', Ncpus = parallel::detectCores())" || exit -1 +RDscriptvalgrind -e "install.packages(c('R6', 'data.table', 'jsonlite', 'Matrix', 'RhpcBLASctl', 'testthat'), repos = 'https://cran.rstudio.com')" || exit -1 sh build-cran-package.sh \ --r-executable=RDvalgrind \ + --no-build-vignettes \ || exit -1 + RDvalgrind CMD INSTALL --preclean --install-tests lightgbm_*.tar.gz || exit -1 cd R-package/tests @@ -68,7 +70,7 @@ bytes_possibly_lost=$( | tr -d "," ) echo "valgrind found ${bytes_possibly_lost} bytes possibly lost" -if [[ ${bytes_possibly_lost} -gt 352 ]]; then +if [[ ${bytes_possibly_lost} -gt 1056 ]]; then exit -1 fi diff --git a/.ci/test_r_package_windows.ps1 b/.ci/test_r_package_windows.ps1 index e4d20de50b90..c4ae84f49dae 100644 --- a/.ci/test_r_package_windows.ps1 +++ b/.ci/test_r_package_windows.ps1 @@ -55,12 +55,14 @@ Remove-From-Path ".*\\R\\.*" Remove-From-Path ".*R Client.*" Remove-From-Path ".*rtools40.*" Remove-From-Path ".*rtools42.*" +Remove-From-Path ".*rtools43.*" Remove-From-Path ".*shells.*" Remove-From-Path ".*Strawberry.*" Remove-From-Path ".*tools.*" Remove-Item C:\rtools40 -Force -Recurse -ErrorAction Ignore Remove-Item C:\rtools42 -Force -Recurse -ErrorAction Ignore +Remove-Item C:\rtools43 -Force -Recurse -ErrorAction Ignore # Get details needed for installing R components # @@ -76,11 +78,11 @@ if ($env:R_MAJOR_VERSION -eq "3") { $env:RTOOLS_EXE_FILE = "rtools35-x86_64.exe" $env:R_WINDOWS_VERSION = "3.6.3" } elseif ($env:R_MAJOR_VERSION -eq "4") { - $RTOOLS_INSTALL_PATH = "C:\rtools42" + $RTOOLS_INSTALL_PATH = "C:\rtools43" $env:RTOOLS_BIN = "$RTOOLS_INSTALL_PATH\usr\bin" $env:RTOOLS_MINGW_BIN = "$RTOOLS_INSTALL_PATH\x86_64-w64-mingw32.static.posix\bin" - $env:RTOOLS_EXE_FILE = "rtools42-5253-5107.exe" - $env:R_WINDOWS_VERSION = "4.2.2" + $env:RTOOLS_EXE_FILE = "rtools43-5550-5548.exe" + $env:R_WINDOWS_VERSION = "4.3.1" } else { Write-Output "[ERROR] Unrecognized R version: $env:R_VERSION" Check-Output $false @@ -122,7 +124,7 @@ Start-Process -FilePath Rtools.exe -NoNewWindow -Wait -ArgumentList "/VERYSILENT Write-Output "Done installing Rtools" Write-Output "Installing dependencies" -$packages = "c('data.table', 'jsonlite', 'knitr', 'Matrix', 'processx', 'R6', 'RhpcBLASctl', 'rmarkdown', 'testthat'), dependencies = c('Imports', 'Depends', 'LinkingTo')" +$packages = "c('data.table', 'jsonlite', 'knitr', 'markdown', 'Matrix', 'processx', 'R6', 'RhpcBLASctl', 'testthat'), dependencies = c('Imports', 'Depends', 'LinkingTo')" Run-R-Code-Redirect-Stderr "options(install.packages.check.source = 'no'); install.packages($packages, repos = '$env:CRAN_MIRROR', type = 'binary', lib = '$env:R_LIB_PATH', Ncpus = parallel::detectCores())" ; Check-Output $? Write-Output "Building R package" @@ -203,6 +205,19 @@ if ($env:COMPILER -ne "MSVC") { } } +# Checking that the correct R version was used +if ($env:TOOLCHAIN -ne "MSVC") { + $checks = Select-String -Path "${LOG_FILE_NAME}" -Pattern "using R version $env:R_WINDOWS_VERSION" + $checks_cnt = $checks.Matches.length +} else { + $checks = Select-String -Path "${INSTALL_LOG_FILE_NAME}" -Pattern "R version passed into FindLibR.* $env:R_WINDOWS_VERSION" + $checks_cnt = $checks.Matches.length +} +if ($checks_cnt -eq 0) { + Write-Output "Wrong R version was found (expected '$env:R_WINDOWS_VERSION'). Check the build logs." + Check-Output $False +} + # Checking that we actually got the expected compiler. The R package has some logic # to fail back to MinGW if MSVC fails, but for CI builds we need to check that the correct # compiler was used. diff --git a/.ci/test_windows.ps1 b/.ci/test_windows.ps1 index 413af821e065..6b02aed6ce8b 100644 --- a/.ci/test_windows.ps1 +++ b/.ci/test_windows.ps1 @@ -52,12 +52,14 @@ conda install brotlipy conda update -q -y conda conda create -q -y -n $env:CONDA_ENV ` + cffi ` cloudpickle ` joblib ` matplotlib ` numpy ` pandas ` psutil ` + pyarrow ` pytest ` "python=$env:PYTHON_VERSION[build=*cpython]" ` python-graphviz ` diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 02b5cfbdae23..b6885cad0503 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -7,4 +7,4 @@ # offer a reasonable automatic best-guess # catch-all rule (this only gets matched if no rules below match) -* @guolinke @jameslamb @shiyu1994 @jmoralez +* @guolinke @jameslamb @shiyu1994 @jmoralez @borchero diff --git a/.github/workflows/cuda.yml b/.github/workflows/cuda.yml index 9ab8440a6625..3120fb1fae9f 100644 --- a/.github/workflows/cuda.yml +++ b/.github/workflows/cuda.yml @@ -31,17 +31,17 @@ jobs: - method: wheel compiler: gcc python_version: "3.11" - cuda_version: "11.7.1" + cuda_version: "11.8.0" task: cuda - method: source compiler: gcc python_version: "3.9" - cuda_version: "10.0" + cuda_version: "12.2.0" task: cuda - method: pip compiler: clang python_version: "3.10" - cuda_version: "11.7.1" + cuda_version: "11.8.0" task: cuda steps: - name: Setup or update software on host machine @@ -98,8 +98,10 @@ jobs: cuda_version="${{ matrix.cuda_version }}" cuda_major=${cuda_version%%.*} docker_img="nvcr.io/nvidia/cuda:${cuda_version}-devel" - if [[ ${cuda_major} -gt 10 ]]; then - docker_img="${docker_img}-ubuntu$(lsb_release -rs)" + if [[ ${cuda_major} -eq 11 ]]; then + docker_img="${docker_img}-ubuntu18.04" + elif [[ ${cuda_major} -ge 12 ]]; then + docker_img="${docker_img}-ubuntu20.04" fi docker run --env-file docker.env -v "$GITHUB_WORKSPACE":"$ROOT_DOCKER_FOLDER" --rm --gpus all "$docker_img" /bin/bash $ROOT_DOCKER_FOLDER/docker-script.sh all-cuda-jobs-successful: diff --git a/.github/workflows/lock.yml b/.github/workflows/lock.yml index 72d8b7c2f585..4efe658b7f45 100644 --- a/.github/workflows/lock.yml +++ b/.github/workflows/lock.yml @@ -18,15 +18,15 @@ jobs: action: runs-on: ubuntu-latest steps: - - uses: dessant/lock-threads@v4 + - uses: dessant/lock-threads@v5 with: github-token: ${{ github.token }} # after how many days of inactivity should a closed issue/PR be locked? - issue-inactive-days: '90' - pr-inactive-days: '90' + issue-inactive-days: '365' + pr-inactive-days: '365' # do not close feature request issues... # we close those but track them in https://github.com/microsoft/LightGBM/issues/2302 - exclude-any-issue-labels: '"feature request"' + exclude-any-issue-labels: 'feature request' # what labels should be removed prior to locking? remove-issue-labels: 'awaiting response,awaiting review,blocking,in progress' remove-pr-labels: 'awaiting response,awaiting review,blocking,in progress' @@ -42,3 +42,4 @@ jobs: # what shoulld the locking status be? issue-lock-reason: 'resolved' pr-lock-reason: 'resolved' + process-only: 'issues, prs' diff --git a/.github/workflows/r_package.yml b/.github/workflows/r_package.yml index 838528617143..4fa7f2ff8683 100644 --- a/.github/workflows/r_package.yml +++ b/.github/workflows/r_package.yml @@ -86,7 +86,7 @@ jobs: task: r-package compiler: MINGW toolchain: MSYS - r_version: 4.2 + r_version: 4.3 build_type: cmake container: null # Visual Studio 2019 @@ -102,7 +102,7 @@ jobs: task: r-package compiler: MSVC toolchain: MSVC - r_version: 4.2 + r_version: 4.3 build_type: cmake container: null ############### @@ -119,7 +119,7 @@ jobs: task: r-package compiler: MINGW toolchain: MSYS - r_version: 4.2 + r_version: 4.3 build_type: cran container: null - os: ubuntu-latest @@ -246,7 +246,7 @@ jobs: - name: Install packages shell: bash run: | - RDscript${{ matrix.r_customization }} -e "install.packages(c('R6', 'data.table', 'jsonlite', 'knitr', 'Matrix', 'RhpcBLASctl', 'rmarkdown', 'testthat'), repos = 'https://cran.rstudio.com', Ncpus = parallel::detectCores())" + RDscript${{ matrix.r_customization }} -e "install.packages(c('R6', 'data.table', 'jsonlite', 'knitr', 'markdown', 'Matrix', 'RhpcBLASctl', 'testthat'), repos = 'https://cran.rstudio.com', Ncpus = parallel::detectCores())" sh build-cran-package.sh --r-executable=RD${{ matrix.r_customization }} RD${{ matrix.r_customization }} CMD INSTALL lightgbm_*.tar.gz || exit -1 - name: Run tests with sanitizers @@ -320,7 +320,7 @@ jobs: shell: bash run: | export PATH=/opt/R-devel/bin/:${PATH} - Rscript -e "install.packages(c('R6', 'data.table', 'jsonlite', 'knitr', 'Matrix', 'RhpcBLASctl', 'rmarkdown', 'testthat'), repos = 'https://cran.rstudio.com', Ncpus = parallel::detectCores())" + Rscript -e "install.packages(c('R6', 'data.table', 'jsonlite', 'knitr', 'markdown', 'Matrix', 'RhpcBLASctl', 'testthat'), repos = 'https://cran.rstudio.com', Ncpus = parallel::detectCores())" sh build-cran-package.sh R CMD check --as-cran --run-donttest lightgbm_*.tar.gz || exit -1 if grep -q -E "NOTE|WARNING|ERROR" lightgbm.Rcheck/00check.log; then diff --git a/.github/workflows/static_analysis.yml b/.github/workflows/static_analysis.yml index bf369e79c0c5..33d50b751197 100644 --- a/.github/workflows/static_analysis.yml +++ b/.github/workflows/static_analysis.yml @@ -66,7 +66,7 @@ jobs: - name: Install packages shell: bash run: | - Rscript -e "install.packages(c('R6', 'data.table', 'jsonlite', 'knitr', 'Matrix', 'RhpcBLASctl', 'rmarkdown', 'roxygen2', 'testthat'), repos = 'https://cran.rstudio.com', Ncpus = parallel::detectCores())" + Rscript -e "install.packages(c('R6', 'data.table', 'jsonlite', 'knitr', 'markdown', 'Matrix', 'RhpcBLASctl', 'roxygen2', 'testthat'), repos = 'https://cran.rstudio.com', Ncpus = parallel::detectCores())" sh build-cran-package.sh || exit -1 R CMD INSTALL --with-keep.source lightgbm_*.tar.gz || exit -1 - name: Test documentation diff --git a/.gitignore b/.gitignore index bcf6f48b4cea..9403475cc190 100644 --- a/.gitignore +++ b/.gitignore @@ -266,7 +266,7 @@ _Pvt_Extensions *.out *.app /windows/LightGBM.VC.db -lightgbm +/lightgbm /testlightgbm # Created by https://www.gitignore.io/api/python diff --git a/.vsts-ci.yml b/.vsts-ci.yml index 8c4e3e6a4949..f3f3fd8dd1fe 100644 --- a/.vsts-ci.yml +++ b/.vsts-ci.yml @@ -34,7 +34,7 @@ jobs: SETUP_CONDA: 'false' OS_NAME: 'linux' PRODUCES_ARTIFACTS: 'true' - pool: sh-ubuntu + pool: sh-mariner container: linux-artifact-builder strategy: matrix: @@ -82,12 +82,12 @@ jobs: - job: Linux_latest ########################################### variables: - COMPILER: clang + COMPILER: clang-17 DEBIAN_FRONTEND: 'noninteractive' IN_UBUNTU_BASE_CONTAINER: 'true' OS_NAME: 'linux' SETUP_CONDA: 'true' - pool: sh-ubuntu + pool: sh-mariner container: ubuntu-latest strategy: matrix: @@ -309,7 +309,7 @@ jobs: R_LIB_PATH=~/Rlib export R_LIBS=${R_LIB_PATH} mkdir -p ${R_LIB_PATH} - RDscript -e "install.packages(c('R6', 'data.table', 'jsonlite', 'knitr', 'Matrix', 'RhpcBLASctl', 'rmarkdown'), lib = '${R_LIB_PATH}', dependencies = c('Depends', 'Imports', 'LinkingTo'), repos = 'https://cran.rstudio.com', Ncpus = parallel::detectCores())" || exit -1 + RDscript -e "install.packages(c('R6', 'data.table', 'jsonlite', 'knitr', 'markdown', 'Matrix', 'RhpcBLASctl'), lib = '${R_LIB_PATH}', dependencies = c('Depends', 'Imports', 'LinkingTo'), repos = 'https://cran.rstudio.com', Ncpus = parallel::detectCores())" || exit -1 sh build-cran-package.sh --r-executable=RD || exit -1 mv lightgbm_${LGB_VER}.tar.gz $(Build.ArtifactStagingDirectory)/lightgbm-${LGB_VER}-r-cran.tar.gz displayName: 'Build CRAN R-package' diff --git a/CMakeLists.txt b/CMakeLists.txt index 6705ef130052..1ff289b9d045 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -27,20 +27,17 @@ if(APPLE) option(APPLE_OUTPUT_DYLIB "Output dylib shared library" OFF) endif() -if(__INTEGRATE_OPENCL) - cmake_minimum_required(VERSION 3.11) -elseif(USE_SWIG) - cmake_minimum_required(VERSION 3.8) -elseif(USE_GPU OR APPLE) - cmake_minimum_required(VERSION 3.2) -elseif(USE_CUDA) - cmake_minimum_required(VERSION 3.16) -else() - cmake_minimum_required(VERSION 3.0) -endif() +cmake_minimum_required(VERSION 3.18) project(lightgbm LANGUAGES C CXX) +if(BUILD_CPP_TEST) + set(CMAKE_CXX_STANDARD 14) +else() + set(CMAKE_CXX_STANDARD 11) +endif() +set(CMAKE_CXX_STANDARD_REQUIRED ON) + list(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake/modules") #-- Sanitizer @@ -78,7 +75,6 @@ elseif(CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang") if(CMAKE_CXX_COMPILER_VERSION VERSION_LESS "8.1.0") message(FATAL_ERROR "Insufficient AppleClang version") endif() - cmake_minimum_required(VERSION 3.16) elseif(MSVC) if(MSVC_VERSION LESS 1900) message( @@ -86,7 +82,6 @@ elseif(MSVC) "The compiler ${CMAKE_CXX_COMPILER} doesn't support required C++11 features. Please use a newer MSVC." ) endif() - cmake_minimum_required(VERSION 3.8) endif() if(USE_SWIG) @@ -203,20 +198,26 @@ if(__INTEGRATE_OPENCL) endif() if(USE_CUDA) - find_package(CUDA 10.0 REQUIRED) + find_package(CUDA 11.0 REQUIRED) include_directories(${CUDA_INCLUDE_DIRS}) set(CMAKE_CUDA_FLAGS "${CMAKE_CUDA_FLAGS} -Xcompiler=${OpenMP_CXX_FLAGS} -Xcompiler=-fPIC -Xcompiler=-Wall") - set(CUDA_ARCHS "6.0" "6.1" "6.2" "7.0") - if(CUDA_VERSION VERSION_GREATER_EQUAL "10.0") - list(APPEND CUDA_ARCHS "7.5") - endif() + # reference for mapping of CUDA toolkit component versions to supported architectures ("compute capabilities"): + # https://en.wikipedia.org/wiki/CUDA#GPUs_supported + set(CUDA_ARCHS "6.0" "6.1" "6.2" "7.0" "7.5") if(CUDA_VERSION VERSION_GREATER_EQUAL "11.0") list(APPEND CUDA_ARCHS "8.0") endif() if(CUDA_VERSION VERSION_GREATER_EQUAL "11.1") list(APPEND CUDA_ARCHS "8.6") endif() + if(CUDA_VERSION VERSION_GREATER_EQUAL "11.5") + list(APPEND CUDA_ARCHS "8.7") + endif() + if(CUDA_VERSION VERSION_GREATER_EQUAL "11.8") + list(APPEND CUDA_ARCHS "8.9") + list(APPEND CUDA_ARCHS "9.0") + endif() list(POP_BACK CUDA_ARCHS CUDA_LAST_SUPPORTED_ARCH) list(APPEND CUDA_ARCHS "${CUDA_LAST_SUPPORTED_ARCH}+PTX") cuda_select_nvcc_arch_flags(CUDA_ARCH_FLAGS ${CUDA_ARCHS}) @@ -324,7 +325,7 @@ endif() if(UNIX OR MINGW OR CYGWIN) set( CMAKE_CXX_FLAGS - "${CMAKE_CXX_FLAGS} -std=c++11 -pthread -Wextra -Wall -Wno-ignored-attributes -Wno-unknown-pragmas -Wno-return-type" + "${CMAKE_CXX_FLAGS} -pthread -Wextra -Wall -Wno-ignored-attributes -Wno-unknown-pragmas -Wno-return-type" ) if(MINGW) # ignore this warning: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=95353 @@ -378,6 +379,13 @@ if(MSVC) CMAKE_CXX_FLAGS_RELWITHDEBINFO ) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /W4 /MP") + if(__BUILD_FOR_R) + # MSVC does not like this commit: + # https://github.com/wch/r-source/commit/fb52ac1a610571fcb8ac92d886b9fefcffaa7d48 + # + # and raises "error C3646: 'private_data_c': unknown override specifier" + add_definitions(-DR_LEGACY_RCOMPLEX) + endif() if(USE_DEBUG) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /Od") else() @@ -420,7 +428,11 @@ file( src/objective/*.cpp src/network/*.cpp src/treelearner/*.cpp -if(USE_CUDA) + src/utils/*.cpp +) +file( + GLOB + LGBM_CUDA_SOURCES src/treelearner/*.cu src/boosting/cuda/*.cpp src/boosting/cuda/*.cu @@ -434,9 +446,12 @@ if(USE_CUDA) src/io/cuda/*.cpp src/cuda/*.cpp src/cuda/*.cu -endif() ) +if(USE_CUDA) + list(APPEND SOURCES ${LGBM_CUDA_SOURCES}) +endif() + add_library(lightgbm_objs OBJECT ${SOURCES}) if(BUILD_CLI) @@ -501,7 +516,18 @@ if(USE_SWIG) TARGET _lightgbm_swig POST_BUILD COMMAND "${Java_JAVAC_EXECUTABLE}" -d . java/*.java - COMMAND cp "${PROJECT_SOURCE_DIR}/Release/*.dll" com/microsoft/ml/lightgbm/windows/x86_64 + COMMAND + "${CMAKE_COMMAND}" + -E + copy_if_different + "${PROJECT_SOURCE_DIR}/Release/lib_lightgbm.dll" + com/microsoft/ml/lightgbm/windows/x86_64 + COMMAND + "${CMAKE_COMMAND}" + -E + copy_if_different + "${PROJECT_SOURCE_DIR}/Release/lib_lightgbm_swig.dll" + com/microsoft/ml/lightgbm/windows/x86_64 COMMAND "${Java_JAR_EXECUTABLE}" -cf lightgbmlib.jar com ) endif() @@ -510,9 +536,16 @@ if(USE_SWIG) TARGET _lightgbm_swig POST_BUILD COMMAND "${Java_JAVAC_EXECUTABLE}" -d . java/*.java - COMMAND cp "${PROJECT_SOURCE_DIR}/*.dylib" com/microsoft/ml/lightgbm/osx/x86_64 COMMAND - cp + "${CMAKE_COMMAND}" + -E + copy_if_different + "${PROJECT_SOURCE_DIR}/lib_lightgbm.dylib" + com/microsoft/ml/lightgbm/osx/x86_64 + COMMAND + "${CMAKE_COMMAND}" + -E + copy_if_different "${PROJECT_SOURCE_DIR}/lib_lightgbm_swig.jnilib" com/microsoft/ml/lightgbm/osx/x86_64/lib_lightgbm_swig.dylib COMMAND "${Java_JAR_EXECUTABLE}" -cf lightgbmlib.jar com @@ -522,7 +555,12 @@ if(USE_SWIG) TARGET _lightgbm_swig POST_BUILD COMMAND "${Java_JAVAC_EXECUTABLE}" -d . java/*.java - COMMAND cp "${PROJECT_SOURCE_DIR}/*.so" com/microsoft/ml/lightgbm/linux/x86_64 + COMMAND + "${CMAKE_COMMAND}" + -E + copy_if_different + "${PROJECT_SOURCE_DIR}/lib_lightgbm.so" + com/microsoft/ml/lightgbm/linux/x86_64 COMMAND "${Java_JAR_EXECUTABLE}" -cf lightgbmlib.jar com ) endif() @@ -617,7 +655,7 @@ if(BUILD_CPP_TEST) FetchContent_Declare( googletest GIT_REPOSITORY https://github.com/google/googletest.git - GIT_TAG release-1.11.0 + GIT_TAG v1.14.0 ) FetchContent_MakeAvailable(googletest) add_library(GTest::GTest ALIAS gtest) @@ -628,6 +666,7 @@ if(BUILD_CPP_TEST) file(GLOB CPP_TEST_SOURCES tests/cpp_tests/*.cpp) if(MSVC) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /permissive-") set( CompilerFlags CMAKE_CXX_FLAGS diff --git a/R-package/DESCRIPTION b/R-package/DESCRIPTION index 1193c0d463b9..6fbeeee859be 100755 --- a/R-package/DESCRIPTION +++ b/R-package/DESCRIPTION @@ -4,10 +4,10 @@ Title: Light Gradient Boosting Machine Version: ~~VERSION~~ Date: ~~DATE~~ Authors@R: c( - person("Yu", "Shi", email = "yushi2@microsoft.com", role = c("aut", "cre")), + person("Yu", "Shi", email = "yushi2@microsoft.com", role = c("aut")), person("Guolin", "Ke", email = "guolin.ke@outlook.com", role = c("aut")), person("Damien", "Soukhavong", email = "damien.soukhavong@skema.edu", role = c("aut")), - person("James", "Lamb", email="jaylamb20@gmail.com", role = c("aut")), + person("James", "Lamb", email="jaylamb20@gmail.com", role = c("aut", "cre")), person("Qi", "Meng", role = c("aut")), person("Thomas", "Finley", role = c("aut")), person("Taifeng", "Wang", role = c("aut")), @@ -46,9 +46,9 @@ Biarch: true VignetteBuilder: knitr Suggests: knitr, + markdown, processx, RhpcBLASctl, - rmarkdown, testthat Depends: R (>= 3.5) diff --git a/R-package/NAMESPACE b/R-package/NAMESPACE index e07af84d8824..718f0e55a0d7 100644 --- a/R-package/NAMESPACE +++ b/R-package/NAMESPACE @@ -9,6 +9,7 @@ S3method(print,lgb.Booster) S3method(set_field,lgb.Dataset) S3method(slice,lgb.Dataset) S3method(summary,lgb.Booster) +export(getLGBMthreads) export(get_field) export(lgb.Dataset) export(lgb.Dataset.construct) @@ -33,8 +34,7 @@ export(lgb.restore_handle) export(lgb.save) export(lgb.train) export(lightgbm) -export(readRDS.lgb.Booster) -export(saveRDS.lgb.Booster) +export(setLGBMthreads) export(set_field) export(slice) import(methods) diff --git a/R-package/R/callback.R b/R-package/R/callback.R index e428dfb79eea..c436409ddafb 100644 --- a/R-package/R/callback.R +++ b/R-package/R/callback.R @@ -90,7 +90,7 @@ cb_print_evaluation <- function(period) { # Check if message is existing if (nchar(msg) > 0L) { - print(.merge_eval_string(env = env)) + cat(.merge_eval_string(env = env), "\n") } } @@ -208,9 +208,9 @@ cb_early_stop <- function(stopping_rounds, first_metric_only, verbose) { msg <- paste0( "Will train until there is no improvement in " , stopping_rounds - , " rounds." + , " rounds.\n" ) - print(msg) + cat(msg) } # Internally treat everything as a maximization task @@ -284,7 +284,7 @@ cb_early_stop <- function(stopping_rounds, first_metric_only, verbose) { } if (isTRUE(verbose)) { - print(paste0("Early stopping, best iteration is: ", best_msg[[i]])) + cat(paste0("Early stopping, best iteration is: ", best_msg[[i]], "\n")) } # Store best iteration and stop @@ -302,7 +302,7 @@ cb_early_stop <- function(stopping_rounds, first_metric_only, verbose) { } if (isTRUE(verbose)) { - print(paste0("Did not meet early stopping, best iteration is: ", best_msg[[i]])) + cat(paste0("Did not meet early stopping, best iteration is: ", best_msg[[i]], "\n")) } # Store best iteration and stop @@ -323,17 +323,17 @@ cb_early_stop <- function(stopping_rounds, first_metric_only, verbose) { } # Extract callback names from the list of callbacks -callback.names <- function(cb_list) { +.callback_names <- function(cb_list) { return(unlist(lapply(cb_list, attr, "name"))) } -add.cb <- function(cb_list, cb) { +.add_cb <- function(cb_list, cb) { # Combine two elements cb_list <- c(cb_list, cb) # Set names of elements - names(cb_list) <- callback.names(cb_list = cb_list) + names(cb_list) <- .callback_names(cb_list = cb_list) if ("cb_early_stop" %in% names(cb_list)) { @@ -349,7 +349,7 @@ add.cb <- function(cb_list, cb) { } -categorize.callbacks <- function(cb_list) { +.categorize_callbacks <- function(cb_list) { # Check for pre-iteration or post-iteration return( diff --git a/R-package/R/lgb.Booster.R b/R-package/R/lgb.Booster.R index 755b171724f9..4437c6fa552e 100644 --- a/R-package/R/lgb.Booster.R +++ b/R-package/R/lgb.Booster.R @@ -31,12 +31,12 @@ Booster <- R6::R6Class( if (!is.null(train_set)) { - if (!lgb.is.Dataset(train_set)) { + if (!.is_Dataset(train_set)) { stop("lgb.Booster: Can only use lgb.Dataset as training data") } train_set_handle <- train_set$.__enclos_env__$private$get_handle() params <- utils::modifyList(params, train_set$get_params()) - params_str <- lgb.params2str(params = params) + params_str <- .params2str(params = params) # Store booster handle handle <- .Call( LGBM_BoosterCreate_R @@ -130,7 +130,7 @@ Booster <- R6::R6Class( # Add validation data add_valid = function(data, name) { - if (!lgb.is.Dataset(data)) { + if (!.is_Dataset(data)) { stop("lgb.Booster.add_valid: Can only use lgb.Dataset as validation data") } @@ -167,7 +167,7 @@ Booster <- R6::R6Class( params <- utils::modifyList(self$params, params) } - params_str <- lgb.params2str(params = params) + params_str <- .params2str(params = params) self$restore_handle() @@ -193,7 +193,7 @@ Booster <- R6::R6Class( if (!is.null(train_set)) { - if (!lgb.is.Dataset(train_set)) { + if (!.is_Dataset(train_set)) { stop("lgb.Booster.update: Only can use lgb.Dataset as training data") } @@ -340,7 +340,7 @@ Booster <- R6::R6Class( # Evaluate data on metrics eval = function(data, name, feval = NULL) { - if (!lgb.is.Dataset(data)) { + if (!.is_Dataset(data)) { stop("lgb.Booster.eval: Can only use lgb.Dataset to eval") } @@ -508,17 +508,17 @@ Booster <- R6::R6Class( # NOTE: doing this here instead of in Predictor$predict() to keep # Predictor$predict() as fast as possible if (length(params) > 0L) { - params <- lgb.check.wrapper_param( + params <- .check_wrapper_param( main_param_name = "predict_raw_score" , params = params , alternative_kwarg_value = rawscore ) - params <- lgb.check.wrapper_param( + params <- .check_wrapper_param( main_param_name = "predict_leaf_index" , params = params , alternative_kwarg_value = predleaf ) - params <- lgb.check.wrapper_param( + params <- .check_wrapper_param( main_param_name = "predict_contrib" , params = params , alternative_kwarg_value = predcontrib @@ -586,7 +586,7 @@ Booster <- R6::R6Class( , predcontrib , start_iteration , num_iteration - , lgb.params2str(params = params) + , .params2str(params = params) ) private$fast_predict_config <- list( @@ -622,7 +622,7 @@ Booster <- R6::R6Class( }, check_null_handle = function() { - return(lgb.is.null.handle(private$handle)) + return(.is_null_handle(private$handle)) }, restore_handle = function() { @@ -917,6 +917,8 @@ NULL #' the factor levels not being present in the output. #' @examples #' \donttest{ +#' \dontshow{setLGBMthreads(2L)} +#' \dontshow{data.table::setDTthreads(1L)} #' data(agaricus.train, package = "lightgbm") #' train <- agaricus.train #' dtrain <- lgb.Dataset(train$data, label = train$label) @@ -959,7 +961,7 @@ predict.lgb.Booster <- function(object, params = list(), ...) { - if (!lgb.is.Booster(x = object)) { + if (!.is_Booster(x = object)) { stop("predict.lgb.Booster: object should be an ", sQuote("lgb.Booster")) } @@ -1082,6 +1084,8 @@ predict.lgb.Booster <- function(object, #' \link{predict.lgb.Booster}. #' @examples #' \donttest{ +#' \dontshow{setLGBMthreads(2L)} +#' \dontshow{data.table::setDTthreads(1L)} #' library(lightgbm) #' data(mtcars) #' X <- as.matrix(mtcars[, -1L]) @@ -1114,7 +1118,7 @@ lgb.configure_fast_predict <- function(model, num_iteration = NULL, type = "response", params = list()) { - if (!lgb.is.Booster(x = model)) { + if (!.is_Booster(x = model)) { stop("lgb.configure_fast_predict: model should be an ", sQuote("lgb.Booster")) } if (type == "class") { @@ -1160,7 +1164,7 @@ lgb.configure_fast_predict <- function(model, print.lgb.Booster <- function(x, ...) { # nolint start handle <- x$.__enclos_env__$private$handle - handle_is_null <- lgb.is.null.handle(handle) + handle_is_null <- .is_null_handle(handle) if (!handle_is_null) { ntrees <- x$current_iter() @@ -1224,6 +1228,8 @@ summary.lgb.Booster <- function(object, ...) { #' #' @examples #' \donttest{ +#' \dontshow{setLGBMthreads(2L)} +#' \dontshow{data.table::setDTthreads(1L)} #' data(agaricus.train, package = "lightgbm") #' train <- agaricus.train #' dtrain <- lgb.Dataset(train$data, label = train$label) @@ -1289,6 +1295,8 @@ lgb.load <- function(filename = NULL, model_str = NULL) { #' #' @examples #' \donttest{ +#' \dontshow{setLGBMthreads(2L)} +#' \dontshow{data.table::setDTthreads(1L)} #' library(lightgbm) #' data(agaricus.train, package = "lightgbm") #' train <- agaricus.train @@ -1316,7 +1324,7 @@ lgb.load <- function(filename = NULL, model_str = NULL) { #' @export lgb.save <- function(booster, filename, num_iteration = NULL) { - if (!lgb.is.Booster(x = booster)) { + if (!.is_Booster(x = booster)) { stop("lgb.save: booster should be an ", sQuote("lgb.Booster")) } @@ -1346,6 +1354,8 @@ lgb.save <- function(booster, filename, num_iteration = NULL) { #' @examples #' \donttest{ #' library(lightgbm) +#' \dontshow{setLGBMthreads(2L)} +#' \dontshow{data.table::setDTthreads(1L)} #' data(agaricus.train, package = "lightgbm") #' train <- agaricus.train #' dtrain <- lgb.Dataset(train$data, label = train$label) @@ -1372,7 +1382,7 @@ lgb.save <- function(booster, filename, num_iteration = NULL) { #' @export lgb.dump <- function(booster, num_iteration = NULL) { - if (!lgb.is.Booster(x = booster)) { + if (!.is_Booster(x = booster)) { stop("lgb.dump: booster should be an ", sQuote("lgb.Booster")) } @@ -1396,6 +1406,8 @@ lgb.dump <- function(booster, num_iteration = NULL) { #' #' @examples #' \donttest{ +#' \dontshow{setLGBMthreads(2L)} +#' \dontshow{data.table::setDTthreads(1L)} #' # train a regression model #' data(agaricus.train, package = "lightgbm") #' train <- agaricus.train @@ -1430,7 +1442,7 @@ lgb.dump <- function(booster, num_iteration = NULL) { #' @export lgb.get.eval.result <- function(booster, data_name, eval_name, iters = NULL, is_err = FALSE) { - if (!lgb.is.Booster(x = booster)) { + if (!.is_Booster(x = booster)) { stop("lgb.get.eval.result: Can only use ", sQuote("lgb.Booster"), " to get eval result") } @@ -1462,7 +1474,6 @@ lgb.get.eval.result <- function(booster, data_name, eval_name, iters = NULL, is_ , toString(eval_names) , "]" )) - stop("lgb.get.eval.result: wrong eval name") } result <- booster$record_evals[[data_name]][[eval_name]][[.EVAL_KEY()]] diff --git a/R-package/R/lgb.DataProcessor.R b/R-package/R/lgb.DataProcessor.R index fc7061945b62..c35ce4f93bd3 100644 --- a/R-package/R/lgb.DataProcessor.R +++ b/R-package/R/lgb.DataProcessor.R @@ -39,7 +39,7 @@ DataProcessor <- R6::R6Class( ) } data_num_class <- length(self$factor_levels) - params <- lgb.check.wrapper_param( + params <- .check_wrapper_param( main_param_name = "num_class" , params = params , alternative_kwarg_value = data_num_class diff --git a/R-package/R/lgb.Dataset.R b/R-package/R/lgb.Dataset.R index e2892ea4bae0..ff9b0b4fa38a 100644 --- a/R-package/R/lgb.Dataset.R +++ b/R-package/R/lgb.Dataset.R @@ -55,10 +55,10 @@ Dataset <- R6::R6Class( init_score = NULL) { # validate inputs early to avoid unnecessary computation - if (!(is.null(reference) || lgb.is.Dataset(reference))) { + if (!(is.null(reference) || .is_Dataset(reference))) { stop("lgb.Dataset: If provided, reference must be a ", sQuote("lgb.Dataset")) } - if (!(is.null(predictor) || lgb.is.Predictor(predictor))) { + if (!(is.null(predictor) || .is_Predictor(predictor))) { stop("lgb.Dataset: If provided, predictor must be a ", sQuote("lgb.Predictor")) } @@ -135,7 +135,7 @@ Dataset <- R6::R6Class( construct = function() { # Check for handle null - if (!lgb.is.null.handle(x = private$handle)) { + if (!.is_null_handle(x = private$handle)) { return(invisible(self)) } @@ -191,7 +191,7 @@ Dataset <- R6::R6Class( } # Generate parameter str - params_str <- lgb.params2str(params = private$params) + params_str <- .params2str(params = private$params) # Get handle of reference dataset ref_handle <- NULL @@ -277,7 +277,7 @@ Dataset <- R6::R6Class( ) } - if (lgb.is.null.handle(x = handle)) { + if (.is_null_handle(x = handle)) { stop("lgb.Dataset.construct: cannot create Dataset handle") } # Setup class and private type @@ -345,7 +345,7 @@ Dataset <- R6::R6Class( dim = function() { # Check for handle - if (!lgb.is.null.handle(x = private$handle)) { + if (!.is_null_handle(x = private$handle)) { num_row <- 0L num_col <- 0L @@ -385,7 +385,7 @@ Dataset <- R6::R6Class( # Get number of bins for feature get_feature_num_bin = function(feature) { - if (lgb.is.null.handle(x = private$handle)) { + if (.is_null_handle(x = private$handle)) { stop("Cannot get number of bins in feature before constructing Dataset.") } if (is.character(feature)) { @@ -409,7 +409,7 @@ Dataset <- R6::R6Class( get_colnames = function() { # Check for handle - if (!lgb.is.null.handle(x = private$handle)) { + if (!.is_null_handle(x = private$handle)) { private$colnames <- .Call( LGBM_DatasetGetFeatureNames_R , private$handle @@ -449,7 +449,7 @@ Dataset <- R6::R6Class( # Write column names private$colnames <- colnames - if (!lgb.is.null.handle(x = private$handle)) { + if (!.is_null_handle(x = private$handle)) { # Merge names with tab separation merged_name <- paste0(as.list(private$colnames), collapse = "\t") @@ -478,7 +478,7 @@ Dataset <- R6::R6Class( # Check for info name and handle if (is.null(private$info[[field_name]])) { - if (lgb.is.null.handle(x = private$handle)) { + if (.is_null_handle(x = private$handle)) { stop("Cannot perform Dataset$get_field() before constructing Dataset.") } @@ -536,7 +536,7 @@ Dataset <- R6::R6Class( # Store information privately private$info[[field_name]] <- data - if (!lgb.is.null.handle(x = private$handle) && !is.null(data)) { + if (!.is_null_handle(x = private$handle) && !is.null(data)) { if (length(data) > 0L) { @@ -583,14 +583,14 @@ Dataset <- R6::R6Class( return(invisible(self)) } new_params <- utils::modifyList(private$params, params) - if (lgb.is.null.handle(x = private$handle)) { + if (.is_null_handle(x = private$handle)) { private$params <- new_params } else { tryCatch({ .Call( LGBM_DatasetUpdateParamChecking_R - , lgb.params2str(params = private$params) - , lgb.params2str(params = new_params) + , .params2str(params = private$params) + , .params2str(params = new_params) ) private$params <- new_params }, error = function(e) { @@ -663,7 +663,7 @@ Dataset <- R6::R6Class( please set ", sQuote("free_raw_data = FALSE"), " when you construct lgb.Dataset") } - if (!lgb.is.Dataset(reference)) { + if (!.is_Dataset(reference)) { stop("set_reference: Can only use lgb.Dataset as a reference") } @@ -711,7 +711,7 @@ Dataset <- R6::R6Class( get_handle = function() { # Get handle and construct if needed - if (lgb.is.null.handle(x = private$handle)) { + if (.is_null_handle(x = private$handle)) { self$construct() } return(private$handle) @@ -734,7 +734,7 @@ Dataset <- R6::R6Class( if (!is.null(predictor)) { # Predictor is unknown - if (!lgb.is.Predictor(predictor)) { + if (!.is_Predictor(predictor)) { stop("set_predictor: Can only use lgb.Predictor as predictor") } @@ -780,6 +780,8 @@ Dataset <- R6::R6Class( #' #' @examples #' \donttest{ +#' \dontshow{setLGBMthreads(2L)} +#' \dontshow{data.table::setDTthreads(1L)} #' data(agaricus.train, package = "lightgbm") #' train <- agaricus.train #' dtrain <- lgb.Dataset(train$data, label = train$label) @@ -837,6 +839,8 @@ lgb.Dataset <- function(data, #' #' @examples #' \donttest{ +#' \dontshow{setLGBMthreads(2L)} +#' \dontshow{data.table::setDTthreads(1L)} #' data(agaricus.train, package = "lightgbm") #' train <- agaricus.train #' dtrain <- lgb.Dataset(train$data, label = train$label) @@ -888,7 +892,7 @@ lgb.Dataset.create.valid <- function(dataset, init_score = NULL, params = list()) { - if (!lgb.is.Dataset(x = dataset)) { + if (!.is_Dataset(x = dataset)) { stop("lgb.Dataset.create.valid: input data should be an lgb.Dataset object") } @@ -913,6 +917,8 @@ lgb.Dataset.create.valid <- function(dataset, #' #' @examples #' \donttest{ +#' \dontshow{setLGBMthreads(2L)} +#' \dontshow{data.table::setDTthreads(1L)} #' data(agaricus.train, package = "lightgbm") #' train <- agaricus.train #' dtrain <- lgb.Dataset(train$data, label = train$label) @@ -922,7 +928,7 @@ lgb.Dataset.create.valid <- function(dataset, #' @export lgb.Dataset.construct <- function(dataset) { - if (!lgb.is.Dataset(x = dataset)) { + if (!.is_Dataset(x = dataset)) { stop("lgb.Dataset.construct: input data should be an lgb.Dataset object") } @@ -942,6 +948,8 @@ lgb.Dataset.construct <- function(dataset) { #' #' @examples #' \donttest{ +#' \dontshow{setLGBMthreads(2L)} +#' \dontshow{data.table::setDTthreads(1L)} #' data(agaricus.train, package = "lightgbm") #' train <- agaricus.train #' dtrain <- lgb.Dataset(train$data, label = train$label) @@ -954,7 +962,7 @@ lgb.Dataset.construct <- function(dataset) { #' @export dim.lgb.Dataset <- function(x) { - if (!lgb.is.Dataset(x = x)) { + if (!.is_Dataset(x = x)) { stop("dim.lgb.Dataset: input data should be an lgb.Dataset object") } @@ -975,6 +983,8 @@ dim.lgb.Dataset <- function(x) { #' #' @examples #' \donttest{ +#' \dontshow{setLGBMthreads(2L)} +#' \dontshow{data.table::setDTthreads(1L)} #' data(agaricus.train, package = "lightgbm") #' train <- agaricus.train #' dtrain <- lgb.Dataset(train$data, label = train$label) @@ -989,7 +999,7 @@ dim.lgb.Dataset <- function(x) { #' @export dimnames.lgb.Dataset <- function(x) { - if (!lgb.is.Dataset(x = x)) { + if (!.is_Dataset(x = x)) { stop("dimnames.lgb.Dataset: input data should be an lgb.Dataset object") } @@ -1045,6 +1055,8 @@ dimnames.lgb.Dataset <- function(x) { #' #' @examples #' \donttest{ +#' \dontshow{setLGBMthreads(2L)} +#' \dontshow{data.table::setDTthreads(1L)} #' data(agaricus.train, package = "lightgbm") #' train <- agaricus.train #' dtrain <- lgb.Dataset(train$data, label = train$label) @@ -1062,7 +1074,7 @@ slice <- function(dataset, idxset) { #' @export slice.lgb.Dataset <- function(dataset, idxset) { - if (!lgb.is.Dataset(x = dataset)) { + if (!.is_Dataset(x = dataset)) { stop("slice.lgb.Dataset: input dataset should be an lgb.Dataset object") } @@ -1089,6 +1101,8 @@ slice.lgb.Dataset <- function(dataset, idxset) { #' #' @examples #' \donttest{ +#' \dontshow{setLGBMthreads(2L)} +#' \dontshow{data.table::setDTthreads(1L)} #' data(agaricus.train, package = "lightgbm") #' train <- agaricus.train #' dtrain <- lgb.Dataset(train$data, label = train$label) @@ -1110,7 +1124,7 @@ get_field <- function(dataset, field_name) { get_field.lgb.Dataset <- function(dataset, field_name) { # Check if dataset is not a dataset - if (!lgb.is.Dataset(x = dataset)) { + if (!.is_Dataset(x = dataset)) { stop("get_field.lgb.Dataset(): input dataset should be an lgb.Dataset object") } @@ -1138,6 +1152,8 @@ get_field.lgb.Dataset <- function(dataset, field_name) { #' #' @examples #' \donttest{ +#' \dontshow{setLGBMthreads(2L)} +#' \dontshow{data.table::setDTthreads(1L)} #' data(agaricus.train, package = "lightgbm") #' train <- agaricus.train #' dtrain <- lgb.Dataset(train$data, label = train$label) @@ -1158,7 +1174,7 @@ set_field <- function(dataset, field_name, data) { #' @export set_field.lgb.Dataset <- function(dataset, field_name, data) { - if (!lgb.is.Dataset(x = dataset)) { + if (!.is_Dataset(x = dataset)) { stop("set_field.lgb.Dataset: input dataset should be an lgb.Dataset object") } @@ -1177,6 +1193,8 @@ set_field.lgb.Dataset <- function(dataset, field_name, data) { #' #' @examples #' \donttest{ +#' \dontshow{setLGBMthreads(2L)} +#' \dontshow{data.table::setDTthreads(1L)} #' data(agaricus.train, package = "lightgbm") #' train <- agaricus.train #' dtrain <- lgb.Dataset(train$data, label = train$label) @@ -1189,7 +1207,7 @@ set_field.lgb.Dataset <- function(dataset, field_name, data) { #' @export lgb.Dataset.set.categorical <- function(dataset, categorical_feature) { - if (!lgb.is.Dataset(x = dataset)) { + if (!.is_Dataset(x = dataset)) { stop("lgb.Dataset.set.categorical: input dataset should be an lgb.Dataset object") } @@ -1207,6 +1225,8 @@ lgb.Dataset.set.categorical <- function(dataset, categorical_feature) { #' #' @examples #' \donttest{ +#' \dontshow{setLGBMthreads(2L)} +#' \dontshow{data.table::setDTthreads(1L)} #' # create training Dataset #' data(agaricus.train, package ="lightgbm") #' train <- agaricus.train @@ -1222,7 +1242,7 @@ lgb.Dataset.set.categorical <- function(dataset, categorical_feature) { #' @export lgb.Dataset.set.reference <- function(dataset, reference) { - if (!lgb.is.Dataset(x = dataset)) { + if (!.is_Dataset(x = dataset)) { stop("lgb.Dataset.set.reference: input dataset should be an lgb.Dataset object") } @@ -1240,6 +1260,8 @@ lgb.Dataset.set.reference <- function(dataset, reference) { #' #' @examples #' \donttest{ +#' \dontshow{setLGBMthreads(2L)} +#' \dontshow{data.table::setDTthreads(1L)} #' data(agaricus.train, package = "lightgbm") #' train <- agaricus.train #' dtrain <- lgb.Dataset(train$data, label = train$label) @@ -1248,7 +1270,7 @@ lgb.Dataset.set.reference <- function(dataset, reference) { #' @export lgb.Dataset.save <- function(dataset, fname) { - if (!lgb.is.Dataset(x = dataset)) { + if (!.is_Dataset(x = dataset)) { stop("lgb.Dataset.save: input dataset should be an lgb.Dataset object") } diff --git a/R-package/R/lgb.Predictor.R b/R-package/R/lgb.Predictor.R index 0b7b39e2d8c2..3a411efd75ba 100644 --- a/R-package/R/lgb.Predictor.R +++ b/R-package/R/lgb.Predictor.R @@ -28,7 +28,7 @@ Predictor <- R6::R6Class( # Initialize will create a starter model initialize = function(modelfile, params = list(), fast_predict_config = list()) { - private$params <- lgb.params2str(params = params) + private$params <- .params2str(params = params) handle <- NULL if (is.character(modelfile)) { @@ -46,7 +46,7 @@ Predictor <- R6::R6Class( handle <- modelfile private$need_free_handle <- FALSE - } else if (lgb.is.Booster(modelfile)) { + } else if (.is_Booster(modelfile)) { handle <- modelfile$get_handle() private$need_free_handle <- FALSE @@ -512,7 +512,7 @@ Predictor <- R6::R6Class( return(FALSE) } - if (lgb.is.null.handle(private$fast_predict_config$handle)) { + if (.is_null_handle(private$fast_predict_config$handle)) { warning(paste0("Model had fast CSR predict configuration, but it is inactive." , " Try re-generating it through 'lgb.configure_fast_predict'.")) return(FALSE) @@ -527,8 +527,8 @@ Predictor <- R6::R6Class( private$fast_predict_config$rawscore == rawscore && private$fast_predict_config$predleaf == predleaf && private$fast_predict_config$predcontrib == predcontrib && - lgb.equal.or.both.null(private$fast_predict_config$start_iteration, start_iteration) && - lgb.equal.or.both.null(private$fast_predict_config$num_iteration, num_iteration) + .equal_or_both_null(private$fast_predict_config$start_iteration, start_iteration) && + .equal_or_both_null(private$fast_predict_config$num_iteration, num_iteration) ) } ) diff --git a/R-package/R/lgb.cv.R b/R-package/R/lgb.cv.R index f81026fe673f..0545fbf71899 100644 --- a/R-package/R/lgb.cv.R +++ b/R-package/R/lgb.cv.R @@ -51,6 +51,8 @@ CVBooster <- R6::R6Class( #' #' @examples #' \donttest{ +#' \dontshow{setLGBMthreads(2L)} +#' \dontshow{data.table::setDTthreads(1L)} #' data(agaricus.train, package = "lightgbm") #' train <- agaricus.train #' dtrain <- lgb.Dataset(train$data, label = train$label) @@ -99,7 +101,7 @@ lgb.cv <- function(params = list() } # If 'data' is not an lgb.Dataset, try to construct one using 'label' - if (!lgb.is.Dataset(x = data)) { + if (!.is_Dataset(x = data)) { if (is.null(label)) { stop("'label' must be provided for lgb.cv if 'data' is not an 'lgb.Dataset'") } @@ -110,27 +112,27 @@ lgb.cv <- function(params = list() # in `params`. # this ensures that the model stored with Booster$save() correctly represents # what was passed in - params <- lgb.check.wrapper_param( + params <- .check_wrapper_param( main_param_name = "verbosity" , params = params , alternative_kwarg_value = verbose ) - params <- lgb.check.wrapper_param( + params <- .check_wrapper_param( main_param_name = "num_iterations" , params = params , alternative_kwarg_value = nrounds ) - params <- lgb.check.wrapper_param( + params <- .check_wrapper_param( main_param_name = "metric" , params = params , alternative_kwarg_value = NULL ) - params <- lgb.check.wrapper_param( + params <- .check_wrapper_param( main_param_name = "objective" , params = params , alternative_kwarg_value = obj ) - params <- lgb.check.wrapper_param( + params <- .check_wrapper_param( main_param_name = "early_stopping_round" , params = params , alternative_kwarg_value = early_stopping_rounds @@ -148,7 +150,7 @@ lgb.cv <- function(params = list() # (for backwards compatibility). If it is a list of functions, store # all of them. This makes it possible to pass any mix of strings like "auc" # and custom functions to eval - params <- lgb.check.eval(params = params, eval = eval) + params <- .check_eval(params = params, eval = eval) eval_functions <- list(NULL) if (is.function(eval)) { eval_functions <- list(eval) @@ -166,7 +168,7 @@ lgb.cv <- function(params = list() # Check for boosting from a trained model if (is.character(init_model)) { predictor <- Predictor$new(modelfile = init_model) - } else if (lgb.is.Booster(x = init_model)) { + } else if (.is_Booster(x = init_model)) { predictor <- init_model$to_predictor() } @@ -193,7 +195,7 @@ lgb.cv <- function(params = list() } else if (!is.null(data$get_colnames())) { cnames <- data$get_colnames() } - params[["interaction_constraints"]] <- lgb.check_interaction_constraints( + params[["interaction_constraints"]] <- .check_interaction_constraints( interaction_constraints = interaction_constraints , column_names = cnames ) @@ -232,7 +234,7 @@ lgb.cv <- function(params = list() } # Create folds - folds <- generate.cv.folds( + folds <- .generate_cv_folds( nfold = nfold , nrows = nrow(data) , stratified = stratified @@ -245,12 +247,12 @@ lgb.cv <- function(params = list() # Add printing log callback if (params[["verbosity"]] > 0L && eval_freq > 0L) { - callbacks <- add.cb(cb_list = callbacks, cb = cb_print_evaluation(period = eval_freq)) + callbacks <- .add_cb(cb_list = callbacks, cb = cb_print_evaluation(period = eval_freq)) } # Add evaluation log callback if (record) { - callbacks <- add.cb(cb_list = callbacks, cb = cb_record_evaluation()) + callbacks <- .add_cb(cb_list = callbacks, cb = cb_record_evaluation()) } # Did user pass parameters that indicate they want to use early stopping? @@ -282,7 +284,7 @@ lgb.cv <- function(params = list() # If user supplied early_stopping_rounds, add the early stopping callback if (using_early_stopping) { - callbacks <- add.cb( + callbacks <- .add_cb( cb_list = callbacks , cb = cb_early_stop( stopping_rounds = early_stopping_rounds @@ -292,7 +294,7 @@ lgb.cv <- function(params = list() ) } - cb <- categorize.callbacks(cb_list = callbacks) + cb <- .categorize_callbacks(cb_list = callbacks) # Construct booster for each fold. The data.table() code below is used to # guarantee that indices are sorted while keeping init_score and weight together @@ -387,7 +389,7 @@ lgb.cv <- function(params = list() }) # Prepare collection of evaluation results - merged_msg <- lgb.merge.cv.result( + merged_msg <- .merge_cv_result( msg = msg , showsd = showsd ) @@ -463,7 +465,7 @@ lgb.cv <- function(params = list() } # Generates random (stratified if needed) CV folds -generate.cv.folds <- function(nfold, nrows, stratified, label, group, params) { +.generate_cv_folds <- function(nfold, nrows, stratified, label, group, params) { # Check for group existence if (is.null(group)) { @@ -476,7 +478,7 @@ generate.cv.folds <- function(nfold, nrows, stratified, label, group, params) { y <- label[rnd_idx] y <- as.factor(y) - folds <- lgb.stratified.folds(y = y, k = nfold) + folds <- .stratified_folds(y = y, k = nfold) } else { @@ -528,7 +530,7 @@ generate.cv.folds <- function(nfold, nrows, stratified, label, group, params) { # It was borrowed from caret::createFolds and simplified # by always returning an unnamed list of fold indices. #' @importFrom stats quantile -lgb.stratified.folds <- function(y, k) { +.stratified_folds <- function(y, k) { # Group the numeric data based on their magnitudes # and sample within those groups. @@ -594,7 +596,7 @@ lgb.stratified.folds <- function(y, k) { return(out) } -lgb.merge.cv.result <- function(msg, showsd) { +.merge_cv_result <- function(msg, showsd) { if (length(msg) == 0L) { stop("lgb.cv: size of cv result error") diff --git a/R-package/R/lgb.drop_serialized.R b/R-package/R/lgb.drop_serialized.R index bcc2480e8ccc..e53f2cafac11 100644 --- a/R-package/R/lgb.drop_serialized.R +++ b/R-package/R/lgb.drop_serialized.R @@ -13,7 +13,7 @@ #' @seealso \link{lgb.restore_handle}, \link{lgb.make_serializable}. #' @export lgb.drop_serialized <- function(model) { - if (!lgb.is.Booster(x = model)) { + if (!.is_Booster(x = model)) { stop("lgb.drop_serialized: model should be an ", sQuote("lgb.Booster")) } model$drop_raw() diff --git a/R-package/R/lgb.importance.R b/R-package/R/lgb.importance.R index 5a58770553f9..7c76131f4f53 100644 --- a/R-package/R/lgb.importance.R +++ b/R-package/R/lgb.importance.R @@ -14,6 +14,8 @@ #' #' @examples #' \donttest{ +#' \dontshow{setLGBMthreads(2L)} +#' \dontshow{data.table::setDTthreads(1L)} #' data(agaricus.train, package = "lightgbm") #' train <- agaricus.train #' dtrain <- lgb.Dataset(train$data, label = train$label) @@ -39,7 +41,7 @@ #' @export lgb.importance <- function(model, percentage = TRUE) { - if (!lgb.is.Booster(x = model)) { + if (!.is_Booster(x = model)) { stop("'model' has to be an object of class lgb.Booster") } diff --git a/R-package/R/lgb.interprete.R b/R-package/R/lgb.interprete.R index 7de772664d8b..8f93d45429f1 100644 --- a/R-package/R/lgb.interprete.R +++ b/R-package/R/lgb.interprete.R @@ -17,6 +17,8 @@ #' #' @examples #' \donttest{ +#' \dontshow{setLGBMthreads(2L)} +#' \dontshow{data.table::setDTthreads(1L)} #' Logit <- function(x) log(x / (1.0 - x)) #' data(agaricus.train, package = "lightgbm") #' train <- agaricus.train @@ -86,7 +88,7 @@ lgb.interprete <- function(model, ) for (i in seq_along(idxset)) { - tree_interpretation_dt_list[[i]] <- single.row.interprete( + tree_interpretation_dt_list[[i]] <- .single_row_interprete( tree_dt = tree_dt , num_class = num_class , tree_index_mat = tree_index_mat_list[[i]] @@ -151,7 +153,7 @@ single.tree.interprete <- function(tree_dt, } #' @importFrom data.table := rbindlist setorder -multiple.tree.interprete <- function(tree_dt, +.multiple_tree_interprete <- function(tree_dt, tree_index, leaf_index) { @@ -186,7 +188,7 @@ multiple.tree.interprete <- function(tree_dt, } #' @importFrom data.table set setnames -single.row.interprete <- function(tree_dt, num_class, tree_index_mat, leaf_index_mat) { +.single_row_interprete <- function(tree_dt, num_class, tree_index_mat, leaf_index_mat) { # Prepare vector list tree_interpretation <- vector(mode = "list", length = num_class) @@ -194,7 +196,7 @@ single.row.interprete <- function(tree_dt, num_class, tree_index_mat, leaf_index # Loop throughout each class for (i in seq_len(num_class)) { - next_interp_dt <- multiple.tree.interprete( + next_interp_dt <- .multiple_tree_interprete( tree_dt = tree_dt , tree_index = tree_index_mat[, i] , leaf_index = leaf_index_mat[, i] diff --git a/R-package/R/lgb.make_serializable.R b/R-package/R/lgb.make_serializable.R index 58bdd194df4d..5a639aacb2b5 100644 --- a/R-package/R/lgb.make_serializable.R +++ b/R-package/R/lgb.make_serializable.R @@ -13,7 +13,7 @@ #' @seealso \link{lgb.restore_handle}, \link{lgb.drop_serialized}. #' @export lgb.make_serializable <- function(model) { - if (!lgb.is.Booster(x = model)) { + if (!.is_Booster(x = model)) { stop("lgb.make_serializable: model should be an ", sQuote("lgb.Booster")) } model$save_raw() diff --git a/R-package/R/lgb.model.dt.tree.R b/R-package/R/lgb.model.dt.tree.R index 8b0d8d81e2e8..bf4562e41018 100644 --- a/R-package/R/lgb.model.dt.tree.R +++ b/R-package/R/lgb.model.dt.tree.R @@ -29,6 +29,8 @@ #' #' @examples #' \donttest{ +#' \dontshow{setLGBMthreads(2L)} +#' \dontshow{data.table::setDTthreads(1L)} #' data(agaricus.train, package = "lightgbm") #' train <- agaricus.train #' dtrain <- lgb.Dataset(train$data, label = train$label) @@ -62,7 +64,10 @@ lgb.model.dt.tree <- function(model, num_iteration = NULL) { ) # Parse tree model - tree_list <- lapply(parsed_json_model$tree_info, single.tree.parse) + tree_list <- lapply( + X = parsed_json_model$tree_info + , FUN = .single_tree_parse + ) # Combine into single data.table tree_dt <- data.table::rbindlist(l = tree_list, use.names = TRUE) @@ -84,7 +89,7 @@ lgb.model.dt.tree <- function(model, num_iteration = NULL) { #' @importFrom data.table := data.table rbindlist -single.tree.parse <- function(lgb_tree) { +.single_tree_parse <- function(lgb_tree) { # Traverse tree function pre_order_traversal <- function(env = NULL, tree_node_leaf, current_depth = 0L, parent_index = NA_integer_) { diff --git a/R-package/R/lgb.plot.importance.R b/R-package/R/lgb.plot.importance.R index fc59ebd0efec..b8a90ca158ae 100644 --- a/R-package/R/lgb.plot.importance.R +++ b/R-package/R/lgb.plot.importance.R @@ -19,6 +19,8 @@ #' #' @examples #' \donttest{ +#' \dontshow{setLGBMthreads(2L)} +#' \dontshow{data.table::setDTthreads(1L)} #' data(agaricus.train, package = "lightgbm") #' train <- agaricus.train #' dtrain <- lgb.Dataset(train$data, label = train$label) diff --git a/R-package/R/lgb.plot.interpretation.R b/R-package/R/lgb.plot.interpretation.R index a88f14bf83f0..97650f30a7d3 100644 --- a/R-package/R/lgb.plot.interpretation.R +++ b/R-package/R/lgb.plot.interpretation.R @@ -16,6 +16,8 @@ #' #' @examples #' \donttest{ +#' \dontshow{setLGBMthreads(2L)} +#' \dontshow{data.table::setDTthreads(1L)} #' Logit <- function(x) { #' log(x / (1.0 - x)) #' } @@ -89,7 +91,7 @@ lgb.plot.interpretation <- function(tree_interpretation_dt, if (num_class == 1L) { # Only one class, plot straight away - multiple.tree.plot.interpretation( + .multiple_tree_plot_interpretation( tree_interpretation = tree_interpretation_dt , top_n = top_n , title = NULL @@ -118,7 +120,7 @@ lgb.plot.interpretation <- function(tree_interpretation_dt, , old = names(plot_dt) , new = c("Feature", "Contribution") ) - multiple.tree.plot.interpretation( + .multiple_tree_plot_interpretation( tree_interpretation = plot_dt , top_n = top_n , title = paste("Class", i - 1L) @@ -131,7 +133,7 @@ lgb.plot.interpretation <- function(tree_interpretation_dt, } #' @importFrom graphics barplot -multiple.tree.plot.interpretation <- function(tree_interpretation, +.multiple_tree_plot_interpretation <- function(tree_interpretation, top_n, title, cex) { diff --git a/R-package/R/lgb.restore_handle.R b/R-package/R/lgb.restore_handle.R index 4de93d46c96a..8a24cc628ca9 100644 --- a/R-package/R/lgb.restore_handle.R +++ b/R-package/R/lgb.restore_handle.R @@ -16,7 +16,10 @@ #' @return \code{lgb.Booster} (the same `model` object that was passed as input, invisibly). #' @seealso \link{lgb.make_serializable}, \link{lgb.drop_serialized}. #' @examples +#' \donttest{ #' library(lightgbm) +#' \dontshow{setLGBMthreads(2L)} +#' \dontshow{data.table::setDTthreads(1L)} #' data("agaricus.train") #' model <- lightgbm( #' agaricus.train$data @@ -33,9 +36,10 @@ #' model_new$check_null_handle() #' lgb.restore_handle(model_new) #' model_new$check_null_handle() +#' } #' @export lgb.restore_handle <- function(model) { - if (!lgb.is.Booster(x = model)) { + if (!.is_Booster(x = model)) { stop("lgb.restore_handle: model should be an ", sQuote("lgb.Booster")) } model$restore_handle() diff --git a/R-package/R/lgb.train.R b/R-package/R/lgb.train.R index 20916c9844b5..8a299fb6b8ac 100644 --- a/R-package/R/lgb.train.R +++ b/R-package/R/lgb.train.R @@ -19,6 +19,8 @@ #' #' @examples #' \donttest{ +#' \dontshow{setLGBMthreads(2L)} +#' \dontshow{data.table::setDTthreads(1L)} #' data(agaricus.train, package = "lightgbm") #' train <- agaricus.train #' dtrain <- lgb.Dataset(train$data, label = train$label) @@ -63,11 +65,11 @@ lgb.train <- function(params = list(), if (nrounds <= 0L) { stop("nrounds should be greater than zero") } - if (!lgb.is.Dataset(x = data)) { + if (!.is_Dataset(x = data)) { stop("lgb.train: data must be an lgb.Dataset instance") } if (length(valids) > 0L) { - if (!identical(class(valids), "list") || !all(vapply(valids, lgb.is.Dataset, logical(1L)))) { + if (!identical(class(valids), "list") || !all(vapply(valids, .is_Dataset, logical(1L)))) { stop("lgb.train: valids must be a list of lgb.Dataset elements") } evnames <- names(valids) @@ -80,27 +82,27 @@ lgb.train <- function(params = list(), # in `params`. # this ensures that the model stored with Booster$save() correctly represents # what was passed in - params <- lgb.check.wrapper_param( + params <- .check_wrapper_param( main_param_name = "verbosity" , params = params , alternative_kwarg_value = verbose ) - params <- lgb.check.wrapper_param( + params <- .check_wrapper_param( main_param_name = "num_iterations" , params = params , alternative_kwarg_value = nrounds ) - params <- lgb.check.wrapper_param( + params <- .check_wrapper_param( main_param_name = "metric" , params = params , alternative_kwarg_value = NULL ) - params <- lgb.check.wrapper_param( + params <- .check_wrapper_param( main_param_name = "objective" , params = params , alternative_kwarg_value = obj ) - params <- lgb.check.wrapper_param( + params <- .check_wrapper_param( main_param_name = "early_stopping_round" , params = params , alternative_kwarg_value = early_stopping_rounds @@ -118,7 +120,7 @@ lgb.train <- function(params = list(), # (for backwards compatibility). If it is a list of functions, store # all of them. This makes it possible to pass any mix of strings like "auc" # and custom functions to eval - params <- lgb.check.eval(params = params, eval = eval) + params <- .check_eval(params = params, eval = eval) eval_functions <- list(NULL) if (is.function(eval)) { eval_functions <- list(eval) @@ -136,7 +138,7 @@ lgb.train <- function(params = list(), # Check for boosting from a trained model if (is.character(init_model)) { predictor <- Predictor$new(modelfile = init_model) - } else if (lgb.is.Booster(x = init_model)) { + } else if (.is_Booster(x = init_model)) { predictor <- init_model$to_predictor() } @@ -166,7 +168,7 @@ lgb.train <- function(params = list(), } else if (!is.null(data$get_colnames())) { cnames <- data$get_colnames() } - params[["interaction_constraints"]] <- lgb.check_interaction_constraints( + params[["interaction_constraints"]] <- .check_interaction_constraints( interaction_constraints = interaction_constraints , column_names = cnames ) @@ -212,12 +214,18 @@ lgb.train <- function(params = list(), # Add printing log callback if (params[["verbosity"]] > 0L && eval_freq > 0L) { - callbacks <- add.cb(cb_list = callbacks, cb = cb_print_evaluation(period = eval_freq)) + callbacks <- .add_cb( + cb_list = callbacks + , cb = cb_print_evaluation(period = eval_freq) + ) } # Add evaluation log callback if (record && length(valids) > 0L) { - callbacks <- add.cb(cb_list = callbacks, cb = cb_record_evaluation()) + callbacks <- .add_cb( + cb_list = callbacks + , cb = cb_record_evaluation() + ) } # Did user pass parameters that indicate they want to use early stopping? @@ -249,7 +257,7 @@ lgb.train <- function(params = list(), # If user supplied early_stopping_rounds, add the early stopping callback if (using_early_stopping) { - callbacks <- add.cb( + callbacks <- .add_cb( cb_list = callbacks , cb = cb_early_stop( stopping_rounds = early_stopping_rounds @@ -259,7 +267,7 @@ lgb.train <- function(params = list(), ) } - cb <- categorize.callbacks(cb_list = callbacks) + cb <- .categorize_callbacks(cb_list = callbacks) # Construct booster with datasets booster <- Booster$new(params = params, train_set = data) diff --git a/R-package/R/lightgbm.R b/R-package/R/lightgbm.R index 711b3ef0dc38..e5df7a93fc97 100644 --- a/R-package/R/lightgbm.R +++ b/R-package/R/lightgbm.R @@ -184,21 +184,21 @@ lightgbm <- function(data, } if (is.null(num_threads)) { - num_threads <- lgb.get.default.num.threads() + num_threads <- .get_default_num_threads() } - params <- lgb.check.wrapper_param( + params <- .check_wrapper_param( main_param_name = "num_threads" , params = params , alternative_kwarg_value = num_threads ) - params <- lgb.check.wrapper_param( + params <- .check_wrapper_param( main_param_name = "verbosity" , params = params , alternative_kwarg_value = verbose ) # Process factors as labels and auto-determine objective - if (!lgb.is.Dataset(data)) { + if (!.is_Dataset(data)) { data_processor <- DataProcessor$new() temp <- data_processor$process_label( label = label @@ -220,7 +220,7 @@ lightgbm <- function(data, dtrain <- data # Check whether data is lgb.Dataset, if not then create lgb.Dataset manually - if (!lgb.is.Dataset(x = dtrain)) { + if (!.is_Dataset(x = dtrain)) { dtrain <- lgb.Dataset(data = data, label = label, weight = weights, init_score = init_score) } diff --git a/R-package/R/multithreading.R b/R-package/R/multithreading.R new file mode 100644 index 000000000000..a8d6b51a8968 --- /dev/null +++ b/R-package/R/multithreading.R @@ -0,0 +1,51 @@ +#' @name setLGBMThreads +#' @title Set maximum number of threads used by LightGBM +#' @description LightGBM attempts to speed up many operations by using multi-threading. +#' The number of threads used in those operations can be controlled via the +#' \code{num_threads} parameter passed through \code{params} to functions like +#' \link{lgb.train} and \link{lgb.Dataset}. However, some operations (like materializing +#' a model from a text file) are done via code paths that don't explicitly accept thread-control +#' configuration. +#' +#' Use this function to set the maximum number of threads LightGBM will use for such operations. +#' +#' This function affects all LightGBM operations in the same process. +#' +#' So, for example, if you call \code{setLGBMthreads(4)}, no other multi-threaded LightGBM +#' operation in the same process will use more than 4 threads. +#' +#' Call \code{setLGBMthreads(-1)} to remove this limitation. +#' @param num_threads maximum number of threads to be used by LightGBM in multi-threaded operations +#' @return NULL +#' @seealso \link{getLGBMthreads} +#' @export +setLGBMthreads <- function(num_threads) { + .Call( + LGBM_SetMaxThreads_R, + num_threads + ) + return(invisible(NULL)) +} + +#' @name getLGBMThreads +#' @title Get default number of threads used by LightGBM +#' @description LightGBM attempts to speed up many operations by using multi-threading. +#' The number of threads used in those operations can be controlled via the +#' \code{num_threads} parameter passed through \code{params} to functions like +#' \link{lgb.train} and \link{lgb.Dataset}. However, some operations (like materializing +#' a model from a text file) are done via code paths that don't explicitly accept thread-control +#' configuration. +#' +#' Use this function to see the default number of threads LightGBM will use for such operations. +#' @return number of threads as an integer. \code{-1} means that in situations where parameter \code{num_threads} is +#' not explicitly supplied, LightGBM will choose a number of threads to use automatically. +#' @seealso \link{setLGBMthreads} +#' @export +getLGBMthreads <- function() { + out <- 0L + .Call( + LGBM_GetMaxThreads_R, + out + ) + return(out) +} diff --git a/R-package/R/readRDS.lgb.Booster.R b/R-package/R/readRDS.lgb.Booster.R deleted file mode 100644 index a8abac642c24..000000000000 --- a/R-package/R/readRDS.lgb.Booster.R +++ /dev/null @@ -1,48 +0,0 @@ -#' @name readRDS.lgb.Booster -#' @title readRDS for \code{lgb.Booster} models (DEPRECATED) -#' @description Calls \code{readRDS} in what is expected to be a serialized \code{lgb.Booster} object, -#' and then restores its handle through \code{lgb.restore_handle}. -#' -#' \bold{This function throws a warning and will be removed in future versions.} -#' @param file a connection or the name of the file where the R object is saved to or read from. -#' @param refhook a hook function for handling reference objects. -#' -#' @return \code{lgb.Booster} -#' -#' @examples -#' \donttest{ -#' library(lightgbm) -#' data(agaricus.train, package = "lightgbm") -#' train <- agaricus.train -#' dtrain <- lgb.Dataset(train$data, label = train$label) -#' data(agaricus.test, package = "lightgbm") -#' test <- agaricus.test -#' dtest <- lgb.Dataset.create.valid(dtrain, test$data, label = test$label) -#' params <- list( -#' objective = "regression" -#' , metric = "l2" -#' , min_data = 1L -#' , learning_rate = 1.0 -#' , num_threads = 2L -#' ) -#' valids <- list(test = dtest) -#' model <- lgb.train( -#' params = params -#' , data = dtrain -#' , nrounds = 10L -#' , valids = valids -#' , early_stopping_rounds = 5L -#' ) -#' model_file <- tempfile(fileext = ".rds") -#' saveRDS.lgb.Booster(model, model_file) -#' new_model <- readRDS.lgb.Booster(model_file) -#' } -#' @export -readRDS.lgb.Booster <- function(file, refhook = NULL) { - - warning("'readRDS.lgb.Booster' is deprecated and will be removed in a future release. Use readRDS() instead.") - - object <- readRDS(file = file, refhook = refhook) - lgb.restore_handle(object) - return(object) -} diff --git a/R-package/R/saveRDS.lgb.Booster.R b/R-package/R/saveRDS.lgb.Booster.R deleted file mode 100644 index 5d3af097301f..000000000000 --- a/R-package/R/saveRDS.lgb.Booster.R +++ /dev/null @@ -1,78 +0,0 @@ -#' @name saveRDS.lgb.Booster -#' @title saveRDS for \code{lgb.Booster} models (DEPRECATED) -#' @description Calls \code{saveRDS} on an \code{lgb.Booster} object, making it serializable before the call if -#' it isn't already. -#' -#' \bold{This function throws a warning and will be removed in future versions.} -#' @param object \code{lgb.Booster} object to serialize. -#' @param file a connection or the name of the file where the R object is saved to or read from. -#' @param ascii a logical. If TRUE or NA, an ASCII representation is written; otherwise (default), -#' a binary one is used. See the comments in the help for save. -#' @param version the workspace format version to use. \code{NULL} specifies the current default -#' version (2). Versions prior to 2 are not supported, so this will only be relevant -#' when there are later versions. -#' @param compress a logical specifying whether saving to a named file is to use "gzip" compression, -#' or one of \code{"gzip"}, \code{"bzip2"} or \code{"xz"} to indicate the type of -#' compression to be used. Ignored if file is a connection. -#' @param refhook a hook function for handling reference objects. -#' @param raw whether to save the model in a raw variable or not, recommended to leave it to \code{TRUE}. -#' -#' @return NULL invisibly. -#' -#' @examples -#' \donttest{ -#' library(lightgbm) -#' data(agaricus.train, package = "lightgbm") -#' train <- agaricus.train -#' dtrain <- lgb.Dataset(train$data, label = train$label) -#' data(agaricus.test, package = "lightgbm") -#' test <- agaricus.test -#' dtest <- lgb.Dataset.create.valid(dtrain, test$data, label = test$label) -#' params <- list( -#' objective = "regression" -#' , metric = "l2" -#' , min_data = 1L -#' , learning_rate = 1.0 -#' , num_threads = 2L -#' ) -#' valids <- list(test = dtest) -#' model <- lgb.train( -#' params = params -#' , data = dtrain -#' , nrounds = 10L -#' , valids = valids -#' , early_stopping_rounds = 5L -#' ) -#' model_file <- tempfile(fileext = ".rds") -#' saveRDS.lgb.Booster(model, model_file) -#' } -#' @export -saveRDS.lgb.Booster <- function(object, - file, - ascii = FALSE, - version = NULL, - compress = TRUE, - refhook = NULL, - raw = TRUE) { - - warning("'saveRDS.lgb.Booster' is deprecated and will be removed in a future release. Use saveRDS() instead.") - - if (!lgb.is.Booster(x = object)) { - stop("saveRDS.lgb.Booster: object should be an ", sQuote("lgb.Booster")) - } - - if (is.null(object$raw)) { - lgb.make_serializable(object) - } - - saveRDS( - object - , file = file - , ascii = ascii - , version = version - , compress = compress - , refhook = refhook - ) - - return(invisible(NULL)) -} diff --git a/R-package/R/utils.R b/R-package/R/utils.R index c9ba780316df..1ac6f197ca77 100644 --- a/R-package/R/utils.R +++ b/R-package/R/utils.R @@ -1,16 +1,16 @@ -lgb.is.Booster <- function(x) { +.is_Booster <- function(x) { return(all(c("R6", "lgb.Booster") %in% class(x))) # nolint: class_equals } -lgb.is.Dataset <- function(x) { +.is_Dataset <- function(x) { return(all(c("R6", "lgb.Dataset") %in% class(x))) # nolint: class_equals } -lgb.is.Predictor <- function(x) { +.is_Predictor <- function(x) { return(all(c("R6", "lgb.Predictor") %in% class(x))) # nolint: class_equals } -lgb.is.null.handle <- function(x) { +.is_null_handle <- function(x) { if (is.null(x)) { return(TRUE) } @@ -19,7 +19,7 @@ lgb.is.null.handle <- function(x) { ) } -lgb.params2str <- function(params) { +.params2str <- function(params) { if (!identical(class(params), "list")) { stop("params must be a list") @@ -59,7 +59,7 @@ lgb.params2str <- function(params) { } -lgb.check_interaction_constraints <- function(interaction_constraints, column_names) { +.check_interaction_constraints <- function(interaction_constraints, column_names) { # Convert interaction constraints to feature numbers string_constraints <- list() @@ -129,7 +129,7 @@ lgb.check_interaction_constraints <- function(interaction_constraints, column_na # This has to account for the fact that `eval` could be a character vector, # a function, a list of functions, or a list with a mix of strings and # functions -lgb.check.eval <- function(params, eval) { +.check_eval <- function(params, eval) { if (is.null(params$metric)) { params$metric <- list() @@ -194,7 +194,7 @@ lgb.check.eval <- function(params, eval) { # [return] # params with num_iterations set to the chosen value, and other aliases # of num_iterations removed -lgb.check.wrapper_param <- function(main_param_name, params, alternative_kwarg_value) { +.check_wrapper_param <- function(main_param_name, params, alternative_kwarg_value) { aliases <- .PARAMETER_ALIASES()[[main_param_name]] aliases_provided <- aliases[aliases %in% names(params)] @@ -225,7 +225,7 @@ lgb.check.wrapper_param <- function(main_param_name, params, alternative_kwarg_v } #' @importFrom parallel detectCores -lgb.get.default.num.threads <- function() { +.get_default_num_threads <- function() { if (requireNamespace("RhpcBLASctl", quietly = TRUE)) { # nolint: undesirable_function return(RhpcBLASctl::get_num_cores()) } else { @@ -247,7 +247,7 @@ lgb.get.default.num.threads <- function() { } } -lgb.equal.or.both.null <- function(a, b) { +.equal_or_both_null <- function(a, b) { if (is.null(a)) { if (!is.null(b)) { return(FALSE) diff --git a/R-package/README.md b/R-package/README.md index 69f6dba67053..a9dc65b5d7b3 100644 --- a/R-package/README.md +++ b/R-package/README.md @@ -97,9 +97,10 @@ After installing `Rtools` and `CMake`, be sure the following paths are added to - If you have `Rtools` 4.0, example: - `C:\rtools40\mingw64\bin` - `C:\rtools40\usr\bin` - - If you have `Rtools` 4.2, example: + - If you have `Rtools` 4.2+, example: - `C:\rtools42\x86_64-w64-mingw32.static.posix\bin` - `C:\rtools42\usr\bin` + - **NOTE**: this is e.g. `rtools43\` for R 4.3 * `CMake` - example: `C:\Program Files\CMake\bin` * `R` @@ -107,7 +108,7 @@ After installing `Rtools` and `CMake`, be sure the following paths are added to NOTE: Two `Rtools` paths are required from `Rtools` 4.0 onwards because paths and the list of included software was changed in `Rtools` 4.0. -NOTE: `Rtools42` takes a very different approach to the compiler toolchain than previous releases, and how you install it changes what is required to build packages. See ["Howto: Building R 4.2 and packages on Windows"](https://cran.r-project.org/bin/windows/base/howto-R-4.2.html). +NOTE: `Rtools42` and later take a very different approach to the compiler toolchain than previous releases, and how you install it changes what is required to build packages. See ["Howto: Building R 4.2 and packages on Windows"](https://cran.r-project.org/bin/windows/base/howto-R-4.2.html). #### Windows Toolchain Options @@ -427,7 +428,7 @@ docker run \ # install dependencies RDscript${R_CUSTOMIZATION} \ - -e "install.packages(c('R6', 'data.table', 'jsonlite', 'knitr', 'Matrix', 'RhpcBLASctl', 'rmarkdown', 'testthat'), repos = 'https://cran.r-project.org', Ncpus = parallel::detectCores())" + -e "install.packages(c('R6', 'data.table', 'jsonlite', 'knitr', 'markdown', 'Matrix', 'RhpcBLASctl', 'testthat'), repos = 'https://cran.r-project.org', Ncpus = parallel::detectCores())" # install lightgbm sh build-cran-package.sh --r-executable=RD${R_CUSTOMIZATION} @@ -458,7 +459,7 @@ docker run \ -it \ wch1/r-debug -RDscriptvalgrind -e "install.packages(c('R6', 'data.table', 'jsonlite', 'knitr', 'Matrix', 'RhpcBLASctl', 'rmarkdown', 'testthat'), repos = 'https://cran.rstudio.com', Ncpus = parallel::detectCores())" +RDscriptvalgrind -e "install.packages(c('R6', 'data.table', 'jsonlite', 'knitr', 'markdown', 'Matrix', 'RhpcBLASctl', 'testthat'), repos = 'https://cran.rstudio.com', Ncpus = parallel::detectCores())" sh build-cran-package.sh \ --r-executable=RDvalgrind diff --git a/R-package/configure b/R-package/configure index 39a18d669833..37dfdbfbf6c7 100755 --- a/R-package/configure +++ b/R-package/configure @@ -1,6 +1,6 @@ #! /bin/sh # Guess values for system-dependent variables and create Makefiles. -# Generated by GNU Autoconf 2.71 for lightgbm 4.1.0.99. +# Generated by GNU Autoconf 2.71 for lightgbm 4.2.0.99. # # # Copyright (C) 1992-1996, 1998-2017, 2020-2021 Free Software Foundation, @@ -607,8 +607,8 @@ MAKEFLAGS= # Identity of this package. PACKAGE_NAME='lightgbm' PACKAGE_TARNAME='lightgbm' -PACKAGE_VERSION='4.1.0.99' -PACKAGE_STRING='lightgbm 4.1.0.99' +PACKAGE_VERSION='4.2.0.99' +PACKAGE_STRING='lightgbm 4.2.0.99' PACKAGE_BUGREPORT='' PACKAGE_URL='' @@ -1211,7 +1211,7 @@ if test "$ac_init_help" = "long"; then # Omit some internal or obsolete options to make the list less imposing. # This message is too long to be a string in the A/UX 3.1 sh. cat <<_ACEOF -\`configure' configures lightgbm 4.1.0.99 to adapt to many kinds of systems. +\`configure' configures lightgbm 4.2.0.99 to adapt to many kinds of systems. Usage: $0 [OPTION]... [VAR=VALUE]... @@ -1273,7 +1273,7 @@ fi if test -n "$ac_init_help"; then case $ac_init_help in - short | recursive ) echo "Configuration of lightgbm 4.1.0.99:";; + short | recursive ) echo "Configuration of lightgbm 4.2.0.99:";; esac cat <<\_ACEOF @@ -1341,7 +1341,7 @@ fi test -n "$ac_init_help" && exit $ac_status if $ac_init_version; then cat <<\_ACEOF -lightgbm configure 4.1.0.99 +lightgbm configure 4.2.0.99 generated by GNU Autoconf 2.71 Copyright (C) 2021 Free Software Foundation, Inc. @@ -1378,7 +1378,7 @@ cat >config.log <<_ACEOF This file contains any messages produced by compilers while running configure, to aid debugging if configure makes a mistake. -It was created by lightgbm $as_me 4.1.0.99, which was +It was created by lightgbm $as_me 4.2.0.99, which was generated by GNU Autoconf 2.71. Invocation command line was $ $0$ac_configure_args_raw @@ -2454,7 +2454,7 @@ cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 # report actual input values of CONFIG_FILES etc. instead of their # values after options handling. ac_log=" -This file was extended by lightgbm $as_me 4.1.0.99, which was +This file was extended by lightgbm $as_me 4.2.0.99, which was generated by GNU Autoconf 2.71. Invocation command line was CONFIG_FILES = $CONFIG_FILES @@ -2509,7 +2509,7 @@ ac_cs_config_escaped=`printf "%s\n" "$ac_cs_config" | sed "s/^ //; s/'/'\\\\\\\\ cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 ac_cs_config='$ac_cs_config_escaped' ac_cs_version="\\ -lightgbm config.status 4.1.0.99 +lightgbm config.status 4.2.0.99 configured by $0, generated by GNU Autoconf 2.71, with options \\"\$ac_cs_config\\" diff --git a/R-package/cran-comments.md b/R-package/cran-comments.md index 44b8ed391bfc..80f84b924a48 100644 --- a/R-package/cran-comments.md +++ b/R-package/cran-comments.md @@ -1,5 +1,16 @@ # CRAN Submission History +## v4.2.0 - Submission 1 - (December 7, 2023) + +### CRAN response + +Accepted to CRAN + +### Maintainer Notes + +This submission included many changes from the last 2 years, as well as fixes for a warning +CRAN said could cause the package to be archived: https://github.com/microsoft/LightGBM/issues/6221. + ## v4.1.0 - not submitted v4.1.0 was not submitted to CRAN, because https://github.com/microsoft/LightGBM/issues/5987 had not been resolved. diff --git a/R-package/demo/basic_walkthrough.R b/R-package/demo/basic_walkthrough.R index b5bfed26d935..c9ac484e68cb 100644 --- a/R-package/demo/basic_walkthrough.R +++ b/R-package/demo/basic_walkthrough.R @@ -1,5 +1,4 @@ library(lightgbm) -library(methods) # We load in the agaricus dataset # In this example, we are aiming to predict whether a mushroom is edible diff --git a/R-package/demo/boost_from_prediction.R b/R-package/demo/boost_from_prediction.R index 5ee04f2d756e..68d33a73cac0 100644 --- a/R-package/demo/boost_from_prediction.R +++ b/R-package/demo/boost_from_prediction.R @@ -1,5 +1,4 @@ library(lightgbm) -library(methods) # Load in the agaricus dataset data(agaricus.train, package = "lightgbm") diff --git a/R-package/demo/early_stopping.R b/R-package/demo/early_stopping.R index 5179195f02ad..6ca214c5ac7b 100644 --- a/R-package/demo/early_stopping.R +++ b/R-package/demo/early_stopping.R @@ -1,5 +1,4 @@ library(lightgbm) -library(methods) # Load in the agaricus dataset data(agaricus.train, package = "lightgbm") diff --git a/R-package/man/dim.Rd b/R-package/man/dim.Rd index 94ca192d8291..69332d0ec397 100644 --- a/R-package/man/dim.Rd +++ b/R-package/man/dim.Rd @@ -21,6 +21,8 @@ be directly used with an \code{lgb.Dataset} object. } \examples{ \donttest{ +\dontshow{setLGBMthreads(2L)} +\dontshow{data.table::setDTthreads(1L)} data(agaricus.train, package = "lightgbm") train <- agaricus.train dtrain <- lgb.Dataset(train$data, label = train$label) diff --git a/R-package/man/dimnames.lgb.Dataset.Rd b/R-package/man/dimnames.lgb.Dataset.Rd index ec01a04f607b..85f2085f1d77 100644 --- a/R-package/man/dimnames.lgb.Dataset.Rd +++ b/R-package/man/dimnames.lgb.Dataset.Rd @@ -28,6 +28,8 @@ Since row names are irrelevant, it is recommended to use \code{colnames} directl } \examples{ \donttest{ +\dontshow{setLGBMthreads(2L)} +\dontshow{data.table::setDTthreads(1L)} data(agaricus.train, package = "lightgbm") train <- agaricus.train dtrain <- lgb.Dataset(train$data, label = train$label) diff --git a/R-package/man/getLGBMThreads.Rd b/R-package/man/getLGBMThreads.Rd new file mode 100644 index 000000000000..21af4f4849d4 --- /dev/null +++ b/R-package/man/getLGBMThreads.Rd @@ -0,0 +1,26 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/multithreading.R +\name{getLGBMThreads} +\alias{getLGBMThreads} +\alias{getLGBMthreads} +\title{Get default number of threads used by LightGBM} +\usage{ +getLGBMthreads() +} +\value{ +number of threads as an integer. \code{-1} means that in situations where parameter \code{num_threads} is + not explicitly supplied, LightGBM will choose a number of threads to use automatically. +} +\description{ +LightGBM attempts to speed up many operations by using multi-threading. + The number of threads used in those operations can be controlled via the + \code{num_threads} parameter passed through \code{params} to functions like + \link{lgb.train} and \link{lgb.Dataset}. However, some operations (like materializing + a model from a text file) are done via code paths that don't explicitly accept thread-control + configuration. + + Use this function to see the default number of threads LightGBM will use for such operations. +} +\seealso{ +\link{setLGBMthreads} +} diff --git a/R-package/man/get_field.Rd b/R-package/man/get_field.Rd index 1b6692fcf807..e2562cc21364 100644 --- a/R-package/man/get_field.Rd +++ b/R-package/man/get_field.Rd @@ -32,6 +32,8 @@ Get one attribute of a \code{lgb.Dataset} } \examples{ \donttest{ +\dontshow{setLGBMthreads(2L)} +\dontshow{data.table::setDTthreads(1L)} data(agaricus.train, package = "lightgbm") train <- agaricus.train dtrain <- lgb.Dataset(train$data, label = train$label) diff --git a/R-package/man/lgb.Dataset.Rd b/R-package/man/lgb.Dataset.Rd index 4895600ff922..2605657b060a 100644 --- a/R-package/man/lgb.Dataset.Rd +++ b/R-package/man/lgb.Dataset.Rd @@ -65,6 +65,8 @@ Construct \code{lgb.Dataset} object from dense matrix, sparse matrix } \examples{ \donttest{ +\dontshow{setLGBMthreads(2L)} +\dontshow{data.table::setDTthreads(1L)} data(agaricus.train, package = "lightgbm") train <- agaricus.train dtrain <- lgb.Dataset(train$data, label = train$label) diff --git a/R-package/man/lgb.Dataset.construct.Rd b/R-package/man/lgb.Dataset.construct.Rd index 97c9e7887602..e400e0a5f8d5 100644 --- a/R-package/man/lgb.Dataset.construct.Rd +++ b/R-package/man/lgb.Dataset.construct.Rd @@ -17,6 +17,8 @@ Construct Dataset explicitly } \examples{ \donttest{ +\dontshow{setLGBMthreads(2L)} +\dontshow{data.table::setDTthreads(1L)} data(agaricus.train, package = "lightgbm") train <- agaricus.train dtrain <- lgb.Dataset(train$data, label = train$label) diff --git a/R-package/man/lgb.Dataset.create.valid.Rd b/R-package/man/lgb.Dataset.create.valid.Rd index ab8ca753c2b9..fc50dff19986 100644 --- a/R-package/man/lgb.Dataset.create.valid.Rd +++ b/R-package/man/lgb.Dataset.create.valid.Rd @@ -48,6 +48,8 @@ Construct validation data according to training data } \examples{ \donttest{ +\dontshow{setLGBMthreads(2L)} +\dontshow{data.table::setDTthreads(1L)} data(agaricus.train, package = "lightgbm") train <- agaricus.train dtrain <- lgb.Dataset(train$data, label = train$label) diff --git a/R-package/man/lgb.Dataset.save.Rd b/R-package/man/lgb.Dataset.save.Rd index 5ea38227ba66..b03c2c5e0ac5 100644 --- a/R-package/man/lgb.Dataset.save.Rd +++ b/R-package/man/lgb.Dataset.save.Rd @@ -20,6 +20,8 @@ Please note that \code{init_score} is not saved in binary file. } \examples{ \donttest{ +\dontshow{setLGBMthreads(2L)} +\dontshow{data.table::setDTthreads(1L)} data(agaricus.train, package = "lightgbm") train <- agaricus.train dtrain <- lgb.Dataset(train$data, label = train$label) diff --git a/R-package/man/lgb.Dataset.set.categorical.Rd b/R-package/man/lgb.Dataset.set.categorical.Rd index 26eb10770e47..5dfcc9a771e8 100644 --- a/R-package/man/lgb.Dataset.set.categorical.Rd +++ b/R-package/man/lgb.Dataset.set.categorical.Rd @@ -22,6 +22,8 @@ Set the categorical features of an \code{lgb.Dataset} object. Use this function } \examples{ \donttest{ +\dontshow{setLGBMthreads(2L)} +\dontshow{data.table::setDTthreads(1L)} data(agaricus.train, package = "lightgbm") train <- agaricus.train dtrain <- lgb.Dataset(train$data, label = train$label) diff --git a/R-package/man/lgb.Dataset.set.reference.Rd b/R-package/man/lgb.Dataset.set.reference.Rd index 349b0b22913e..a4efbfac5962 100644 --- a/R-package/man/lgb.Dataset.set.reference.Rd +++ b/R-package/man/lgb.Dataset.set.reference.Rd @@ -19,6 +19,8 @@ If you want to use validation data, you should set reference to training data } \examples{ \donttest{ +\dontshow{setLGBMthreads(2L)} +\dontshow{data.table::setDTthreads(1L)} # create training Dataset data(agaricus.train, package ="lightgbm") train <- agaricus.train diff --git a/R-package/man/lgb.configure_fast_predict.Rd b/R-package/man/lgb.configure_fast_predict.Rd index 39fe6afa6b18..e02600451df5 100644 --- a/R-package/man/lgb.configure_fast_predict.Rd +++ b/R-package/man/lgb.configure_fast_predict.Rd @@ -114,6 +114,8 @@ Calling this function multiple times with different parameters might not overrid } \examples{ \donttest{ +\dontshow{setLGBMthreads(2L)} +\dontshow{data.table::setDTthreads(1L)} library(lightgbm) data(mtcars) X <- as.matrix(mtcars[, -1L]) diff --git a/R-package/man/lgb.cv.Rd b/R-package/man/lgb.cv.Rd index 555cb11c7bb3..7ea2928c6166 100644 --- a/R-package/man/lgb.cv.Rd +++ b/R-package/man/lgb.cv.Rd @@ -152,6 +152,8 @@ Cross validation logic used by LightGBM \examples{ \donttest{ +\dontshow{setLGBMthreads(2L)} +\dontshow{data.table::setDTthreads(1L)} data(agaricus.train, package = "lightgbm") train <- agaricus.train dtrain <- lgb.Dataset(train$data, label = train$label) diff --git a/R-package/man/lgb.dump.Rd b/R-package/man/lgb.dump.Rd index f4e90242fd75..39f0e3018ac7 100644 --- a/R-package/man/lgb.dump.Rd +++ b/R-package/man/lgb.dump.Rd @@ -20,6 +20,8 @@ Dump LightGBM model to json \examples{ \donttest{ library(lightgbm) +\dontshow{setLGBMthreads(2L)} +\dontshow{data.table::setDTthreads(1L)} data(agaricus.train, package = "lightgbm") train <- agaricus.train dtrain <- lgb.Dataset(train$data, label = train$label) diff --git a/R-package/man/lgb.get.eval.result.Rd b/R-package/man/lgb.get.eval.result.Rd index 9c2293a0f909..0dc7eb0845c3 100644 --- a/R-package/man/lgb.get.eval.result.Rd +++ b/R-package/man/lgb.get.eval.result.Rd @@ -33,6 +33,8 @@ Given a \code{lgb.Booster}, return evaluation results for a } \examples{ \donttest{ +\dontshow{setLGBMthreads(2L)} +\dontshow{data.table::setDTthreads(1L)} # train a regression model data(agaricus.train, package = "lightgbm") train <- agaricus.train diff --git a/R-package/man/lgb.importance.Rd b/R-package/man/lgb.importance.Rd index 89a3d4e6b5b7..79cb82f5d8ef 100644 --- a/R-package/man/lgb.importance.Rd +++ b/R-package/man/lgb.importance.Rd @@ -25,6 +25,8 @@ Creates a \code{data.table} of feature importances in a model. } \examples{ \donttest{ +\dontshow{setLGBMthreads(2L)} +\dontshow{data.table::setDTthreads(1L)} data(agaricus.train, package = "lightgbm") train <- agaricus.train dtrain <- lgb.Dataset(train$data, label = train$label) diff --git a/R-package/man/lgb.interprete.Rd b/R-package/man/lgb.interprete.Rd index c1166b2c1cc9..3acc27955c46 100644 --- a/R-package/man/lgb.interprete.Rd +++ b/R-package/man/lgb.interprete.Rd @@ -30,6 +30,8 @@ Computes feature contribution components of rawscore prediction. } \examples{ \donttest{ +\dontshow{setLGBMthreads(2L)} +\dontshow{data.table::setDTthreads(1L)} Logit <- function(x) log(x / (1.0 - x)) data(agaricus.train, package = "lightgbm") train <- agaricus.train diff --git a/R-package/man/lgb.load.Rd b/R-package/man/lgb.load.Rd index c1a00a20974b..f145db5a245e 100644 --- a/R-package/man/lgb.load.Rd +++ b/R-package/man/lgb.load.Rd @@ -20,6 +20,8 @@ Load LightGBM takes in either a file path or model string. } \examples{ \donttest{ +\dontshow{setLGBMthreads(2L)} +\dontshow{data.table::setDTthreads(1L)} data(agaricus.train, package = "lightgbm") train <- agaricus.train dtrain <- lgb.Dataset(train$data, label = train$label) diff --git a/R-package/man/lgb.model.dt.tree.Rd b/R-package/man/lgb.model.dt.tree.Rd index 4d02ede9a001..60ef8cdac133 100644 --- a/R-package/man/lgb.model.dt.tree.Rd +++ b/R-package/man/lgb.model.dt.tree.Rd @@ -40,6 +40,8 @@ Parse a LightGBM model json dump into a \code{data.table} structure. } \examples{ \donttest{ +\dontshow{setLGBMthreads(2L)} +\dontshow{data.table::setDTthreads(1L)} data(agaricus.train, package = "lightgbm") train <- agaricus.train dtrain <- lgb.Dataset(train$data, label = train$label) diff --git a/R-package/man/lgb.plot.importance.Rd b/R-package/man/lgb.plot.importance.Rd index 302f46460e3f..bdf354da0385 100644 --- a/R-package/man/lgb.plot.importance.Rd +++ b/R-package/man/lgb.plot.importance.Rd @@ -38,6 +38,8 @@ Features are shown ranked in a decreasing importance order. } \examples{ \donttest{ +\dontshow{setLGBMthreads(2L)} +\dontshow{data.table::setDTthreads(1L)} data(agaricus.train, package = "lightgbm") train <- agaricus.train dtrain <- lgb.Dataset(train$data, label = train$label) diff --git a/R-package/man/lgb.plot.interpretation.Rd b/R-package/man/lgb.plot.interpretation.Rd index a914071e896f..6f168e120a4e 100644 --- a/R-package/man/lgb.plot.interpretation.Rd +++ b/R-package/man/lgb.plot.interpretation.Rd @@ -35,6 +35,8 @@ contribution of a feature. Features are shown ranked in a decreasing contributio } \examples{ \donttest{ +\dontshow{setLGBMthreads(2L)} +\dontshow{data.table::setDTthreads(1L)} Logit <- function(x) { log(x / (1.0 - x)) } diff --git a/R-package/man/lgb.restore_handle.Rd b/R-package/man/lgb.restore_handle.Rd index 95cbdc64485d..37922c077642 100644 --- a/R-package/man/lgb.restore_handle.Rd +++ b/R-package/man/lgb.restore_handle.Rd @@ -27,7 +27,10 @@ function. If you wish to make fast single-row predictions using a \code{lgb.Boos call \link{lgb.configure_fast_predict} on the loaded \code{lgb.Booster} object. } \examples{ +\donttest{ library(lightgbm) +\dontshow{setLGBMthreads(2L)} +\dontshow{data.table::setDTthreads(1L)} data("agaricus.train") model <- lightgbm( agaricus.train$data @@ -45,6 +48,7 @@ model_new$check_null_handle() lgb.restore_handle(model_new) model_new$check_null_handle() } +} \seealso{ \link{lgb.make_serializable}, \link{lgb.drop_serialized}. } diff --git a/R-package/man/lgb.save.Rd b/R-package/man/lgb.save.Rd index efd110c7d816..62ec0ed462f6 100644 --- a/R-package/man/lgb.save.Rd +++ b/R-package/man/lgb.save.Rd @@ -21,6 +21,8 @@ Save LightGBM model } \examples{ \donttest{ +\dontshow{setLGBMthreads(2L)} +\dontshow{data.table::setDTthreads(1L)} library(lightgbm) data(agaricus.train, package = "lightgbm") train <- agaricus.train diff --git a/R-package/man/lgb.train.Rd b/R-package/man/lgb.train.Rd index 0f2961edc415..557c85b7f9dc 100644 --- a/R-package/man/lgb.train.Rd +++ b/R-package/man/lgb.train.Rd @@ -130,6 +130,8 @@ Low-level R interface to train a LightGBM model. Unlike \code{\link{lightgbm}}, \examples{ \donttest{ +\dontshow{setLGBMthreads(2L)} +\dontshow{data.table::setDTthreads(1L)} data(agaricus.train, package = "lightgbm") train <- agaricus.train dtrain <- lgb.Dataset(train$data, label = train$label) diff --git a/R-package/man/predict.lgb.Booster.Rd b/R-package/man/predict.lgb.Booster.Rd index 2df13b9bc374..bcb2f3f980fb 100644 --- a/R-package/man/predict.lgb.Booster.Rd +++ b/R-package/man/predict.lgb.Booster.Rd @@ -121,6 +121,8 @@ If the model object has been configured for fast single-row predictions through } \examples{ \donttest{ +\dontshow{setLGBMthreads(2L)} +\dontshow{data.table::setDTthreads(1L)} data(agaricus.train, package = "lightgbm") train <- agaricus.train dtrain <- lgb.Dataset(train$data, label = train$label) diff --git a/R-package/man/readRDS.lgb.Booster.Rd b/R-package/man/readRDS.lgb.Booster.Rd deleted file mode 100644 index 6a8e4c80ca91..000000000000 --- a/R-package/man/readRDS.lgb.Booster.Rd +++ /dev/null @@ -1,51 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/readRDS.lgb.Booster.R -\name{readRDS.lgb.Booster} -\alias{readRDS.lgb.Booster} -\title{readRDS for \code{lgb.Booster} models (DEPRECATED)} -\usage{ -readRDS.lgb.Booster(file, refhook = NULL) -} -\arguments{ -\item{file}{a connection or the name of the file where the R object is saved to or read from.} - -\item{refhook}{a hook function for handling reference objects.} -} -\value{ -\code{lgb.Booster} -} -\description{ -Calls \code{readRDS} in what is expected to be a serialized \code{lgb.Booster} object, - and then restores its handle through \code{lgb.restore_handle}. - - \bold{This function throws a warning and will be removed in future versions.} -} -\examples{ -\donttest{ -library(lightgbm) -data(agaricus.train, package = "lightgbm") -train <- agaricus.train -dtrain <- lgb.Dataset(train$data, label = train$label) -data(agaricus.test, package = "lightgbm") -test <- agaricus.test -dtest <- lgb.Dataset.create.valid(dtrain, test$data, label = test$label) -params <- list( - objective = "regression" - , metric = "l2" - , min_data = 1L - , learning_rate = 1.0 - , num_threads = 2L -) -valids <- list(test = dtest) -model <- lgb.train( - params = params - , data = dtrain - , nrounds = 10L - , valids = valids - , early_stopping_rounds = 5L -) -model_file <- tempfile(fileext = ".rds") -saveRDS.lgb.Booster(model, model_file) -new_model <- readRDS.lgb.Booster(model_file) -} -} diff --git a/R-package/man/saveRDS.lgb.Booster.Rd b/R-package/man/saveRDS.lgb.Booster.Rd deleted file mode 100644 index a8664243dce2..000000000000 --- a/R-package/man/saveRDS.lgb.Booster.Rd +++ /dev/null @@ -1,73 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/saveRDS.lgb.Booster.R -\name{saveRDS.lgb.Booster} -\alias{saveRDS.lgb.Booster} -\title{saveRDS for \code{lgb.Booster} models (DEPRECATED)} -\usage{ -saveRDS.lgb.Booster( - object, - file, - ascii = FALSE, - version = NULL, - compress = TRUE, - refhook = NULL, - raw = TRUE -) -} -\arguments{ -\item{object}{\code{lgb.Booster} object to serialize.} - -\item{file}{a connection or the name of the file where the R object is saved to or read from.} - -\item{ascii}{a logical. If TRUE or NA, an ASCII representation is written; otherwise (default), -a binary one is used. See the comments in the help for save.} - -\item{version}{the workspace format version to use. \code{NULL} specifies the current default -version (2). Versions prior to 2 are not supported, so this will only be relevant -when there are later versions.} - -\item{compress}{a logical specifying whether saving to a named file is to use "gzip" compression, -or one of \code{"gzip"}, \code{"bzip2"} or \code{"xz"} to indicate the type of -compression to be used. Ignored if file is a connection.} - -\item{refhook}{a hook function for handling reference objects.} - -\item{raw}{whether to save the model in a raw variable or not, recommended to leave it to \code{TRUE}.} -} -\value{ -NULL invisibly. -} -\description{ -Calls \code{saveRDS} on an \code{lgb.Booster} object, making it serializable before the call if - it isn't already. - - \bold{This function throws a warning and will be removed in future versions.} -} -\examples{ -\donttest{ -library(lightgbm) -data(agaricus.train, package = "lightgbm") -train <- agaricus.train -dtrain <- lgb.Dataset(train$data, label = train$label) -data(agaricus.test, package = "lightgbm") -test <- agaricus.test -dtest <- lgb.Dataset.create.valid(dtrain, test$data, label = test$label) -params <- list( - objective = "regression" - , metric = "l2" - , min_data = 1L - , learning_rate = 1.0 - , num_threads = 2L -) -valids <- list(test = dtest) -model <- lgb.train( - params = params - , data = dtrain - , nrounds = 10L - , valids = valids - , early_stopping_rounds = 5L -) -model_file <- tempfile(fileext = ".rds") -saveRDS.lgb.Booster(model, model_file) -} -} diff --git a/R-package/man/setLGBMThreads.Rd b/R-package/man/setLGBMThreads.Rd new file mode 100644 index 000000000000..53336fc2548e --- /dev/null +++ b/R-package/man/setLGBMThreads.Rd @@ -0,0 +1,32 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/multithreading.R +\name{setLGBMThreads} +\alias{setLGBMThreads} +\alias{setLGBMthreads} +\title{Set maximum number of threads used by LightGBM} +\usage{ +setLGBMthreads(num_threads) +} +\arguments{ +\item{num_threads}{maximum number of threads to be used by LightGBM in multi-threaded operations} +} +\description{ +LightGBM attempts to speed up many operations by using multi-threading. + The number of threads used in those operations can be controlled via the + \code{num_threads} parameter passed through \code{params} to functions like + \link{lgb.train} and \link{lgb.Dataset}. However, some operations (like materializing + a model from a text file) are done via code paths that don't explicitly accept thread-control + configuration. + + Use this function to set the maximum number of threads LightGBM will use for such operations. + + This function affects all LightGBM operations in the same process. + + So, for example, if you call \code{setLGBMthreads(4)}, no other multi-threaded LightGBM + operation in the same process will use more than 4 threads. + + Call \code{setLGBMthreads(-1)} to remove this limitation. +} +\seealso{ +\link{getLGBMthreads} +} diff --git a/R-package/man/set_field.Rd b/R-package/man/set_field.Rd index f9901e27eefd..2ceebfb87753 100644 --- a/R-package/man/set_field.Rd +++ b/R-package/man/set_field.Rd @@ -34,6 +34,8 @@ Set one attribute of a \code{lgb.Dataset} } \examples{ \donttest{ +\dontshow{setLGBMthreads(2L)} +\dontshow{data.table::setDTthreads(1L)} data(agaricus.train, package = "lightgbm") train <- agaricus.train dtrain <- lgb.Dataset(train$data, label = train$label) diff --git a/R-package/man/slice.Rd b/R-package/man/slice.Rd index 1d7bec08de0f..a65809a239d8 100644 --- a/R-package/man/slice.Rd +++ b/R-package/man/slice.Rd @@ -23,6 +23,8 @@ Get a new \code{lgb.Dataset} containing the specified rows of } \examples{ \donttest{ +\dontshow{setLGBMthreads(2L)} +\dontshow{data.table::setDTthreads(1L)} data(agaricus.train, package = "lightgbm") train <- agaricus.train dtrain <- lgb.Dataset(train$data, label = train$label) diff --git a/R-package/pkgdown/_pkgdown.yml b/R-package/pkgdown/_pkgdown.yml index 233a31f0ead9..99a3b1010d41 100644 --- a/R-package/pkgdown/_pkgdown.yml +++ b/R-package/pkgdown/_pkgdown.yml @@ -85,8 +85,6 @@ reference: - '`lgb.save`' - '`lgb.load`' - '`lgb.model.dt.tree`' - - '`saveRDS.lgb.Booster`' - - '`readRDS.lgb.Booster`' - title: Model Interpretation desc: Analyze your models contents: diff --git a/R-package/src/Makevars.in b/R-package/src/Makevars.in index ba9ef054bfab..c04263f62c1c 100644 --- a/R-package/src/Makevars.in +++ b/R-package/src/Makevars.in @@ -53,5 +53,6 @@ OBJECTS = \ treelearner/serial_tree_learner.o \ treelearner/tree_learner.o \ treelearner/voting_parallel_tree_learner.o \ + utils/openmp_wrapper.o \ c_api.o \ lightgbm_R.o diff --git a/R-package/src/Makevars.win.in b/R-package/src/Makevars.win.in index 14f5afde002f..86d56fecdf34 100644 --- a/R-package/src/Makevars.win.in +++ b/R-package/src/Makevars.win.in @@ -54,5 +54,6 @@ OBJECTS = \ treelearner/serial_tree_learner.o \ treelearner/tree_learner.o \ treelearner/voting_parallel_tree_learner.o \ + utils/openmp_wrapper.o \ c_api.o \ lightgbm_R.o diff --git a/R-package/src/lightgbm_R.cpp b/R-package/src/lightgbm_R.cpp index 82956daef4b9..a76a56c06b24 100644 --- a/R-package/src/lightgbm_R.cpp +++ b/R-package/src/lightgbm_R.cpp @@ -40,7 +40,7 @@ void LGBM_R_save_exception_msg(const std::string &err); catch(std::exception& ex) { LGBM_R_save_exception_msg(ex); } \ catch(std::string& ex) { LGBM_R_save_exception_msg(ex); } \ catch(...) { Rf_error("unknown exception"); } \ - Rf_error(R_errmsg_buffer); \ + Rf_error("%s", R_errmsg_buffer); \ return R_NilValue; /* <- won't be reached */ #define CHECK_CALL(x) \ @@ -224,16 +224,19 @@ SEXP LGBM_DatasetGetSubset_R(SEXP handle, _AssertDatasetHandleNotNull(handle); SEXP ret = PROTECT(R_MakeExternalPtr(nullptr, R_NilValue, R_NilValue)); int32_t len = static_cast(Rf_asInteger(len_used_row_indices)); - std::vector idxvec(len); + std::unique_ptr idxvec(new int32_t[len]); // convert from one-based to zero-based index -#pragma omp parallel for schedule(static, 512) if (len >= 1024) + const int *used_row_indices_ = INTEGER(used_row_indices); +#ifndef _MSC_VER +#pragma omp simd +#endif for (int32_t i = 0; i < len; ++i) { - idxvec[i] = static_cast(INTEGER(used_row_indices)[i] - 1); + idxvec[i] = static_cast(used_row_indices_[i] - 1); } const char* parameters_ptr = CHAR(PROTECT(Rf_asChar(parameters))); DatasetHandle res = nullptr; CHECK_CALL(LGBM_DatasetGetSubset(R_ExternalPtrAddr(handle), - idxvec.data(), len, parameters_ptr, + idxvec.get(), len, parameters_ptr, &res)); R_SetExternalPtrAddr(ret, res); R_RegisterCFinalizerEx(ret, _DatasetFinalizer, TRUE); @@ -247,13 +250,13 @@ SEXP LGBM_DatasetSetFeatureNames_R(SEXP handle, R_API_BEGIN(); _AssertDatasetHandleNotNull(handle); auto vec_names = Split(CHAR(PROTECT(Rf_asChar(feature_names))), '\t'); - std::vector vec_sptr; int len = static_cast(vec_names.size()); + std::unique_ptr vec_sptr(new const char*[len]); for (int i = 0; i < len; ++i) { - vec_sptr.push_back(vec_names[i].c_str()); + vec_sptr[i] = vec_names[i].c_str(); } CHECK_CALL(LGBM_DatasetSetFeatureNames(R_ExternalPtrAddr(handle), - vec_sptr.data(), len)); + vec_sptr.get(), len)); UNPROTECT(1); return R_NilValue; R_API_END(); @@ -338,21 +341,13 @@ SEXP LGBM_DatasetSetField_R(SEXP handle, int len = Rf_asInteger(num_element); const char* name = CHAR(PROTECT(Rf_asChar(field_name))); if (!strcmp("group", name) || !strcmp("query", name)) { - std::vector vec(len); -#pragma omp parallel for schedule(static, 512) if (len >= 1024) - for (int i = 0; i < len; ++i) { - vec[i] = static_cast(INTEGER(field_data)[i]); - } - CHECK_CALL(LGBM_DatasetSetField(R_ExternalPtrAddr(handle), name, vec.data(), len, C_API_DTYPE_INT32)); + CHECK_CALL(LGBM_DatasetSetField(R_ExternalPtrAddr(handle), name, INTEGER(field_data), len, C_API_DTYPE_INT32)); } else if (!strcmp("init_score", name)) { CHECK_CALL(LGBM_DatasetSetField(R_ExternalPtrAddr(handle), name, REAL(field_data), len, C_API_DTYPE_FLOAT64)); } else { - std::vector vec(len); -#pragma omp parallel for schedule(static, 512) if (len >= 1024) - for (int i = 0; i < len; ++i) { - vec[i] = static_cast(REAL(field_data)[i]); - } - CHECK_CALL(LGBM_DatasetSetField(R_ExternalPtrAddr(handle), name, vec.data(), len, C_API_DTYPE_FLOAT32)); + std::unique_ptr vec(new float[len]); + std::copy(REAL(field_data), REAL(field_data) + len, vec.get()); + CHECK_CALL(LGBM_DatasetSetField(R_ExternalPtrAddr(handle), name, vec.get(), len, C_API_DTYPE_FLOAT32)); } UNPROTECT(1); return R_NilValue; @@ -372,22 +367,19 @@ SEXP LGBM_DatasetGetField_R(SEXP handle, if (!strcmp("group", name) || !strcmp("query", name)) { auto p_data = reinterpret_cast(res); // convert from boundaries to size -#pragma omp parallel for schedule(static, 512) if (out_len >= 1024) + int *field_data_ = INTEGER(field_data); +#ifndef _MSC_VER +#pragma omp simd +#endif for (int i = 0; i < out_len - 1; ++i) { - INTEGER(field_data)[i] = p_data[i + 1] - p_data[i]; + field_data_[i] = p_data[i + 1] - p_data[i]; } } else if (!strcmp("init_score", name)) { auto p_data = reinterpret_cast(res); -#pragma omp parallel for schedule(static, 512) if (out_len >= 1024) - for (int i = 0; i < out_len; ++i) { - REAL(field_data)[i] = p_data[i]; - } + std::copy(p_data, p_data + out_len, REAL(field_data)); } else { auto p_data = reinterpret_cast(res); -#pragma omp parallel for schedule(static, 512) if (out_len >= 1024) - for (int i = 0; i < out_len; ++i) { - REAL(field_data)[i] = p_data[i]; - } + std::copy(p_data, p_data + out_len, REAL(field_data)); } UNPROTECT(1); return R_NilValue; @@ -610,13 +602,10 @@ SEXP LGBM_BoosterUpdateOneIterCustom_R(SEXP handle, _AssertBoosterHandleNotNull(handle); int is_finished = 0; int int_len = Rf_asInteger(len); - std::vector tgrad(int_len), thess(int_len); -#pragma omp parallel for schedule(static, 512) if (int_len >= 1024) - for (int j = 0; j < int_len; ++j) { - tgrad[j] = static_cast(REAL(grad)[j]); - thess[j] = static_cast(REAL(hess)[j]); - } - CHECK_CALL(LGBM_BoosterUpdateOneIterCustom(R_ExternalPtrAddr(handle), tgrad.data(), thess.data(), &is_finished)); + std::unique_ptr tgrad(new float[int_len]), thess(new float[int_len]); + std::copy(REAL(grad), REAL(grad) + int_len, tgrad.get()); + std::copy(REAL(hess), REAL(hess) + int_len, thess.get()); + CHECK_CALL(LGBM_BoosterUpdateOneIterCustom(R_ExternalPtrAddr(handle), tgrad.get(), thess.get(), &is_finished)); return R_NilValue; R_API_END(); } @@ -1204,6 +1193,23 @@ SEXP LGBM_BoosterGetLoadedParam_R(SEXP handle) { R_API_END(); } +SEXP LGBM_GetMaxThreads_R(SEXP out) { + R_API_BEGIN(); + int num_threads; + CHECK_CALL(LGBM_GetMaxThreads(&num_threads)); + INTEGER(out)[0] = num_threads; + return R_NilValue; + R_API_END(); +} + +SEXP LGBM_SetMaxThreads_R(SEXP num_threads) { + R_API_BEGIN(); + int new_num_threads = Rf_asInteger(num_threads); + CHECK_CALL(LGBM_SetMaxThreads(new_num_threads)); + return R_NilValue; + R_API_END(); +} + // .Call() calls static const R_CallMethodDef CallEntries[] = { {"LGBM_HandleIsNull_R" , (DL_FUNC) &LGBM_HandleIsNull_R , 1}, @@ -1260,6 +1266,8 @@ static const R_CallMethodDef CallEntries[] = { {"LGBM_BoosterDumpModel_R" , (DL_FUNC) &LGBM_BoosterDumpModel_R , 3}, {"LGBM_NullBoosterHandleError_R" , (DL_FUNC) &LGBM_NullBoosterHandleError_R , 0}, {"LGBM_DumpParamAliases_R" , (DL_FUNC) &LGBM_DumpParamAliases_R , 0}, + {"LGBM_GetMaxThreads_R" , (DL_FUNC) &LGBM_GetMaxThreads_R , 1}, + {"LGBM_SetMaxThreads_R" , (DL_FUNC) &LGBM_SetMaxThreads_R , 1}, {NULL, NULL, 0} }; diff --git a/R-package/src/lightgbm_R.h b/R-package/src/lightgbm_R.h index 7141a06a207c..4f0407e8f2ec 100644 --- a/R-package/src/lightgbm_R.h +++ b/R-package/src/lightgbm_R.h @@ -850,4 +850,23 @@ LIGHTGBM_C_EXPORT SEXP LGBM_BoosterDumpModel_R( */ LIGHTGBM_C_EXPORT SEXP LGBM_DumpParamAliases_R(); +/*! +* \brief Get current maximum number of threads used by LightGBM routines in this process. +* \param[out] out current maximum number of threads used by LightGBM. -1 means defaulting to omp_get_num_threads(). +* \return R NULL value +*/ +LIGHTGBM_C_EXPORT SEXP LGBM_GetMaxThreads_R( + SEXP out +); + + +/*! +* \brief Set maximum number of threads used by LightGBM routines in this process. +* \param num_threads maximum number of threads used by LightGBM. -1 means defaulting to omp_get_num_threads(). +* \return R NULL value +*/ +LIGHTGBM_C_EXPORT SEXP LGBM_SetMaxThreads_R( + SEXP num_threads +); + #endif // LIGHTGBM_R_H_ diff --git a/R-package/tests/testthat.R b/R-package/tests/testthat.R index 4752952740b2..72a440c275b0 100644 --- a/R-package/tests/testthat.R +++ b/R-package/tests/testthat.R @@ -1,5 +1,5 @@ library(testthat) -library(lightgbm) +library(lightgbm) # nolint: [unused_import] test_check( package = "lightgbm" diff --git a/R-package/tests/testthat/helper.R b/R-package/tests/testthat/helper.R index 9c928c1f71d1..45edf40efbeb 100644 --- a/R-package/tests/testthat/helper.R +++ b/R-package/tests/testthat/helper.R @@ -11,6 +11,11 @@ # the check farm is a shared resource and will typically be running many checks simultaneously. # .LGB_MAX_THREADS <- 2L +setLGBMthreads(.LGB_MAX_THREADS) + +# control data.table parallelism +# ref: https://github.com/Rdatatable/data.table/issues/5658 +data.table::setDTthreads(1L) # by default, how much should results in tests be allowed to differ from hard-coded expected numbers? .LGB_NUMERIC_TOLERANCE <- 1e-6 diff --git a/R-package/tests/testthat/test_Predictor.R b/R-package/tests/testthat/test_Predictor.R index 90be1d08cf67..192171c915bf 100644 --- a/R-package/tests/testthat/test_Predictor.R +++ b/R-package/tests/testthat/test_Predictor.R @@ -17,16 +17,16 @@ test_that("Predictor$finalize() should not fail", { bst$save_model(filename = model_file) predictor <- Predictor$new(modelfile = model_file) - expect_true(lgb.is.Predictor(predictor)) + expect_true(.is_Predictor(predictor)) - expect_false(lgb.is.null.handle(predictor$.__enclos_env__$private$handle)) + expect_false(.is_null_handle(predictor$.__enclos_env__$private$handle)) predictor$finalize() - expect_true(lgb.is.null.handle(predictor$.__enclos_env__$private$handle)) + expect_true(.is_null_handle(predictor$.__enclos_env__$private$handle)) # calling finalize() a second time shouldn't cause any issues predictor$finalize() - expect_true(lgb.is.null.handle(predictor$.__enclos_env__$private$handle)) + expect_true(.is_null_handle(predictor$.__enclos_env__$private$handle)) }) test_that("predictions do not fail for integer input", { @@ -79,7 +79,7 @@ test_that("start_iteration works correctly", { , valids = list("test" = dtest) , early_stopping_rounds = 2L ) - expect_true(lgb.is.Booster(bst)) + expect_true(.is_Booster(bst)) pred1 <- predict(bst, newdata = test$data, type = "raw") pred_contrib1 <- predict(bst, test$data, type = "contrib") pred2 <- rep(0.0, length(pred1)) diff --git a/R-package/tests/testthat/test_basic.R b/R-package/tests/testthat/test_basic.R index 57c33c35dfee..75abd26dd152 100644 --- a/R-package/tests/testthat/test_basic.R +++ b/R-package/tests/testthat/test_basic.R @@ -1094,7 +1094,7 @@ test_that("lgb.train() works as expected with sparse features", { , nrounds = nrounds ) - expect_true(lgb.is.Booster(bst)) + expect_true(.is_Booster(bst)) expect_equal(bst$current_iter(), nrounds) parsed_model <- jsonlite::fromJSON(bst$dump_model()) expect_equal(parsed_model$objective, "binary sigmoid:1") @@ -1816,7 +1816,7 @@ test_that("lgb.train() supports non-ASCII feature names", { ) , colnames = feature_names ) - expect_true(lgb.is.Booster(bst)) + expect_true(.is_Booster(bst)) dumped_model <- jsonlite::fromJSON(bst$dump_model()) # UTF-8 strings are not well-supported on Windows @@ -2522,7 +2522,7 @@ test_that("lgb.train() fit on linearly-relatead data improves when using linear , params = params , valids = list("train" = dtrain) ) - expect_true(lgb.is.Booster(bst)) + expect_true(.is_Booster(bst)) dtrain <- .new_dataset() bst_linear <- lgb.train( @@ -2531,7 +2531,7 @@ test_that("lgb.train() fit on linearly-relatead data improves when using linear , params = utils::modifyList(params, list(linear_tree = TRUE)) , valids = list("train" = dtrain) ) - expect_true(lgb.is.Booster(bst_linear)) + expect_true(.is_Booster(bst_linear)) bst_last_mse <- bst$record_evals[["train"]][["l2"]][["eval"]][[10L]] bst_lin_last_mse <- bst_linear$record_evals[["train"]][["l2"]][["eval"]][[10L]] @@ -2599,7 +2599,7 @@ test_that("lgb.train() works with linear learners even if Dataset has missing va , params = params , valids = list("train" = dtrain) ) - expect_true(lgb.is.Booster(bst)) + expect_true(.is_Booster(bst)) dtrain <- .new_dataset() bst_linear <- lgb.train( @@ -2608,7 +2608,7 @@ test_that("lgb.train() works with linear learners even if Dataset has missing va , params = utils::modifyList(params, list(linear_tree = TRUE)) , valids = list("train" = dtrain) ) - expect_true(lgb.is.Booster(bst_linear)) + expect_true(.is_Booster(bst_linear)) bst_last_mse <- bst$record_evals[["train"]][["l2"]][["eval"]][[10L]] bst_lin_last_mse <- bst_linear$record_evals[["train"]][["l2"]][["eval"]][[10L]] @@ -2649,7 +2649,7 @@ test_that("lgb.train() works with linear learners, bagging, and a Dataset that h , params = params , valids = list("train" = dtrain) ) - expect_true(lgb.is.Booster(bst)) + expect_true(.is_Booster(bst)) dtrain <- .new_dataset() bst_linear <- lgb.train( @@ -2658,7 +2658,7 @@ test_that("lgb.train() works with linear learners, bagging, and a Dataset that h , params = utils::modifyList(params, list(linear_tree = TRUE)) , valids = list("train" = dtrain) ) - expect_true(lgb.is.Booster(bst_linear)) + expect_true(.is_Booster(bst_linear)) bst_last_mse <- bst$record_evals[["train"]][["l2"]][["eval"]][[10L]] bst_lin_last_mse <- bst_linear$record_evals[["train"]][["l2"]][["eval"]][[10L]] @@ -2699,7 +2699,7 @@ test_that("lgb.train() works with linear learners and data where a feature has o , nrounds = 10L , params = utils::modifyList(params, list(linear_tree = TRUE)) ) - expect_true(lgb.is.Booster(bst_linear)) + expect_true(.is_Booster(bst_linear)) }) test_that("lgb.train() works with linear learners when Dataset has categorical features", { @@ -2732,7 +2732,7 @@ test_that("lgb.train() works with linear learners when Dataset has categorical f , params = params , valids = list("train" = dtrain) ) - expect_true(lgb.is.Booster(bst)) + expect_true(.is_Booster(bst)) dtrain <- .new_dataset() bst_linear <- lgb.train( @@ -2741,7 +2741,7 @@ test_that("lgb.train() works with linear learners when Dataset has categorical f , params = utils::modifyList(params, list(linear_tree = TRUE)) , valids = list("train" = dtrain) ) - expect_true(lgb.is.Booster(bst_linear)) + expect_true(.is_Booster(bst_linear)) bst_last_mse <- bst$record_evals[["train"]][["l2"]][["eval"]][[10L]] bst_lin_last_mse <- bst_linear$record_evals[["train"]][["l2"]][["eval"]][[10L]] @@ -3805,3 +3805,26 @@ test_that("lightgbm() correctly sets objective when passing lgb.Dataset as input ) expect_equal(model$params$objective, "regression") }) + +test_that("Evaluation metrics aren't printed as a single-element vector", { + log_txt <- capture_output({ + data(mtcars) + y <- mtcars$mpg + x <- as.matrix(mtcars[, -1L]) + cv_result <- lgb.cv( + data = lgb.Dataset(x, label = y) + , params = list( + objective = "regression" + , metric = "l2" + , min_data_in_leaf = 5L + , max_depth = 3L + , num_threads = .LGB_MAX_THREADS + ) + , nrounds = 2L + , nfold = 3L + , verbose = 1L + , eval_train_metric = TRUE + ) + }) + expect_false(grepl("[1] \"[1]", log_txt, fixed = TRUE)) +}) diff --git a/R-package/tests/testthat/test_dataset.R b/R-package/tests/testthat/test_dataset.R index cf68ce9262a3..a8585baa2621 100644 --- a/R-package/tests/testthat/test_dataset.R +++ b/R-package/tests/testthat/test_dataset.R @@ -183,7 +183,9 @@ test_that("lgb.Dataset: colnames", { colnames(dtest) <- "asdf" }) new_names <- make.names(seq_len(ncol(test_data))) - expect_silent(colnames(dtest) <- new_names) + expect_silent({ + colnames(dtest) <- new_names + }) expect_equal(colnames(dtest), new_names) }) @@ -204,7 +206,7 @@ test_that("lgb.Dataset: Dataset should be able to construct from matrix and retu , rawData , nrow(rawData) , ncol(rawData) - , lightgbm:::lgb.params2str(params = list()) + , lightgbm:::.params2str(params = list()) , ref_handle ) expect_true(methods::is(handle, "externalptr")) @@ -320,7 +322,7 @@ test_that("Dataset$update_parameters() does nothing for empty inputs", { res <- ds$update_params( params = list() ) - expect_true(lgb.is.Dataset(res)) + expect_true(.is_Dataset(res)) new_params <- ds$get_params() expect_identical(new_params, initial_params) @@ -341,7 +343,7 @@ test_that("Dataset$update_params() works correctly for recognized Dataset parame res <- ds$update_params( params = new_params ) - expect_true(lgb.is.Dataset(res)) + expect_true(.is_Dataset(res)) updated_params <- ds$get_params() for (param_name in names(new_params)) { @@ -354,17 +356,17 @@ test_that("Dataset$finalize() should not fail on an already-finalized Dataset", data = test_data , label = test_label ) - expect_true(lgb.is.null.handle(dtest$.__enclos_env__$private$handle)) + expect_true(.is_null_handle(dtest$.__enclos_env__$private$handle)) dtest$construct() - expect_false(lgb.is.null.handle(dtest$.__enclos_env__$private$handle)) + expect_false(.is_null_handle(dtest$.__enclos_env__$private$handle)) dtest$finalize() - expect_true(lgb.is.null.handle(dtest$.__enclos_env__$private$handle)) + expect_true(.is_null_handle(dtest$.__enclos_env__$private$handle)) # calling finalize() a second time shouldn't cause any issues dtest$finalize() - expect_true(lgb.is.null.handle(dtest$.__enclos_env__$private$handle)) + expect_true(.is_null_handle(dtest$.__enclos_env__$private$handle)) }) test_that("lgb.Dataset: should be able to run lgb.train() immediately after using lgb.Dataset() on a file", { @@ -399,7 +401,7 @@ test_that("lgb.Dataset: should be able to run lgb.train() immediately after usin , data = dtest_read_in ) - expect_true(lgb.is.Booster(x = bst)) + expect_true(.is_Booster(x = bst)) }) test_that("lgb.Dataset: should be able to run lgb.cv() immediately after using lgb.Dataset() on a file", { diff --git a/R-package/tests/testthat/test_learning_to_rank.R b/R-package/tests/testthat/test_learning_to_rank.R index 6868794cf8ec..e99aff44ceb3 100644 --- a/R-package/tests/testthat/test_learning_to_rank.R +++ b/R-package/tests/testthat/test_learning_to_rank.R @@ -25,7 +25,7 @@ test_that("learning-to-rank with lgb.train() works as expected", { , data = dtrain , nrounds = 10L ) - expect_true(lgb.is.Booster(model)) + expect_true(.is_Booster(model)) dumped_model <- jsonlite::fromJSON( model$dump_model() @@ -37,7 +37,8 @@ test_that("learning-to-rank with lgb.train() works as expected", { eval_results <- model$eval_train() expect_equal(length(eval_results), length(eval_names)) for (result in eval_results) { - expect_true(result[["value"]] > 0.0 && result[["value"]] < 1.0) + expect_true(result[["value"]] > 0.0) + expect_true(result[["value"]] < 1.0) expect_true(result[["higher_better"]]) expect_identical(result[["data_name"]], "training") } @@ -104,8 +105,10 @@ test_that("learning-to-rank with lgb.cv() works as expected", { # check that best score and iter make sense (0.0 < nDCG < 1.0) best_iter <- cv_bst$best_iter best_score <- cv_bst$best_score - expect_true(best_iter > 0L && best_iter <= nrounds) - expect_true(best_score > 0.0 && best_score < 1.0) + expect_true(best_iter > 0L) + expect_true(best_iter <= nrounds) + expect_true(best_score > 0.0) + expect_true(best_score < 1.0) expect_true(abs(best_score - 0.75) < .LGB_NUMERIC_TOLERANCE) # best_score should be set for the first metric diff --git a/R-package/tests/testthat/test_lgb.Booster.R b/R-package/tests/testthat/test_lgb.Booster.R index 5f398f1c081d..e6b0e8abda64 100644 --- a/R-package/tests/testthat/test_lgb.Booster.R +++ b/R-package/tests/testthat/test_lgb.Booster.R @@ -11,16 +11,16 @@ test_that("Booster$finalize() should not fail", { , verbose = .LGB_VERBOSITY , nrounds = 3L ) - expect_true(lgb.is.Booster(bst)) + expect_true(.is_Booster(bst)) - expect_false(lgb.is.null.handle(bst$.__enclos_env__$private$handle)) + expect_false(.is_null_handle(bst$.__enclos_env__$private$handle)) bst$finalize() - expect_true(lgb.is.null.handle(bst$.__enclos_env__$private$handle)) + expect_true(.is_null_handle(bst$.__enclos_env__$private$handle)) # calling finalize() a second time shouldn't cause any issues bst$finalize() - expect_true(lgb.is.null.handle(bst$.__enclos_env__$private$handle)) + expect_true(.is_null_handle(bst$.__enclos_env__$private$handle)) }) test_that("lgb.get.eval.result() should throw an informative error if booster is not an lgb.Booster", { @@ -188,7 +188,7 @@ test_that("Loading a Booster from a text file works", { , params = params , nrounds = 2L ) - expect_true(lgb.is.Booster(bst)) + expect_true(.is_Booster(bst)) pred <- predict(bst, test$data) model_file <- tempfile(fileext = ".model") @@ -232,7 +232,7 @@ test_that("boosters with linear models at leaves can be written to text file and , params = params , verbose = .LGB_VERBOSITY ) - expect_true(lgb.is.Booster(bst)) + expect_true(.is_Booster(bst)) # save predictions, then write the model to a file and destroy it in R preds <- predict(bst, X) @@ -269,7 +269,7 @@ test_that("Loading a Booster from a string works", { ) , nrounds = 2L ) - expect_true(lgb.is.Booster(bst)) + expect_true(.is_Booster(bst)) pred <- predict(bst, test$data) model_string <- bst$save_model_to_string() @@ -376,7 +376,7 @@ test_that("If a string and a file are both passed to lgb.load() the file is used ) , nrounds = 2L ) - expect_true(lgb.is.Booster(bst)) + expect_true(.is_Booster(bst)) pred <- predict(bst, test$data) model_file <- tempfile(fileext = ".model") @@ -411,7 +411,7 @@ test_that("Creating a Booster from a Dataset should work", { ), train_set = dtrain ) - expect_true(lgb.is.Booster(bst)) + expect_true(.is_Booster(bst)) expect_equal(bst$current_iter(), 0L) expect_true(is.na(bst$best_score)) expect_true(all(bst$predict(agaricus.train$data) == 0.5)) @@ -446,10 +446,10 @@ test_that("Creating a Booster from a Dataset with an existing predictor should w , num_threads = .LGB_MAX_THREADS ) ) - expect_true(lgb.is.Booster(bst)) + expect_true(.is_Booster(bst)) expect_equal(bst$current_iter(), nrounds) expect_equal(bst$eval_train()[[1L]][["value"]], 0.1115352) - expect_true(lgb.is.Booster(bst_from_ds)) + expect_true(.is_Booster(bst_from_ds)) expect_equal(bst_from_ds$current_iter(), nrounds) expect_equal(bst_from_ds$eval_train()[[1L]][["value"]], 5.65704892) dumped_model <- jsonlite::fromJSON(bst$dump_model()) @@ -531,7 +531,7 @@ test_that("Booster$rollback_one_iter() should work as expected", { , nrounds = nrounds ) expect_equal(bst$current_iter(), nrounds) - expect_true(lgb.is.Booster(bst)) + expect_true(.is_Booster(bst)) logloss <- bst$eval_train()[[1L]][["value"]] expect_equal(logloss, 0.01904786) @@ -539,7 +539,7 @@ test_that("Booster$rollback_one_iter() should work as expected", { # rollback_one_iter() should return a booster and modify the original # booster in place - expect_true(lgb.is.Booster(x)) + expect_true(.is_Booster(x)) expect_equal(bst$current_iter(), nrounds - 1L) # score should now come from the model as of 4 iterations @@ -565,7 +565,7 @@ test_that("Booster$update() passing a train_set works as expected", { ) , nrounds = nrounds ) - expect_true(lgb.is.Booster(bst)) + expect_true(.is_Booster(bst)) expect_equal(bst$current_iter(), nrounds) bst$update( train_set = Dataset$new( @@ -574,7 +574,7 @@ test_that("Booster$update() passing a train_set works as expected", { , params = list(verbose = .LGB_VERBOSITY) ) ) - expect_true(lgb.is.Booster(bst)) + expect_true(.is_Booster(bst)) expect_equal(bst$current_iter(), nrounds + 1L) # train with 3 rounds directly @@ -590,7 +590,7 @@ test_that("Booster$update() passing a train_set works as expected", { ) , nrounds = nrounds + 1L ) - expect_true(lgb.is.Booster(bst2)) + expect_true(.is_Booster(bst2)) expect_equal(bst2$current_iter(), nrounds + 1L) # model with 2 rounds + 1 update should be identical to 3 rounds @@ -716,7 +716,7 @@ test_that("Saving a model with different feature importance types works", { ) , nrounds = 2L ) - expect_true(lgb.is.Booster(bst)) + expect_true(.is_Booster(bst)) .feat_importance_from_string <- function(model_string) { file_lines <- strsplit(model_string, "\n", fixed = TRUE)[[1L]] @@ -772,7 +772,7 @@ test_that("Saving a model with unknown importance type fails", { ) , nrounds = 2L ) - expect_true(lgb.is.Booster(bst)) + expect_true(.is_Booster(bst)) UNSUPPORTED_IMPORTANCE <- 2L expect_error({ @@ -1252,43 +1252,6 @@ test_that("lgb.cv() correctly handles passing through params to the model file", }) -test_that("params (including dataset params) should be stored in .rds file for Booster", { - data(agaricus.train, package = "lightgbm") - dtrain <- lgb.Dataset( - agaricus.train$data - , label = agaricus.train$label - , params = list( - max_bin = 17L - ) - ) - params <- list( - objective = "binary" - , max_depth = 4L - , bagging_fraction = 0.8 - , verbose = .LGB_VERBOSITY - , num_threads = .LGB_MAX_THREADS - ) - bst <- Booster$new( - params = params - , train_set = dtrain - ) - bst_file <- tempfile(fileext = ".rds") - expect_warning(saveRDS.lgb.Booster(bst, file = bst_file)) - - expect_warning(bst_from_file <- readRDS.lgb.Booster(file = bst_file)) - expect_identical( - bst_from_file$params - , list( - objective = "binary" - , max_depth = 4L - , bagging_fraction = 0.8 - , verbose = .LGB_VERBOSITY - , num_threads = .LGB_MAX_THREADS - , max_bin = 17L - ) - ) -}) - test_that("params (including dataset params) should be stored in .rds file for Booster", { data(agaricus.train, package = "lightgbm") dtrain <- lgb.Dataset( @@ -1348,46 +1311,6 @@ test_that("Handle is automatically restored when calling predict", { expect_equal(pred_before, pred_after) }) -test_that("boosters with linear models at leaves work with saveRDS.lgb.Booster and readRDS.lgb.Booster", { - X <- matrix(rnorm(100L), ncol = 1L) - labels <- 2L * X + runif(nrow(X), 0L, 0.1) - dtrain <- lgb.Dataset( - data = X - , label = labels - ) - - params <- list( - objective = "regression" - , verbose = .LGB_VERBOSITY - , metric = "mse" - , seed = 0L - , num_leaves = 2L - , num_threads = .LGB_MAX_THREADS - ) - - bst <- lgb.train( - data = dtrain - , nrounds = 10L - , params = params - ) - expect_true(lgb.is.Booster(bst)) - - # save predictions, then write the model to a file and destroy it in R - preds <- predict(bst, X) - model_file <- tempfile(fileext = ".rds") - expect_warning(saveRDS.lgb.Booster(bst, file = model_file)) - bst$finalize() - expect_null(bst$.__enclos_env__$private$handle) - rm(bst) - - # load the booster and make predictions...should be the same - expect_warning({ - bst2 <- readRDS.lgb.Booster(file = model_file) - }) - preds2 <- predict(bst2, X) - expect_identical(preds, preds2) -}) - test_that("boosters with linear models at leaves can be written to RDS and re-loaded successfully", { X <- matrix(rnorm(100L), ncol = 1L) labels <- 2L * X + runif(nrow(X), 0L, 0.1) @@ -1410,7 +1333,7 @@ test_that("boosters with linear models at leaves can be written to RDS and re-lo , nrounds = 10L , params = params ) - expect_true(lgb.is.Booster(bst)) + expect_true(.is_Booster(bst)) # save predictions, then write the model to a file and destroy it in R preds <- predict(bst, X) diff --git a/R-package/tests/testthat/test_multithreading.R b/R-package/tests/testthat/test_multithreading.R new file mode 100644 index 000000000000..e2f3169627a2 --- /dev/null +++ b/R-package/tests/testthat/test_multithreading.R @@ -0,0 +1,16 @@ +test_that("getLGBMthreads() and setLGBMthreads() work as expected", { + # works with integer input + ret <- setLGBMthreads(2L) + expect_null(ret) + expect_equal(getLGBMthreads(), 2L) + + # works with float input + ret <- setLGBMthreads(1.0) + expect_null(ret) + expect_equal(getLGBMthreads(), 1L) + + # setting to any negative number sets max threads to -1 + ret <- setLGBMthreads(-312L) + expect_null(ret) + expect_equal(getLGBMthreads(), -1L) +}) diff --git a/R-package/tests/testthat/test_utils.R b/R-package/tests/testthat/test_utils.R index 4ab05e075ae3..898aed9b0915 100644 --- a/R-package/tests/testthat/test_utils.R +++ b/R-package/tests/testthat/test_utils.R @@ -1,12 +1,12 @@ -test_that("lgb.params2str() works as expected for empty lists", { - out_str <- lgb.params2str( +test_that(".params2str() works as expected for empty lists", { + out_str <- .params2str( params = list() ) expect_identical(class(out_str), "character") expect_equal(out_str, "") }) -test_that("lgb.params2str() works as expected for a key in params with multiple different-length elements", { +test_that(".params2str() works as expected for a key in params with multiple different-length elements", { metrics <- c("a", "ab", "abc", "abcdefg") params <- list( objective = "magic" @@ -14,7 +14,7 @@ test_that("lgb.params2str() works as expected for a key in params with multiple , nrounds = 10L , learning_rate = 0.0000001 ) - out_str <- lgb.params2str( + out_str <- .params2str( params = params ) expect_identical(class(out_str), "character") @@ -24,8 +24,8 @@ test_that("lgb.params2str() works as expected for a key in params with multiple ) }) -test_that("lgb.params2str() passes through duplicated params", { - out_str <- lgb.params2str( +test_that(".params2str() passes through duplicated params", { + out_str <- .params2str( params = list( objective = "regression" , bagging_fraction = 0.8 @@ -35,8 +35,8 @@ test_that("lgb.params2str() passes through duplicated params", { expect_equal(out_str, "objective=regression bagging_fraction=0.8 bagging_fraction=0.5") }) -test_that("lgb.check.eval works as expected with no metric", { - params <- lgb.check.eval( +test_that(".check_eval works as expected with no metric", { + params <- .check_eval( params = list(device = "cpu") , eval = "binary_error" ) @@ -44,8 +44,8 @@ test_that("lgb.check.eval works as expected with no metric", { expect_identical(params[["metric"]], list("binary_error")) }) -test_that("lgb.check.eval adds eval to metric in params", { - params <- lgb.check.eval( +test_that(".check_eval adds eval to metric in params", { + params <- .check_eval( params = list(metric = "auc") , eval = "binary_error" ) @@ -53,8 +53,8 @@ test_that("lgb.check.eval adds eval to metric in params", { expect_identical(params[["metric"]], list("auc", "binary_error")) }) -test_that("lgb.check.eval adds eval to metric in params if two evaluation names are provided", { - params <- lgb.check.eval( +test_that(".check_eval adds eval to metric in params if two evaluation names are provided", { + params <- .check_eval( params = list(metric = "auc") , eval = c("binary_error", "binary_logloss") ) @@ -62,8 +62,8 @@ test_that("lgb.check.eval adds eval to metric in params if two evaluation names expect_identical(params[["metric"]], list("auc", "binary_error", "binary_logloss")) }) -test_that("lgb.check.eval adds eval to metric in params if a list is provided", { - params <- lgb.check.eval( +test_that(".check_eval adds eval to metric in params if a list is provided", { + params <- .check_eval( params = list(metric = "auc") , eval = list("binary_error", "binary_logloss") ) @@ -71,8 +71,8 @@ test_that("lgb.check.eval adds eval to metric in params if a list is provided", expect_identical(params[["metric"]], list("auc", "binary_error", "binary_logloss")) }) -test_that("lgb.check.eval drops duplicate metrics and preserves order", { - params <- lgb.check.eval( +test_that(".check_eval drops duplicate metrics and preserves order", { + params <- .check_eval( params = list(metric = "l1") , eval = list("l2", "rmse", "l1", "rmse") ) @@ -80,9 +80,9 @@ test_that("lgb.check.eval drops duplicate metrics and preserves order", { expect_identical(params[["metric"]], list("l1", "l2", "rmse")) }) -test_that("lgb.check.wrapper_param() uses passed-in keyword arg if no alias found in params", { +test_that(".check_wrapper_param() uses passed-in keyword arg if no alias found in params", { kwarg_val <- sample(seq_len(100L), size = 1L) - params <- lgb.check.wrapper_param( + params <- .check_wrapper_param( main_param_name = "num_iterations" , params = list() , alternative_kwarg_value = kwarg_val @@ -90,10 +90,10 @@ test_that("lgb.check.wrapper_param() uses passed-in keyword arg if no alias foun expect_equal(params[["num_iterations"]], kwarg_val) }) -test_that("lgb.check.wrapper_param() prefers main parameter to alias and keyword arg", { +test_that(".check_wrapper_param() prefers main parameter to alias and keyword arg", { num_iterations <- sample(seq_len(100L), size = 1L) kwarg_val <- sample(seq_len(100L), size = 1L) - params <- lgb.check.wrapper_param( + params <- .check_wrapper_param( main_param_name = "num_iterations" , params = list( num_iterations = num_iterations @@ -108,11 +108,11 @@ test_that("lgb.check.wrapper_param() prefers main parameter to alias and keyword expect_identical(params, list(num_iterations = num_iterations)) }) -test_that("lgb.check.wrapper_param() prefers alias to keyword arg", { +test_that(".check_wrapper_param() prefers alias to keyword arg", { n_estimators <- sample(seq_len(100L), size = 1L) num_tree <- sample(seq_len(100L), size = 1L) kwarg_val <- sample(seq_len(100L), size = 1L) - params <- lgb.check.wrapper_param( + params <- .check_wrapper_param( main_param_name = "num_iterations" , params = list( num_tree = num_tree @@ -124,7 +124,7 @@ test_that("lgb.check.wrapper_param() prefers alias to keyword arg", { expect_identical(params, list(num_iterations = num_tree)) # switching the order shouldn't switch which one is chosen - params2 <- lgb.check.wrapper_param( + params2 <- .check_wrapper_param( main_param_name = "num_iterations" , params = list( n_estimators = n_estimators @@ -136,14 +136,14 @@ test_that("lgb.check.wrapper_param() prefers alias to keyword arg", { expect_identical(params2, list(num_iterations = num_tree)) }) -test_that("lgb.equal.or.both.null produces expected results", { - expect_true(lgb.equal.or.both.null(NULL, NULL)) - expect_false(lgb.equal.or.both.null(1.0, NULL)) - expect_false(lgb.equal.or.both.null(NULL, 1.0)) - expect_true(lgb.equal.or.both.null(1.0, 1.0)) - expect_true(lgb.equal.or.both.null(1.0, 1L)) - expect_false(lgb.equal.or.both.null(NA, NULL)) - expect_false(lgb.equal.or.both.null(NULL, NA)) - expect_false(lgb.equal.or.both.null(10.0, 1L)) - expect_true(lgb.equal.or.both.null(0L, 0L)) +test_that(".equal_or_both_null produces expected results", { + expect_true(.equal_or_both_null(NULL, NULL)) + expect_false(.equal_or_both_null(1.0, NULL)) + expect_false(.equal_or_both_null(NULL, 1.0)) + expect_true(.equal_or_both_null(1.0, 1.0)) + expect_true(.equal_or_both_null(1.0, 1L)) + expect_false(.equal_or_both_null(NA, NULL)) + expect_false(.equal_or_both_null(NULL, NA)) + expect_false(.equal_or_both_null(10.0, 1L)) + expect_true(.equal_or_both_null(0L, 0L)) }) diff --git a/R-package/vignettes/basic_walkthrough.Rmd b/R-package/vignettes/basic_walkthrough.Rmd index d7aaf676f386..01e2410d501e 100644 --- a/R-package/vignettes/basic_walkthrough.Rmd +++ b/R-package/vignettes/basic_walkthrough.Rmd @@ -3,10 +3,14 @@ title: "Basic Walkthrough" description: > This vignette describes how to train a LightGBM model for binary classification. -output: rmarkdown::html_vignette +output: + markdown::html_format: + options: + toc: true + number_sections: true vignette: > %\VignetteIndexEntry{Basic Walkthrough} - %\VignetteEngine{knitr::rmarkdown} + %\VignetteEngine{knitr::knitr} %\VignetteEncoding{UTF-8} --- @@ -23,10 +27,16 @@ knitr::opts_chunk$set( Welcome to the world of [LightGBM](https://lightgbm.readthedocs.io/en/latest/), a highly efficient gradient boosting implementation (Ke et al. 2017). -```{r setup} +```{r} library(lightgbm) ``` +```{r, include=FALSE} +# limit number of threads used, to be respectful of CRAN's resources when it checks this vignette +data.table::setDTthreads(1L) +setLGBMthreads(2L) +``` + This vignette will guide you through its basic usage. It will show how to build a simple binary classification model based on a subset of the `bank` dataset (Moro, Cortez, and Rita 2014). You will use the two input features "age" and "balance" to predict whether a client has subscribed a term deposit. ## The dataset diff --git a/README.md b/README.md index f6f4e8c570e0..3b3fe40790db 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,6 @@ Next you may want to read: - [**Features**](https://github.com/microsoft/LightGBM/blob/master/docs/Features.rst) and algorithms supported by LightGBM. - [**Parameters**](https://github.com/microsoft/LightGBM/blob/master/docs/Parameters.rst) is an exhaustive list of customization you can make. - [**Distributed Learning**](https://github.com/microsoft/LightGBM/blob/master/docs/Parallel-Learning-Guide.rst) and [**GPU Learning**](https://github.com/microsoft/LightGBM/blob/master/docs/GPU-Tutorial.rst) can speed up computation. -- [**Laurae++ interactive documentation**](https://sites.google.com/view/lauraepp/parameters) is a detailed guide for hyperparameters. - [**FLAML**](https://www.microsoft.com/en-us/research/project/fast-and-lightweight-automl-for-large-scale-data/articles/flaml-a-fast-and-lightweight-automl-library/) provides automated tuning for LightGBM ([code examples](https://microsoft.github.io/FLAML/docs/Examples/AutoML-for-LightGBM/)). - [**Optuna Hyperparameter Tuner**](https://medium.com/optuna/lightgbm-tuner-new-optuna-integration-for-hyperparameter-optimization-8b7095e99258) provides automated tuning for LightGBM hyperparameters ([code examples](https://github.com/optuna/optuna-examples/blob/main/lightgbm/lightgbm_tuner_simple.py)). - [**Understanding LightGBM Parameters (and How to Tune Them using Neptune)**](https://neptune.ai/blog/lightgbm-parameters-guide). @@ -64,6 +63,8 @@ External (Unofficial) Repositories Projects listed here offer alternative ways to use LightGBM. They are not maintained or officially endorsed by the `LightGBM` development team. +LightGBMLSS (An extension of LightGBM to probabilistic modelling from which prediction intervals and quantiles can be derived): https://github.com/StatMixedML/LightGBMLSS + FLAML (AutoML library for hyperparameter optimization): https://github.com/microsoft/FLAML Optuna (hyperparameter optimization framework): https://github.com/optuna/optuna diff --git a/VERSION.txt b/VERSION.txt index 1f06da0058c9..67dae3929837 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -1 +1 @@ -4.1.0.99 +4.2.0.99 diff --git a/build-cran-package.sh b/build-cran-package.sh index 6558afcf77ad..9fa0c5877085 100755 --- a/build-cran-package.sh +++ b/build-cran-package.sh @@ -132,7 +132,7 @@ cd "${TEMP_R_DIR}" using_windows_and_r3=$( Rscript -e 'cat(.Platform$OS.type == "windows" && R.version[["major"]] < 4)' ) - if [[ ${using_windows_and_r3} == "TRUE" ]]; then + if test "${using_windows_and_r3}" = "TRUE"; then LGB_CXX_STD="C++11" fi sed -i.bak -e "s/~~CXXSTD~~/${LGB_CXX_STD}/" DESCRIPTION @@ -227,6 +227,7 @@ if ${BUILD_VIGNETTES} ; then rm -f ./lightgbm/src/network/*.o rm -f ./lightgbm/src/objective/*.o rm -f ./lightgbm/src/treelearner/*.o + rm -f ./lightgbm/src/utils/*.o echo "re-tarring ${TARBALL_NAME}" tar \ diff --git a/docs/.linkcheckerrc b/docs/.linkcheckerrc index 96fdcbd08157..003d8699a875 100644 --- a/docs/.linkcheckerrc +++ b/docs/.linkcheckerrc @@ -11,7 +11,7 @@ ignore= http.*amd.com/.* https.*dl.acm.org/doi/.* https.*tandfonline.com/.* -ignorewarnings=http-robots-denied,https-certificate-error +ignorewarnings=http-redirected,http-robots-denied,https-certificate-error checkextern=1 [output] diff --git a/docs/Experiments.rst b/docs/Experiments.rst index ede19cf3a2eb..4440a2c0ccae 100644 --- a/docs/Experiments.rst +++ b/docs/Experiments.rst @@ -18,19 +18,19 @@ Data We used 5 datasets to conduct our comparison experiments. Details of data are listed in the following table: -+-----------+-----------------------+------------------------------------------------------------------------+-------------+----------+----------------------------------------------+ -| Data | Task | Link | #Train\_Set | #Feature | Comments | -+===========+=======================+========================================================================+=============+==========+==============================================+ -| Higgs | Binary classification | `link `__ | 10,500,000 | 28 | last 500,000 samples were used as test set | -+-----------+-----------------------+------------------------------------------------------------------------+-------------+----------+----------------------------------------------+ -| Yahoo LTR | Learning to rank | `link `__ | 473,134 | 700 | set1.train as train, set1.test as test | -+-----------+-----------------------+------------------------------------------------------------------------+-------------+----------+----------------------------------------------+ -| MS LTR | Learning to rank | `link `__ | 2,270,296 | 137 | {S1,S2,S3} as train set, {S5} as test set | -+-----------+-----------------------+------------------------------------------------------------------------+-------------+----------+----------------------------------------------+ -| Expo | Binary classification | `link `__ | 11,000,000 | 700 | last 1,000,000 samples were used as test set | -+-----------+-----------------------+------------------------------------------------------------------------+-------------+----------+----------------------------------------------+ -| Allstate | Binary classification | `link `__ | 13,184,290 | 4228 | last 1,000,000 samples were used as test set | -+-----------+-----------------------+------------------------------------------------------------------------+-------------+----------+----------------------------------------------+ ++-----------+-----------------------+---------------------------------------------------------------------------------+-------------+----------+----------------------------------------------+ +| Data | Task | Link | #Train\_Set | #Feature | Comments | ++===========+=======================+=================================================================================+=============+==========+==============================================+ +| Higgs | Binary classification | `link `__ | 10,500,000 | 28 | last 500,000 samples were used as test set | ++-----------+-----------------------+---------------------------------------------------------------------------------+-------------+----------+----------------------------------------------+ +| Yahoo LTR | Learning to rank | `link `__ | 473,134 | 700 | set1.train as train, set1.test as test | ++-----------+-----------------------+---------------------------------------------------------------------------------+-------------+----------+----------------------------------------------+ +| MS LTR | Learning to rank | `link `__ | 2,270,296 | 137 | {S1,S2,S3} as train set, {S5} as test set | ++-----------+-----------------------+---------------------------------------------------------------------------------+-------------+----------+----------------------------------------------+ +| Expo | Binary classification | `link `__ | 11,000,000 | 700 | last 1,000,000 samples were used as test set | ++-----------+-----------------------+---------------------------------------------------------------------------------+-------------+----------+----------------------------------------------+ +| Allstate | Binary classification | `link `__ | 13,184,290 | 4228 | last 1,000,000 samples were used as test set | ++-----------+-----------------------+---------------------------------------------------------------------------------+-------------+----------+----------------------------------------------+ Environment ^^^^^^^^^^^ diff --git a/docs/FAQ.rst b/docs/FAQ.rst index 3b06761114d5..43999931ca07 100644 --- a/docs/FAQ.rst +++ b/docs/FAQ.rst @@ -11,44 +11,15 @@ LightGBM FAQ ------ -Critical Issues -=============== +Please post questions, feature requests, and bug reports at https://github.com/microsoft/LightGBM/issues. -A **critical issue** could be a *crash*, *prediction error*, *nonsense output*, or something else requiring immediate attention. +This project is mostly maintained by volunteers, so please be patient. +If your request is time-sensitive or more than a month goes by without a response, please tag the maintainers below for help. -Please post such an issue in the `Microsoft/LightGBM repository `__. - -You may also ping a member of the core team according to the relevant area of expertise by mentioning them with the arabase (@) symbol: - -- `@guolinke `__ **Guolin Ke** (C++ code / R-package / Python-package) -- `@chivee `__ **Qiwei Ye** (C++ code / Python-package) -- `@shiyu1994 `__ **Yu Shi** (C++ code / Python-package) -- `@tongwu-msft` **Tong Wu** (C++ code / Python-package) -- `@hzy46 `__ **Zhiyuan He** (C++ code / Python-package) -- `@btrotta `__ **Belinda Trotta** (C++ code) -- `@Laurae2 `__ **Damien Soukhavong** (R-package) -- `@jameslamb `__ **James Lamb** (R-package / Dask-package) -- `@jmoralez `__ **José Morales** (Dask-package) -- `@wxchan `__ **Wenxuan Chen** (Python-package) -- `@henry0312 `__ **Tsukasa Omoto** (Python-package) -- `@StrikerRUS `__ **Nikita Titov** (Python-package) -- `@huanzhang12 `__ **Huan Zhang** (GPU support) - -Please include as much of the following information as possible when submitting a critical issue: - -- Is it reproducible on CLI (command line interface), R, and/or Python? - -- Is it specific to a wrapper? (R or Python?) - -- Is it specific to the compiler? (gcc or Clang version? MinGW or Visual Studio version?) - -- Is it specific to your Operating System? (Windows? Linux? macOS?) - -- Are you able to reproduce this issue with a simple case? - -- Does the issue persist after removing all optimization flags and compiling LightGBM in debug mode? - -When submitting issues, please keep in mind that this is largely a volunteer effort, and we may not be available 24/7 to provide support. +- `@guolinke `__ **Guolin Ke** +- `@shiyu1994 `__ **Yu Shi** +- `@jameslamb `__ **James Lamb** +- `@jmoralez `__ **José Morales** -------------- @@ -62,7 +33,7 @@ General LightGBM Questions 1. Where do I find more details about LightGBM parameters? ---------------------------------------------------------- -Take a look at `Parameters <./Parameters.rst>`__ and the `Laurae++/Parameters `__ website. +Take a look at `Parameters <./Parameters.rst>`__. 2. On datasets with millions of features, training does not start (or starts after a very long time). ----------------------------------------------------------------------------------------------------- @@ -263,7 +234,7 @@ As of LightGBM v4.0.0, ``setinfo()`` has been replaced by a new method, ``set_fi 3. ``error in data.table::data.table()...argument 2 is NULL`` ------------------------------------------------------------- -If you are experiencing this error when running ``lightgbm``, you may be facing the same issue reported in `#2715 `_ and later in `#2989 `_. We have seen that some in some situations, using ``data.table`` 1.11.x results in this error. To get around this, you can upgrade your version of ``data.table`` to at least version 1.12.0. +If you are experiencing this error when running ``lightgbm``, you may be facing the same issue reported in `#2715 `_ and later in `#2989 `_. We have seen that in some situations, using ``data.table`` 1.11.x results in this error. To get around this, you can upgrade your version of ``data.table`` to at least version 1.12.0. ------ @@ -289,7 +260,7 @@ Python-package This error should be solved in latest version. If you still meet this error, try to remove ``lightgbm.egg-info`` folder in your Python-package and reinstall, -or check `this thread on stackoverflow `__. +or check `this thread on stackoverflow `__. 2. Error messages: ``Cannot ... before construct dataset``. ----------------------------------------------------------- diff --git a/docs/Features.rst b/docs/Features.rst index a7db86ec2935..89b56646588f 100644 --- a/docs/Features.rst +++ b/docs/Features.rst @@ -291,7 +291,7 @@ References .. _On Grouping for Maximum Homogeneity: https://www.tandfonline.com/doi/abs/10.1080/01621459.1958.10501479 -.. _Optimization of collective communication operations in MPICH: https://www.mcs.anl.gov/~thakur/papers/ijhpca-coll.pdf +.. _Optimization of collective communication operations in MPICH: https://web.cels.anl.gov/~thakur/papers/ijhpca-coll.pdf .. _A Communication-Efficient Parallel Algorithm for Decision Tree: http://papers.nips.cc/paper/6381-a-communication-efficient-parallel-algorithm-for-decision-tree diff --git a/docs/GPU-Performance.rst b/docs/GPU-Performance.rst index ab7ff4137cf8..64cd78eb4202 100644 --- a/docs/GPU-Performance.rst +++ b/docs/GPU-Performance.rst @@ -194,17 +194,17 @@ following article: Huan Zhang, Si Si and Cho-Jui Hsieh. `GPU Acceleration for Large-scale Tree Boosting`_. SysML Conference, 2018. -.. _link1: https://archive.ics.uci.edu/ml/datasets/HIGGS +.. _link1: https://archive.ics.uci.edu/dataset/280/higgs -.. _link2: http://www.csie.ntu.edu.tw/~cjlin/libsvmtools/datasets/binary.html +.. _link2: https://www.csie.ntu.edu.tw/~cjlin/libsvmtools/datasets/binary.html .. _link3: https://www.kaggle.com/c/bosch-production-line-performance/data .. _link4: https://webscope.sandbox.yahoo.com/catalog.php?datatype=c -.. _link5: http://research.microsoft.com/en-us/projects/mslr/ +.. _link5: https://www.microsoft.com/en-us/research/project/mslr/ -.. _link6: http://stat-computing.org/dataexpo/2009/ +.. _link6: https://community.amstat.org/jointscsg-section/dataexpo/dataexpo2009 .. _0bb4a82: https://github.com/microsoft/LightGBM/commit/0bb4a82 diff --git a/docs/GPU-Targets.rst b/docs/GPU-Targets.rst index 9c3cac7c814a..ab024847d82d 100644 --- a/docs/GPU-Targets.rst +++ b/docs/GPU-Targets.rst @@ -4,7 +4,7 @@ GPU SDK Correspondence and Device Targeting Table GPU Targets Table ================= -OpenCL is a universal massively parallel programming framework that targets to multiple backends (GPU, CPU, FPGA, etc). +OpenCL is a universal massively parallel programming framework that targets multiple backends (GPU, CPU, FPGA, etc). Basically, to use a device from a vendor, you have to install drivers from that specific vendor. Intel's and AMD's OpenCL runtime also include x86 CPU target support. NVIDIA's OpenCL runtime only supports NVIDIA GPU (no CPU support). diff --git a/docs/GPU-Tutorial.rst b/docs/GPU-Tutorial.rst index 836ab1add378..ee1d3173e556 100644 --- a/docs/GPU-Tutorial.rst +++ b/docs/GPU-Tutorial.rst @@ -33,7 +33,7 @@ After installing the drivers you need to restart the server. After about 30 seconds, the server should be up again. -If you are using an AMD GPU, you should download and install the `AMDGPU-Pro`_ driver and also install package ``ocl-icd-libopencl1`` and ``ocl-icd-opencl-dev``. +If you are using an AMD GPU, you should download and install the `AMDGPU-Pro`_ driver and also install packages ``ocl-icd-libopencl1`` and ``ocl-icd-opencl-dev``. Build LightGBM -------------- diff --git a/docs/GPU-Windows.rst b/docs/GPU-Windows.rst index 36e657e5801b..c4c2ca818433 100644 --- a/docs/GPU-Windows.rst +++ b/docs/GPU-Windows.rst @@ -152,7 +152,7 @@ Download `Prebuilt Boost x86_64`_ or `Prebuilt Boost i686`_ and unpack them wit Boost Compilation ----------------- -Installing Boost requires to download Boost and to install it. +Installing Boost requires downloading Boost and installing it. It takes about 10 minutes to several hours depending on your CPU speed and network speed. We will assume an installation in ``C:\boost`` and a general installation (like in Unix variants: without versioning and without type tags). @@ -279,7 +279,7 @@ Installing CMake requires one download first and then a lot of configuration for :target: ./_static/images/screenshot-downloading-cmake.png :alt: A screenshot of the binary distributions of C Make for downloading on 64 bit Windows. -- Download `CMake`_ (3.8 or higher) +- Download `CMake`_ - Install CMake diff --git a/docs/Installation-Guide.rst b/docs/Installation-Guide.rst index 412f0ec5993a..62383d9f924a 100644 --- a/docs/Installation-Guide.rst +++ b/docs/Installation-Guide.rst @@ -69,7 +69,7 @@ The ``.exe`` file will be in ``LightGBM-master/windows/x64/Release`` folder. From Command Line ***************** -1. Install `Git for Windows`_, `CMake`_ (3.8 or higher) and `VS Build Tools`_ (**VS Build Tools** is not needed if **Visual Studio** (2015 or newer) is already installed). +1. Install `Git for Windows`_, `CMake`_ and `VS Build Tools`_ (**VS Build Tools** is not needed if **Visual Studio** (2015 or newer) is already installed). 2. Run the following commands: @@ -167,7 +167,7 @@ Install Using ``Homebrew`` Build from GitHub ***************** -1. Install `CMake`_ (3.16 or higher): +1. Install `CMake`_ : .. code:: sh @@ -193,7 +193,7 @@ Build from GitHub gcc ^^^ -1. Install `CMake`_ (3.2 or higher): +1. Install `CMake`_ : .. code:: sh @@ -266,7 +266,7 @@ The ``.exe`` file will be in ``LightGBM-master/windows/x64/Release`` folder. From Command Line ----------------- -1. Install `Git for Windows`_, `CMake`_ (3.8 or higher) and `VS Build Tools`_ (**VS Build Tools** is not needed if **Visual Studio** (2015 or newer) is already installed). +1. Install `Git for Windows`_, `CMake`_ and `VS Build Tools`_ (**VS Build Tools** is not needed if **Visual Studio** (2015 or newer) is already installed). 2. Run the following commands: @@ -331,7 +331,7 @@ Apple Clang Only **Apple Clang** version 8.1 or higher is supported. -1. Install `CMake`_ (3.16 or higher): +1. Install `CMake`_ : .. code:: sh @@ -351,7 +351,7 @@ Only **Apple Clang** version 8.1 or higher is supported. gcc *** -1. Install `CMake`_ (3.2 or higher): +1. Install `CMake`_ : .. code:: sh @@ -414,7 +414,7 @@ From Command Line 1. You need to install `MS MPI`_ first. Both ``msmpisdk.msi`` and ``msmpisetup.exe`` are needed. -2. Install `Git for Windows`_, `CMake`_ (3.8 or higher) and `VS Build Tools`_ (**VS Build Tools** is not needed if **Visual Studio** (2015 or newer) is already installed). +2. Install `Git for Windows`_, `CMake`_ and `VS Build Tools`_ (**VS Build Tools** is not needed if **Visual Studio** (2015 or newer) is already installed). 3. Run the following commands: @@ -465,7 +465,7 @@ Apple Clang Only **Apple Clang** version 8.1 or higher is supported. -1. Install `CMake`_ (3.16 or higher): +1. Install `CMake`_ : .. code:: sh @@ -497,7 +497,7 @@ Only **Apple Clang** version 8.1 or higher is supported. gcc *** -1. Install `CMake`_ (3.2 or higher): +1. Install `CMake`_ : .. code:: sh @@ -547,7 +547,7 @@ The following dependencies should be installed before compilation: The following Debian packages should provide necessary Boost libraries: ``libboost-dev``, ``libboost-system-dev``, ``libboost-filesystem-dev``. -- **CMake** 3.2 or later. +- **CMake** To build LightGBM GPU version, run the following commands: @@ -575,7 +575,7 @@ If you use **MinGW**, the build procedure is similar to the build on Linux. Refe Following procedure is for the **MSVC** (Microsoft Visual C++) build. -1. Install `Git for Windows`_, `CMake`_ (3.8 or higher) and `VS Build Tools`_ (**VS Build Tools** is not needed if **Visual Studio** (2015 or newer) is installed). +1. Install `Git for Windows`_, `CMake`_ and `VS Build Tools`_ (**VS Build Tools** is not needed if **Visual Studio** (2015 or newer) is installed). 2. Install **OpenCL** for Windows. The installation depends on the brand (NVIDIA, AMD, Intel) of your GPU card. @@ -635,9 +635,9 @@ On Linux a CUDA version of LightGBM can be built using **CUDA**, **CMake** and * The following dependencies should be installed before compilation: -- **CUDA** 10.0 or later libraries. Please refer to `this detailed guide`_. Pay great attention to the minimum required versions of host compilers listed in the table from that guide and use only recommended versions of compilers. +- **CUDA** 11.0 or later libraries. Please refer to `this detailed guide`_. Pay great attention to the minimum required versions of host compilers listed in the table from that guide and use only recommended versions of compilers. -- **CMake** 3.16 or later. +- **CMake** To build LightGBM CUDA version, run the following commands: @@ -700,7 +700,7 @@ On Windows a Java wrapper of LightGBM can be built using **Java**, **SWIG**, **C VS Build Tools ************** -1. Install `Git for Windows`_, `CMake`_ (3.8 or higher) and `VS Build Tools`_ (**VS Build Tools** is not needed if **Visual Studio** (2015 or newer) is already installed). +1. Install `Git for Windows`_, `CMake`_ and `VS Build Tools`_ (**VS Build Tools** is not needed if **Visual Studio** (2015 or newer) is already installed). 2. Install `SWIG`_ and **Java** (also make sure that ``JAVA_HOME`` is set properly). @@ -779,7 +779,7 @@ Apple Clang Only **Apple Clang** version 8.1 or higher is supported. -1. Install `CMake`_ (3.16 or higher): +1. Install `CMake`_ : .. code:: sh @@ -805,7 +805,7 @@ Only **Apple Clang** version 8.1 or higher is supported. gcc *** -1. Install `CMake`_ (3.2 or higher): +1. Install `CMake`_ : .. code:: sh @@ -839,7 +839,7 @@ Windows On Windows, C++ unit tests of LightGBM can be built using **CMake** and **VS Build Tools**. -1. Install `Git for Windows`_, `CMake`_ (3.8 or higher) and `VS Build Tools`_ (**VS Build Tools** is not needed if **Visual Studio** (2015 or newer) is already installed). +1. Install `Git for Windows`_, `CMake`_ and `VS Build Tools`_ (**VS Build Tools** is not needed if **Visual Studio** (2015 or newer) is already installed). 2. Run the following commands: @@ -884,7 +884,7 @@ Apple Clang Only **Apple Clang** version 8.1 or higher is supported. -1. Install `CMake`_ (3.16 or higher): +1. Install `CMake`_ : .. code:: sh @@ -904,7 +904,7 @@ Only **Apple Clang** version 8.1 or higher is supported. gcc *** -1. Install `CMake`_ (3.2 or higher): +1. Install `CMake`_ : .. code:: sh @@ -950,7 +950,7 @@ gcc .. _RDMA: https://en.wikipedia.org/wiki/Remote_direct_memory_access -.. _MS MPI: https://docs.microsoft.com/en-us/message-passing-interface/microsoft-mpi-release-notes +.. _MS MPI: https://learn.microsoft.com/en-us/message-passing-interface/microsoft-mpi-release-notes .. _Open MPI: https://www.open-mpi.org/ @@ -960,7 +960,7 @@ gcc .. _Boost Binaries: https://sourceforge.net/projects/boost/files/boost-binaries/ -.. _SWIG: http://www.swig.org/download.html +.. _SWIG: https://www.swig.org/download.html .. _this detailed guide: https://docs.nvidia.com/cuda/cuda-installation-guide-linux/index.html diff --git a/docs/Parallel-Learning-Guide.rst b/docs/Parallel-Learning-Guide.rst index e1857034e499..cbc7b1012b98 100644 --- a/docs/Parallel-Learning-Guide.rst +++ b/docs/Parallel-Learning-Guide.rst @@ -384,8 +384,6 @@ From the point forward, you can use any of the following methods to save the Boo Kubeflow ^^^^^^^^ -`Kubeflow Fairing`_ supports LightGBM distributed training. `These examples`_ show how to get started with LightGBM and Kubeflow Fairing in a hybrid cloud environment. - Kubeflow users can also use the `Kubeflow XGBoost Operator`_ for machine learning workflows with LightGBM. You can see `this example`_ for more details. Kubeflow integrations for LightGBM are not maintained by LightGBM's maintainers. @@ -520,7 +518,7 @@ See `the mars documentation`_ for usage examples. .. _the Dask DataFrame documentation: https://docs.dask.org/en/latest/dataframe.html -.. _the Dask prediction example: https://github.com/microsoft/lightgbm/tree/master/examples/python-guide/dask/prediction.py +.. _the Dask prediction example: https://github.com/microsoft/LightGBM/blob/master/examples/python-guide/dask/prediction.py .. _the Dask worker documentation: https://distributed.dask.org/en/stable/worker-memory.html @@ -528,10 +526,6 @@ See `the mars documentation`_ for usage examples. .. _these Dask examples: https://github.com/microsoft/lightgbm/tree/master/examples/python-guide/dask -.. _Kubeflow Fairing: https://www.kubeflow.org/docs/components/fairing/fairing-overview - -.. _These examples: https://github.com/kubeflow/fairing/tree/master/examples/lightgbm - .. _Kubeflow XGBoost Operator: https://github.com/kubeflow/xgboost-operator .. _this example: https://github.com/kubeflow/xgboost-operator/tree/master/config/samples/lightgbm-dist @@ -542,7 +536,7 @@ See `the mars documentation`_ for usage examples. .. _lightgbm_ray: https://github.com/ray-project/lightgbm_ray -.. _Ray: https://ray.io/ +.. _Ray: https://www.ray.io/ .. _the lightgbm_ray documentation: https://docs.ray.io/en/latest/tune/api_docs/integration.html#lightgbm-tune-integration-lightgbm diff --git a/docs/Parameters.rst b/docs/Parameters.rst index 86104ba5be55..341cdd487c71 100644 --- a/docs/Parameters.rst +++ b/docs/Parameters.rst @@ -14,10 +14,6 @@ This page contains descriptions of all parameters in LightGBM. - `Parameters Tuning <./Parameters-Tuning.rst>`__ -**External Links** - -- `Laurae++ Interactive Documentation`_ - Parameters Format ----------------- @@ -119,7 +115,7 @@ Core Parameters - ranking application - - ``lambdarank``, `lambdarank `__ objective. `label_gain <#label_gain>`__ can be used to set the gain (weight) of ``int`` label and all values in ``label`` must be smaller than number of elements in ``label_gain`` + - ``lambdarank``, `lambdarank `__ objective. `label_gain <#label_gain>`__ can be used to set the gain (weight) of ``int`` label and all values in ``label`` must be smaller than number of elements in ``label_gain`` - ``rank_xendcg``, `XE_NDCG_MART `__ ranking objective function, aliases: ``xendcg``, ``xe_ndcg``, ``xe_ndcg_mart``, ``xendcg_mart`` @@ -536,15 +532,15 @@ Learning Control Parameters - ``basic``, the most basic monotone constraints method. It does not slow the library at all, but over-constrains the predictions - - ``intermediate``, a `more advanced method `__, which may slow the library very slightly. However, this method is much less constraining than the basic method and should significantly improve the results + - ``intermediate``, a `more advanced method `__, which may slow the library very slightly. However, this method is much less constraining than the basic method and should significantly improve the results - - ``advanced``, an `even more advanced method `__, which may slow the library. However, this method is even less constraining than the intermediate method and should again significantly improve the results + - ``advanced``, an `even more advanced method `__, which may slow the library. However, this method is even less constraining than the intermediate method and should again significantly improve the results - ``monotone_penalty`` :raw-html:`🔗︎`, default = ``0.0``, type = double, aliases: ``monotone_splits_penalty``, ``ms_penalty``, ``mc_penalty``, constraints: ``monotone_penalty >= 0.0`` - used only if ``monotone_constraints`` is set - - `monotone penalty `__: a penalization parameter X forbids any monotone splits on the first X (rounded down) level(s) of the tree. The penalty applied to monotone splits on a given depth is a continuous, increasing function the penalization parameter + - `monotone penalty `__: a penalization parameter X forbids any monotone splits on the first X (rounded down) level(s) of the tree. The penalty applied to monotone splits on a given depth is a continuous, increasing function the penalization parameter - if ``0.0`` (the default), no penalization is applied @@ -564,7 +560,7 @@ Learning Control Parameters - **Note**: the forced split logic will be ignored, if the split makes gain worse - - see `this file `__ as an example + - see `this file `__ as an example - ``refit_decay_rate`` :raw-html:`🔗︎`, default = ``0.9``, type = double, constraints: ``0.0 <= refit_decay_rate <= 1.0`` @@ -770,7 +766,7 @@ Dataset Parameters - ``enable_bundle`` :raw-html:`🔗︎`, default = ``true``, type = bool, aliases: ``is_enable_bundle``, ``bundle`` - - set this to ``false`` to disable Exclusive Feature Bundling (EFB), which is described in `LightGBM: A Highly Efficient Gradient Boosting Decision Tree `__ + - set this to ``false`` to disable Exclusive Feature Bundling (EFB), which is described in `LightGBM: A Highly Efficient Gradient Boosting Decision Tree `__ - **Note**: disabling this may cause the slow training speed for sparse datasets @@ -894,7 +890,7 @@ Dataset Parameters - ``.json`` file should contain an array of objects, each containing the word ``feature`` (integer feature index) and ``bin_upper_bound`` (array of thresholds for binning) - - see `this file `__ as an example + - see `this file `__ as an example - ``save_binary`` :raw-html:`🔗︎`, default = ``false``, type = bool, aliases: ``is_save_binary``, ``is_save_binary_file`` @@ -961,7 +957,7 @@ Predict Parameters - produces ``#features + 1`` values where the last value is the expected value of the model output over the training data - - **Note**: if you want to get more explanation for your model's predictions using SHAP values like SHAP interaction values, you can install `shap package `__ + - **Note**: if you want to get more explanation for your model's predictions using SHAP values like SHAP interaction values, you can install `shap package `__ - **Note**: unlike the shap package, with ``predict_contrib`` we return a matrix with an extra column, where the last column is the expected value @@ -1380,5 +1376,3 @@ If the name of data file is ``train.txt``, the query file should be named as ``t In this case, LightGBM will load the query file automatically if it exists. Also, you can include query/group id column in your data file. Please refer to the ``group_column`` `parameter <#group_column>`__ in above. - -.. _Laurae++ Interactive Documentation: https://sites.google.com/view/lauraepp/parameters diff --git a/docs/Quick-Start.rst b/docs/Quick-Start.rst index 04e64beb1281..4a372db6736e 100644 --- a/docs/Quick-Start.rst +++ b/docs/Quick-Start.rst @@ -50,7 +50,7 @@ The parameters format is ``key1=value1 key2=value2 ...``. Parameters can be set both in config file and command line. If one parameter appears in both command line and config file, LightGBM will use the parameter from the command line. -The most important parameters which new users should take a look to are located into `Core Parameters <./Parameters.rst#core-parameters>`__ +The most important parameters which new users should take a look at are located into `Core Parameters <./Parameters.rst#core-parameters>`__ and the top of `Learning Control Parameters <./Parameters.rst#learning-control-parameters>`__ sections of the full detailed list of `LightGBM's parameters <./Parameters.rst>`__. @@ -85,4 +85,4 @@ Examples .. _LibSVM: https://www.csie.ntu.edu.tw/~cjlin/libsvm/ -.. _Expo data: http://stat-computing.org/dataexpo/2009/ +.. _Expo data: https://community.amstat.org/jointscsg-section/dataexpo/dataexpo2009 diff --git a/docs/env.yml b/docs/env.yml index 69bcd92fbdc3..351ce1f0fae0 100644 --- a/docs/env.yml +++ b/docs/env.yml @@ -9,9 +9,9 @@ dependencies: - r-data.table=1.14.2 - r-jsonlite=1.7.2 - r-knitr=1.37 + - r-markdown - r-matrix=1.4_0 - r-pkgdown=1.6.1 - - r-rmarkdown=2.11 - r-roxygen2=7.2.1 - scikit-learn - sphinx diff --git a/docs/gcc-Tips.rst b/docs/gcc-Tips.rst index ad5981855d20..938aee407f7d 100644 --- a/docs/gcc-Tips.rst +++ b/docs/gcc-Tips.rst @@ -25,8 +25,6 @@ You can find more details on the experimentation below: - `Laurae's Benchmark Master Data (Interactive) `__ -- `Kaggle Paris Meetup #12 Slides `__ - The image below compares the runtime for training with different compiler options to a baseline using LightGBM compiled with ``-O2 --mtune=core2``. All three options are faster than that baseline. The best performance was achieved with ``-O3 --mtune=native``. .. image:: ./_static/images/gcc-comparison-2.png diff --git a/include/LightGBM/arrow.h b/include/LightGBM/arrow.h new file mode 100644 index 000000000000..767da12a9809 --- /dev/null +++ b/include/LightGBM/arrow.h @@ -0,0 +1,259 @@ +/*! + * Copyright (c) 2023 Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See LICENSE file in the project root for license information. + * + * Author: Oliver Borchert + */ + +#ifndef LIGHTGBM_ARROW_H_ +#define LIGHTGBM_ARROW_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* -------------------------------------- C DATA INTERFACE ------------------------------------- */ +// The C data interface is taken from +// https://arrow.apache.org/docs/format/CDataInterface.html#structure-definitions +// and is available under Apache License 2.0 (https://www.apache.org/licenses/LICENSE-2.0). + +#ifdef __cplusplus +extern "C" { +#endif + +#define ARROW_FLAG_DICTIONARY_ORDERED 1 +#define ARROW_FLAG_NULLABLE 2 +#define ARROW_FLAG_MAP_KEYS_SORTED 4 + +struct ArrowSchema { + // Array type description + const char* format; + const char* name; + const char* metadata; + int64_t flags; + int64_t n_children; + struct ArrowSchema** children; + struct ArrowSchema* dictionary; + + // Release callback + void (*release)(struct ArrowSchema*); + // Opaque producer-specific data + void* private_data; +}; + +struct ArrowArray { + // Array data description + int64_t length; + int64_t null_count; + int64_t offset; + int64_t n_buffers; + int64_t n_children; + const void** buffers; + struct ArrowArray** children; + struct ArrowArray* dictionary; + + // Release callback + void (*release)(struct ArrowArray*); + // Opaque producer-specific data + void* private_data; +}; + +#ifdef __cplusplus +} +#endif + +/* --------------------------------------------------------------------------------------------- */ +/* CHUNKED ARRAY */ +/* --------------------------------------------------------------------------------------------- */ + +namespace LightGBM { + +/** + * @brief Arrow array-like container for a list of Arrow arrays. + */ +class ArrowChunkedArray { + /* List of length `n` for `n` chunks containing the individual Arrow arrays. */ + std::vector chunks_; + /* Schema for all chunks. */ + const ArrowSchema* schema_; + /* List of length `n + 1` for `n` chunks containing the offsets for each chunk. */ + std::vector chunk_offsets_; + + inline void construct_chunk_offsets() { + chunk_offsets_.reserve(chunks_.size() + 1); + chunk_offsets_.emplace_back(0); + for (size_t k = 0; k < chunks_.size(); ++k) { + chunk_offsets_.emplace_back(chunks_[k]->length + chunk_offsets_.back()); + } + } + + public: + /** + * @brief Construct a new Arrow Chunked Array object. + * + * @param chunks A list with the chunks. + * @param schema The schema for all chunks. + */ + inline ArrowChunkedArray(std::vector chunks, const ArrowSchema* schema) { + chunks_ = chunks; + schema_ = schema; + construct_chunk_offsets(); + } + + /** + * @brief Construct a new Arrow Chunked Array object. + * + * @param n_chunks The number of chunks. + * @param chunks A C-style array containing the chunks. + * @param schema The schema for all chunks. + */ + inline ArrowChunkedArray(int64_t n_chunks, + const struct ArrowArray* chunks, + const struct ArrowSchema* schema) { + chunks_.reserve(n_chunks); + for (auto k = 0; k < n_chunks; ++k) { + if (chunks[k].length == 0) continue; + chunks_.push_back(&chunks[k]); + } + schema_ = schema; + construct_chunk_offsets(); + } + + /** + * @brief Get the length of the chunked array. + * This method returns the cumulative length of all chunks. + * Complexity: O(1) + * + * @return int64_t The number of elements in the chunked array. + */ + inline int64_t get_length() const { return chunk_offsets_.back(); } + + /* ----------------------------------------- ITERATOR ---------------------------------------- */ + template + class Iterator { + using getter_fn = std::function; + + /* Reference to the chunked array that this iterator iterates over. */ + const ArrowChunkedArray& array_; + /* Function to fetch the value at a certain index from a single chunk. */ + getter_fn get_; + /* The chunk the iterator currently points to. */ + int64_t ptr_chunk_; + /* The index inside the current chunk that the iterator points to. */ + int64_t ptr_offset_; + + public: + using iterator_category = std::random_access_iterator_tag; + using difference_type = int64_t; + using value_type = T; + using pointer = value_type*; + using reference = value_type&; + + /** + * @brief Construct a new Iterator object. + * + * @param array Reference to the chunked array to iterator over. + * @param get Function to fetch the value at a certain index from a single chunk. + * @param ptr_chunk The index of the chunk to whose first index the iterator points to. + */ + Iterator(const ArrowChunkedArray& array, getter_fn get, int64_t ptr_chunk); + + T operator*() const; + template + T operator[](I idx) const; + + Iterator& operator++(); + Iterator& operator--(); + Iterator& operator+=(int64_t c); + + template + friend bool operator==(const Iterator& a, const Iterator& b); + template + friend bool operator!=(const Iterator& a, const Iterator& b); + template + friend int64_t operator-(const Iterator& a, const Iterator& b); + }; + + /** + * @brief Obtain an iterator to the beginning of the chunked array. + * + * @tparam T The value type of the iterator. May be any primitive type. + * @return Iterator The iterator. + */ + template + inline Iterator begin() const; + + /** + * @brief Obtain an iterator to the beginning of the chunked array. + * + * @tparam T The value type of the iterator. May be any primitive type. + * @return Iterator The iterator. + */ + template + inline Iterator end() const; + + template + friend int64_t operator-(const Iterator& a, const Iterator& b); +}; + +/** + * @brief Arrow container for a list of chunked arrays. + */ +class ArrowTable { + std::vector columns_; + + public: + /** + * @brief Construct a new Arrow Table object. + * + * @param n_chunks The number of chunks. + * @param chunks A C-style array containing the chunks. + * @param schema The schema for all chunks. + */ + inline ArrowTable(int64_t n_chunks, const ArrowArray* chunks, const ArrowSchema* schema) { + columns_.reserve(schema->n_children); + for (int64_t j = 0; j < schema->n_children; ++j) { + std::vector children_chunks; + children_chunks.reserve(n_chunks); + for (int64_t k = 0; k < n_chunks; ++k) { + if (chunks[k].length == 0) continue; + children_chunks.push_back(chunks[k].children[j]); + } + columns_.emplace_back(children_chunks, schema->children[j]); + } + } + + /** + * @brief Get the number of rows in the table. + * + * @return int64_t The number of rows. + */ + inline int64_t get_num_rows() const { return columns_.front().get_length(); } + + /** + * @brief Get the number of columns of this table. + * + * @return int64_t The column count. + */ + inline int64_t get_num_columns() const { return columns_.size(); } + + /** + * @brief Get the column at a particular index. + * + * @param idx The index of the column, must me in the range `[0, num_columns)`. + * @return const ArrowChunkedArray& The chunked array for the child at the provided index. + */ + inline const ArrowChunkedArray& get_column(size_t idx) const { return this->columns_[idx]; } +}; + +} // namespace LightGBM + +#include "arrow.tpp" + +#endif /* LIGHTGBM_ARROW_H_ */ diff --git a/include/LightGBM/arrow.tpp b/include/LightGBM/arrow.tpp new file mode 100644 index 000000000000..8d1ce4f4c0c1 --- /dev/null +++ b/include/LightGBM/arrow.tpp @@ -0,0 +1,190 @@ +#include + +#ifndef ARROW_TPP_ +#define ARROW_TPP_ + +namespace LightGBM { + +/** + * @brief Obtain a function to access an index from an Arrow array. + * + * @tparam T The return type of the function, must be a primitive type. + * @param dtype The Arrow format string describing the datatype of the Arrow array. + * @return std::function The index accessor function. + */ +template +std::function get_index_accessor(const char* dtype); + +/* ---------------------------------- ITERATOR INITIALIZATION ---------------------------------- */ + +template +inline ArrowChunkedArray::Iterator ArrowChunkedArray::begin() const { + return ArrowChunkedArray::Iterator(*this, get_index_accessor(schema_->format), 0); +} + +template +inline ArrowChunkedArray::Iterator ArrowChunkedArray::end() const { + return ArrowChunkedArray::Iterator(*this, get_index_accessor(schema_->format), + chunk_offsets_.size() - 1); +} + +/* ---------------------------------- ITERATOR IMPLEMENTATION ---------------------------------- */ + +template +ArrowChunkedArray::Iterator::Iterator(const ArrowChunkedArray& array, + getter_fn get, + int64_t ptr_chunk) + : array_(array), get_(get), ptr_chunk_(ptr_chunk) { + this->ptr_offset_ = 0; +} + +template +T ArrowChunkedArray::Iterator::operator*() const { + auto chunk = array_.chunks_[ptr_chunk_]; + return static_cast(get_(chunk, ptr_offset_)); +} + +template +template +T ArrowChunkedArray::Iterator::operator[](I idx) const { + auto it = std::lower_bound(array_.chunk_offsets_.begin(), array_.chunk_offsets_.end(), idx, + [](int64_t a, int64_t b) { return a <= b; }); + + auto chunk_idx = std::distance(array_.chunk_offsets_.begin() + 1, it); + auto chunk = array_.chunks_[chunk_idx]; + + auto ptr_offset = static_cast(idx) - array_.chunk_offsets_[chunk_idx]; + return static_cast(get_(chunk, ptr_offset)); +} + +template +ArrowChunkedArray::Iterator& ArrowChunkedArray::Iterator::operator++() { + if (ptr_offset_ + 1 >= array_.chunks_[ptr_chunk_]->length) { + ptr_offset_ = 0; + ptr_chunk_++; + } else { + ptr_offset_++; + } + return *this; +} + +template +ArrowChunkedArray::Iterator& ArrowChunkedArray::Iterator::operator--() { + if (ptr_offset_ == 0) { + ptr_chunk_--; + ptr_offset_ = array_.chunks_[ptr_chunk_]->length - 1; + } else { + ptr_chunk_--; + } + return *this; +} + +template +ArrowChunkedArray::Iterator& ArrowChunkedArray::Iterator::operator+=(int64_t c) { + while (ptr_offset_ + c >= array_.chunks_[ptr_chunk_]->length) { + c -= array_.chunks_[ptr_chunk_]->length - ptr_offset_; + ptr_offset_ = 0; + ptr_chunk_++; + } + ptr_offset_ += c; + return *this; +} + +template +bool operator==(const ArrowChunkedArray::Iterator& a, const ArrowChunkedArray::Iterator& b) { + return a.ptr_chunk_ == b.ptr_chunk_ && a.ptr_offset_ == b.ptr_offset_; +} + +template +bool operator!=(const ArrowChunkedArray::Iterator& a, const ArrowChunkedArray::Iterator& b) { + return a.ptr_chunk_ != b.ptr_chunk_ || a.ptr_offset_ != b.ptr_offset_; +} + +template +int64_t operator-(const ArrowChunkedArray::Iterator& a, + const ArrowChunkedArray::Iterator& b) { + auto full_offset_a = a.array_.chunk_offsets_[a.ptr_chunk_] + a.ptr_offset_; + auto full_offset_b = b.array_.chunk_offsets_[b.ptr_chunk_] + b.ptr_offset_; + return full_offset_a - full_offset_b; +} + +/* --------------------------------------- INDEX ACCESSOR -------------------------------------- */ + +/** + * @brief The value of "no value" for a primitive type. + * + * @tparam T The type for which the missing value is defined. + * @return T The missing value. + */ +template +inline T arrow_primitive_missing_value() { + return 0; +} + +template <> +inline double arrow_primitive_missing_value() { + return std::numeric_limits::quiet_NaN(); +} + +template <> +inline float arrow_primitive_missing_value() { + return std::numeric_limits::quiet_NaN(); +} + +template +struct ArrayIndexAccessor { + V operator()(const ArrowArray* array, size_t idx) { + auto buffer_idx = idx + array->offset; + + // For primitive types, buffer at idx 0 provides validity, buffer at idx 1 data, see: + // https://arrow.apache.org/docs/format/Columnar.html#buffer-listing-for-each-layout + auto validity = static_cast(array->buffers[0]); + + // Take return value from data buffer conditional on the validity of the index: + // - The structure of validity bitmasks is taken from here: + // https://arrow.apache.org/docs/format/Columnar.html#validity-bitmaps + // - If the bitmask is NULL, all indices are valid + if (validity == nullptr || (validity[buffer_idx / 8] & (1 << (buffer_idx % 8)))) { + // In case the index is valid, we take it from the data buffer + auto data = static_cast(array->buffers[1]); + return static_cast(data[buffer_idx]); + } + + // In case the index is not valid, we return a default value + return arrow_primitive_missing_value(); + } +}; + +template +std::function get_index_accessor(const char* dtype) { + // Mapping obtained from: + // https://arrow.apache.org/docs/format/CDataInterface.html#data-type-description-format-strings + switch (dtype[0]) { + case 'c': + return ArrayIndexAccessor(); + case 'C': + return ArrayIndexAccessor(); + case 's': + return ArrayIndexAccessor(); + case 'S': + return ArrayIndexAccessor(); + case 'i': + return ArrayIndexAccessor(); + case 'I': + return ArrayIndexAccessor(); + case 'l': + return ArrayIndexAccessor(); + case 'L': + return ArrayIndexAccessor(); + case 'f': + return ArrayIndexAccessor(); + case 'g': + return ArrayIndexAccessor(); + default: + throw std::invalid_argument("unsupported Arrow datatype"); + } +} + +} // namespace LightGBM + +#endif diff --git a/include/LightGBM/bin.h b/include/LightGBM/bin.h index ffb8f2844843..de1bb6eb94ed 100644 --- a/include/LightGBM/bin.h +++ b/include/LightGBM/bin.h @@ -63,8 +63,7 @@ inline static void Int32HistogramSumReducer(const char* src, char* dst, int type const int64_t* src_ptr = reinterpret_cast(src); int64_t* dst_ptr = reinterpret_cast(dst); const comm_size_t steps = (len + (type_size * 2) - 1) / (type_size * 2); - const int num_threads = OMP_NUM_THREADS(); - #pragma omp parallel for schedule(static) num_threads(num_threads) + #pragma omp parallel for schedule(static) num_threads(OMP_NUM_THREADS()) for (comm_size_t i = 0; i < steps; ++i) { dst_ptr[i] += src_ptr[i]; } @@ -74,8 +73,7 @@ inline static void Int16HistogramSumReducer(const char* src, char* dst, int type const int32_t* src_ptr = reinterpret_cast(src); int32_t* dst_ptr = reinterpret_cast(dst); const comm_size_t steps = (len + (type_size * 2) - 1) / (type_size * 2); - const int num_threads = OMP_NUM_THREADS(); - #pragma omp parallel for schedule(static) num_threads(num_threads) + #pragma omp parallel for schedule(static) num_threads(OMP_NUM_THREADS()) for (comm_size_t i = 0; i < steps; ++i) { dst_ptr[i] += src_ptr[i]; } diff --git a/include/LightGBM/c_api.h b/include/LightGBM/c_api.h index bba46a02a492..b43f096c31ee 100644 --- a/include/LightGBM/c_api.h +++ b/include/LightGBM/c_api.h @@ -13,6 +13,7 @@ #ifndef LIGHTGBM_C_API_H_ #define LIGHTGBM_C_API_H_ +#include #include #ifdef __cplusplus @@ -437,6 +438,23 @@ LIGHTGBM_C_EXPORT int LGBM_DatasetCreateFromMats(int32_t nmat, const DatasetHandle reference, DatasetHandle* out); +/*! + * \brief Create dataset from Arrow. + * \param n_chunks The number of Arrow arrays passed to this function + * \param chunks Pointer to the list of Arrow arrays + * \param schema Pointer to the schema of all Arrow arrays + * \param parameters Additional parameters + * \param reference Used to align bin mapper with other dataset, nullptr means isn't used + * \param[out] out Created dataset + * \return 0 when succeed, -1 when failure happens + */ +LIGHTGBM_C_EXPORT int LGBM_DatasetCreateFromArrow(int64_t n_chunks, + const ArrowArray* chunks, + const ArrowSchema* schema, + const char* parameters, + const DatasetHandle reference, + DatasetHandle *out); + /*! * \brief Create subset of a data. * \param handle Handle of full dataset @@ -537,6 +555,25 @@ LIGHTGBM_C_EXPORT int LGBM_DatasetSetField(DatasetHandle handle, int num_element, int type); +/*! + * \brief Set vector to a content in info. + * \note + * - \a group converts input datatype into ``int32``; + * - \a label and \a weight convert input datatype into ``float32``; + * - \a init_score converts input datatype into ``float64``. + * \param handle Handle of dataset + * \param field_name Field name, can be \a label, \a weight, \a init_score, \a group + * \param n_chunks The number of Arrow arrays passed to this function + * \param chunks Pointer to the list of Arrow arrays + * \param schema Pointer to the schema of all Arrow arrays + * \return 0 when succeed, -1 when failure happens + */ +LIGHTGBM_C_EXPORT int LGBM_DatasetSetFieldFromArrow(DatasetHandle handle, + const char* field_name, + int64_t n_chunks, + const ArrowArray* chunks, + const ArrowSchema* schema); + /*! * \brief Get info vector from dataset. * \param handle Handle of dataset @@ -1380,6 +1417,40 @@ LIGHTGBM_C_EXPORT int LGBM_BoosterPredictForMats(BoosterHandle handle, int64_t* out_len, double* out_result); +/*! + * \brief Make prediction for a new dataset. + * \note + * You should pre-allocate memory for ``out_result``: + * - for normal and raw score, its length is equal to ``num_class * num_data``; + * - for leaf index, its length is equal to ``num_class * num_data * num_iteration``; + * - for feature contributions, its length is equal to ``num_class * num_data * (num_feature + 1)``. + * \param handle Handle of booster + * \param n_chunks The number of Arrow arrays passed to this function + * \param chunks Pointer to the list of Arrow arrays + * \param schema Pointer to the schema of all Arrow arrays + * \param predict_type What should be predicted + * - ``C_API_PREDICT_NORMAL``: normal prediction, with transform (if needed); + * - ``C_API_PREDICT_RAW_SCORE``: raw score; + * - ``C_API_PREDICT_LEAF_INDEX``: leaf index; + * - ``C_API_PREDICT_CONTRIB``: feature contributions (SHAP values) + * \param start_iteration Start index of the iteration to predict + * \param num_iteration Number of iteration for prediction, <= 0 means no limit + * \param parameter Other parameters for prediction, e.g. early stopping for prediction + * \param[out] out_len Length of output result + * \param[out] out_result Pointer to array with predictions + * \return 0 when succeed, -1 when failure happens + */ +LIGHTGBM_C_EXPORT int LGBM_BoosterPredictForArrow(BoosterHandle handle, + int64_t n_chunks, + const ArrowArray* chunks, + const ArrowSchema* schema, + int predict_type, + int start_iteration, + int num_iteration, + const char* parameter, + int64_t* out_len, + double* out_result); + /*! * \brief Save model into file. * \param handle Handle of booster @@ -1524,6 +1595,20 @@ LIGHTGBM_C_EXPORT int LGBM_NetworkInitWithFunctions(int num_machines, void* reduce_scatter_ext_fun, void* allgather_ext_fun); +/*! + * \brief Set maximum number of threads used by LightGBM routines in this process. + * \param num_threads maximum number of threads used by LightGBM. -1 means defaulting to omp_get_num_threads(). + * \return 0 when succeed, -1 when failure happens + */ +LIGHTGBM_C_EXPORT int LGBM_SetMaxThreads(int num_threads); + +/*! + * \brief Get current maximum number of threads used by LightGBM routines in this process. + * \param[out] out current maximum number of threads used by LightGBM. -1 means defaulting to omp_get_num_threads(). + * \return 0 when succeed, -1 when failure happens + */ +LIGHTGBM_C_EXPORT int LGBM_GetMaxThreads(int* out); + #if !defined(__cplusplus) && (!defined(__STDC__) || (__STDC_VERSION__ < 199901L)) /*! \brief Inline specifier no-op in C using standards before C99. */ #define INLINE_FUNCTION diff --git a/include/LightGBM/config.h b/include/LightGBM/config.h index 6d61bc764924..6500cb77272d 100644 --- a/include/LightGBM/config.h +++ b/include/LightGBM/config.h @@ -150,7 +150,7 @@ struct Config { // descl2 = ``cross_entropy_lambda``, alternative parameterization of cross-entropy, aliases: ``xentlambda`` // descl2 = label is anything in interval [0, 1] // desc = ranking application - // descl2 = ``lambdarank``, `lambdarank `__ objective. `label_gain <#label_gain>`__ can be used to set the gain (weight) of ``int`` label and all values in ``label`` must be smaller than number of elements in ``label_gain`` + // descl2 = ``lambdarank``, `lambdarank `__ objective. `label_gain <#label_gain>`__ can be used to set the gain (weight) of ``int`` label and all values in ``label`` must be smaller than number of elements in ``label_gain`` // descl2 = ``rank_xendcg``, `XE_NDCG_MART `__ ranking objective function, aliases: ``xendcg``, ``xe_ndcg``, ``xe_ndcg_mart``, ``xendcg_mart`` // descl2 = ``rank_xendcg`` is faster than and achieves the similar performance as ``lambdarank`` // descl2 = label should be ``int`` type, and larger number represents the higher relevance (e.g. 0:bad, 1:fair, 2:good, 3:perfect) @@ -501,14 +501,14 @@ struct Config { // desc = used only if ``monotone_constraints`` is set // desc = monotone constraints method // descl2 = ``basic``, the most basic monotone constraints method. It does not slow the library at all, but over-constrains the predictions - // descl2 = ``intermediate``, a `more advanced method `__, which may slow the library very slightly. However, this method is much less constraining than the basic method and should significantly improve the results - // descl2 = ``advanced``, an `even more advanced method `__, which may slow the library. However, this method is even less constraining than the intermediate method and should again significantly improve the results + // descl2 = ``intermediate``, a `more advanced method `__, which may slow the library very slightly. However, this method is much less constraining than the basic method and should significantly improve the results + // descl2 = ``advanced``, an `even more advanced method `__, which may slow the library. However, this method is even less constraining than the intermediate method and should again significantly improve the results std::string monotone_constraints_method = "basic"; // alias = monotone_splits_penalty, ms_penalty, mc_penalty // check = >=0.0 // desc = used only if ``monotone_constraints`` is set - // desc = `monotone penalty `__: a penalization parameter X forbids any monotone splits on the first X (rounded down) level(s) of the tree. The penalty applied to monotone splits on a given depth is a continuous, increasing function the penalization parameter + // desc = `monotone penalty `__: a penalization parameter X forbids any monotone splits on the first X (rounded down) level(s) of the tree. The penalty applied to monotone splits on a given depth is a continuous, increasing function the penalization parameter // desc = if ``0.0`` (the default), no penalization is applied double monotone_penalty = 0.0; @@ -524,7 +524,7 @@ struct Config { // desc = ``.json`` file can be arbitrarily nested, and each split contains ``feature``, ``threshold`` fields, as well as ``left`` and ``right`` fields representing subsplits // desc = categorical splits are forced in a one-hot fashion, with ``left`` representing the split containing the feature value and ``right`` representing other values // desc = **Note**: the forced split logic will be ignored, if the split makes gain worse - // desc = see `this file `__ as an example + // desc = see `this file `__ as an example std::string forcedsplits_filename = ""; // check = >=0.0 @@ -683,7 +683,7 @@ struct Config { bool is_enable_sparse = true; // alias = is_enable_bundle, bundle - // desc = set this to ``false`` to disable Exclusive Feature Bundling (EFB), which is described in `LightGBM: A Highly Efficient Gradient Boosting Decision Tree `__ + // desc = set this to ``false`` to disable Exclusive Feature Bundling (EFB), which is described in `LightGBM: A Highly Efficient Gradient Boosting Decision Tree `__ // desc = **Note**: disabling this may cause the slow training speed for sparse datasets bool enable_bundle = true; @@ -770,7 +770,7 @@ struct Config { // desc = path to a ``.json`` file that specifies bin upper bounds for some or all features // desc = ``.json`` file should contain an array of objects, each containing the word ``feature`` (integer feature index) and ``bin_upper_bound`` (array of thresholds for binning) - // desc = see `this file `__ as an example + // desc = see `this file `__ as an example std::string forcedbins_filename = ""; // [no-save] @@ -826,7 +826,7 @@ struct Config { // desc = used only in ``prediction`` task // desc = set this to ``true`` to estimate `SHAP values `__, which represent how each feature contributes to each prediction // desc = produces ``#features + 1`` values where the last value is the expected value of the model output over the training data - // desc = **Note**: if you want to get more explanation for your model's predictions using SHAP values like SHAP interaction values, you can install `shap package `__ + // desc = **Note**: if you want to get more explanation for your model's predictions using SHAP values like SHAP interaction values, you can install `shap package `__ // desc = **Note**: unlike the shap package, with ``predict_contrib`` we return a matrix with an extra column, where the last column is the expected value // desc = **Note**: this feature is not implemented for linear trees bool predict_contrib = false; diff --git a/include/LightGBM/cuda/cuda_algorithms.hpp b/include/LightGBM/cuda/cuda_algorithms.hpp index ab3328bb5561..f79fc57e4f42 100644 --- a/include/LightGBM/cuda/cuda_algorithms.hpp +++ b/include/LightGBM/cuda/cuda_algorithms.hpp @@ -13,7 +13,7 @@ #include #include -#include +#include #include #include diff --git a/include/LightGBM/cuda/cuda_column_data.hpp b/include/LightGBM/cuda/cuda_column_data.hpp index 5b2301ac8de3..314a178859c6 100644 --- a/include/LightGBM/cuda/cuda_column_data.hpp +++ b/include/LightGBM/cuda/cuda_column_data.hpp @@ -9,7 +9,7 @@ #define LIGHTGBM_CUDA_CUDA_COLUMN_DATA_HPP_ #include -#include +#include #include #include diff --git a/include/LightGBM/cuda/cuda_metadata.hpp b/include/LightGBM/cuda/cuda_metadata.hpp index bc7339a84bf7..5882ce7e05c9 100644 --- a/include/LightGBM/cuda/cuda_metadata.hpp +++ b/include/LightGBM/cuda/cuda_metadata.hpp @@ -8,7 +8,7 @@ #ifndef LIGHTGBM_CUDA_CUDA_METADATA_HPP_ #define LIGHTGBM_CUDA_CUDA_METADATA_HPP_ -#include +#include #include #include diff --git a/include/LightGBM/cuda/cuda_metric.hpp b/include/LightGBM/cuda/cuda_metric.hpp index 9186ceea160b..2540b0c1a835 100644 --- a/include/LightGBM/cuda/cuda_metric.hpp +++ b/include/LightGBM/cuda/cuda_metric.hpp @@ -9,7 +9,7 @@ #ifdef USE_CUDA -#include +#include #include namespace LightGBM { diff --git a/include/LightGBM/cuda/cuda_objective_function.hpp b/include/LightGBM/cuda/cuda_objective_function.hpp index fae8aa7ec643..465ed334156c 100644 --- a/include/LightGBM/cuda/cuda_objective_function.hpp +++ b/include/LightGBM/cuda/cuda_objective_function.hpp @@ -9,7 +9,7 @@ #ifdef USE_CUDA -#include +#include #include #include diff --git a/include/LightGBM/cuda/cuda_row_data.hpp b/include/LightGBM/cuda/cuda_row_data.hpp index 0386db0dc300..1d4cb2f73b1e 100644 --- a/include/LightGBM/cuda/cuda_row_data.hpp +++ b/include/LightGBM/cuda/cuda_row_data.hpp @@ -10,7 +10,7 @@ #include #include -#include +#include #include #include #include diff --git a/include/LightGBM/cuda/cuda_split_info.hpp b/include/LightGBM/cuda/cuda_split_info.hpp index 46b35ca37a59..f01ce2b02a02 100644 --- a/include/LightGBM/cuda/cuda_split_info.hpp +++ b/include/LightGBM/cuda/cuda_split_info.hpp @@ -24,12 +24,14 @@ class CUDASplitInfo { double left_sum_gradients; double left_sum_hessians; + int64_t left_sum_of_gradients_hessians; data_size_t left_count; double left_gain; double left_value; double right_sum_gradients; double right_sum_hessians; + int64_t right_sum_of_gradients_hessians; data_size_t right_count; double right_gain; double right_value; diff --git a/include/LightGBM/cuda/cuda_utils.h b/include/LightGBM/cuda/cuda_utils.hu similarity index 91% rename from include/LightGBM/cuda/cuda_utils.h rename to include/LightGBM/cuda/cuda_utils.hu index 953bf9f12e88..4bd84aeb264d 100644 --- a/include/LightGBM/cuda/cuda_utils.h +++ b/include/LightGBM/cuda/cuda_utils.hu @@ -7,15 +7,21 @@ #define LIGHTGBM_CUDA_CUDA_UTILS_H_ #ifdef USE_CUDA + #include #include #include + #include + +#include #include #include namespace LightGBM { +typedef unsigned long long atomic_add_long_t; + #define CUDASUCCESS_OR_FATAL(ans) { gpuAssert((ans), __FILE__, __LINE__); } inline void gpuAssert(cudaError_t code, const char *file, int line, bool abort = true) { if (code != cudaSuccess) { @@ -125,13 +131,19 @@ class CUDAVector { T* new_data = nullptr; AllocateCUDAMemory(&new_data, size, __FILE__, __LINE__); if (size_ > 0 && data_ != nullptr) { - CopyFromCUDADeviceToCUDADevice(new_data, data_, size, __FILE__, __LINE__); + const size_t size_for_old_content = std::min(size_, size); + CopyFromCUDADeviceToCUDADevice(new_data, data_, size_for_old_content, __FILE__, __LINE__); } DeallocateCUDAMemory(&data_, __FILE__, __LINE__); data_ = new_data; size_ = size; } + void InitFromHostVector(const std::vector& host_vector) { + Resize(host_vector.size()); + CopyFromHostToCUDADevice(data_, host_vector.data(), host_vector.size(), __FILE__, __LINE__); + } + void Clear() { if (size_ > 0 && data_ != nullptr) { DeallocateCUDAMemory(&data_, __FILE__, __LINE__); @@ -171,6 +183,10 @@ class CUDAVector { return data_; } + void SetValue(int value) { + SetCUDAMemory(data_, value, size_, __FILE__, __LINE__); + } + const T* RawDataReadOnly() const { return data_; } diff --git a/include/LightGBM/dataset.h b/include/LightGBM/dataset.h index e7baa42dc2e6..220a1f9f009c 100644 --- a/include/LightGBM/dataset.h +++ b/include/LightGBM/dataset.h @@ -5,6 +5,7 @@ #ifndef LIGHTGBM_DATASET_H_ #define LIGHTGBM_DATASET_H_ +#include #include #include #include @@ -109,10 +110,13 @@ class Metadata { const std::vector& used_data_indices); void SetLabel(const label_t* label, data_size_t len); + void SetLabel(const ArrowChunkedArray& array); void SetWeights(const label_t* weights, data_size_t len); + void SetWeights(const ArrowChunkedArray& array); void SetQuery(const data_size_t* query, data_size_t len); + void SetQuery(const ArrowChunkedArray& array); void SetPosition(const data_size_t* position, data_size_t len); @@ -121,6 +125,7 @@ class Metadata { * \param init_score Initial scores, this class will manage memory for init_score. */ void SetInitScore(const double* init_score, data_size_t len); + void SetInitScore(const ArrowChunkedArray& array); /*! @@ -333,12 +338,24 @@ class Metadata { void CalculateQueryBoundaries(); /*! \brief Insert labels at the given index */ void InsertLabels(const label_t* labels, data_size_t start_index, data_size_t len); + /*! \brief Set labels from pointers to the first element and the end of an iterator. */ + template + void SetLabelsFromIterator(It first, It last); /*! \brief Insert weights at the given index */ void InsertWeights(const label_t* weights, data_size_t start_index, data_size_t len); + /*! \brief Set weights from pointers to the first element and the end of an iterator. */ + template + void SetWeightsFromIterator(It first, It last); /*! \brief Insert initial scores at the given index */ void InsertInitScores(const double* init_scores, data_size_t start_index, data_size_t len, data_size_t source_size); + /*! \brief Set init scores from pointers to the first element and the end of an iterator. */ + template + void SetInitScoresFromIterator(It first, It last); /*! \brief Insert queries at the given index */ void InsertQueries(const data_size_t* queries, data_size_t start_index, data_size_t len); + /*! \brief Set queries from pointers to the first element and the end of an iterator. */ + template + void SetQueriesFromIterator(It first, It last); /*! \brief Filename of current data */ std::string data_filename_; /*! \brief Number of data */ @@ -545,24 +562,29 @@ class Dataset { } } - inline void PushOneRow(int tid, data_size_t row_idx, const std::vector& feature_values) { - if (is_finish_load_) { return; } - for (size_t i = 0; i < feature_values.size() && i < static_cast(num_total_features_); ++i) { - int feature_idx = used_feature_map_[i]; - if (feature_idx >= 0) { - const int group = feature2group_[feature_idx]; - const int sub_feature = feature2subfeature_[feature_idx]; - feature_groups_[group]->PushData(tid, sub_feature, row_idx, feature_values[i]); - if (has_raw_) { - int feat_ind = numeric_feature_map_[feature_idx]; - if (feat_ind >= 0) { - raw_data_[feat_ind][row_idx] = static_cast(feature_values[i]); - } + inline void PushOneValue(int tid, data_size_t row_idx, size_t col_idx, double value) { + if (this->is_finish_load_) + return; + auto feature_idx = this->used_feature_map_[col_idx]; + if (feature_idx >= 0) { + auto group = this->feature2group_[feature_idx]; + auto sub_feature = this->feature2subfeature_[feature_idx]; + this->feature_groups_[group]->PushData(tid, sub_feature, row_idx, value); + if (this->has_raw_) { + auto feat_ind = numeric_feature_map_[feature_idx]; + if (feat_ind >= 0) { + raw_data_[feat_ind][row_idx] = static_cast(value); } } } } + inline void PushOneRow(int tid, data_size_t row_idx, const std::vector& feature_values) { + for (size_t i = 0; i < feature_values.size() && i < static_cast(num_total_features_); ++i) { + this->PushOneValue(tid, row_idx, i, feature_values[i]); + } + } + inline void PushOneRow(int tid, data_size_t row_idx, const std::vector>& feature_values) { if (is_finish_load_) { return; } std::vector is_feature_added(num_features_, false); @@ -649,6 +671,8 @@ class Dataset { LIGHTGBM_EXPORT void FinishLoad(); + bool SetFieldFromArrow(const char* field_name, const ArrowChunkedArray& ca); + LIGHTGBM_EXPORT bool SetFloatField(const char* field_name, const float* field_data, data_size_t num_element); LIGHTGBM_EXPORT bool SetDoubleField(const char* field_name, const double* field_data, data_size_t num_element); diff --git a/include/LightGBM/feature_group.h b/include/LightGBM/feature_group.h index 003df70afad7..f13a5fff966f 100644 --- a/include/LightGBM/feature_group.h +++ b/include/LightGBM/feature_group.h @@ -361,7 +361,7 @@ class FeatureGroup { inline void FinishLoad() { if (is_multi_val_) { OMP_INIT_EX(); -#pragma omp parallel for schedule(guided) +#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(guided) for (int i = 0; i < num_feature_; ++i) { OMP_LOOP_EX_BEGIN(); multi_bin_data_[i]->FinishLoad(); diff --git a/include/LightGBM/sample_strategy.h b/include/LightGBM/sample_strategy.h index 51d3cbc16f23..4ea5cfc5f436 100644 --- a/include/LightGBM/sample_strategy.h +++ b/include/LightGBM/sample_strategy.h @@ -6,7 +6,7 @@ #ifndef LIGHTGBM_SAMPLE_STRATEGY_H_ #define LIGHTGBM_SAMPLE_STRATEGY_H_ -#include +#include #include #include #include diff --git a/include/LightGBM/tree.h b/include/LightGBM/tree.h index 13b3c41a2309..0c4a41f46a87 100644 --- a/include/LightGBM/tree.h +++ b/include/LightGBM/tree.h @@ -185,7 +185,7 @@ class Tree { * \param rate The factor of shrinkage */ virtual inline void Shrinkage(double rate) { -#pragma omp parallel for schedule(static, 1024) if (num_leaves_ >= 2048) +#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static, 1024) if (num_leaves_ >= 2048) for (int i = 0; i < num_leaves_ - 1; ++i) { leaf_value_[i] = MaybeRoundToZero(leaf_value_[i] * rate); internal_value_[i] = MaybeRoundToZero(internal_value_[i] * rate); @@ -210,7 +210,7 @@ class Tree { inline double shrinkage() const { return shrinkage_; } virtual inline void AddBias(double val) { -#pragma omp parallel for schedule(static, 1024) if (num_leaves_ >= 2048) +#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static, 1024) if (num_leaves_ >= 2048) for (int i = 0; i < num_leaves_ - 1; ++i) { leaf_value_[i] = MaybeRoundToZero(leaf_value_[i] + val); internal_value_[i] = MaybeRoundToZero(internal_value_[i] + val); @@ -218,7 +218,7 @@ class Tree { leaf_value_[num_leaves_ - 1] = MaybeRoundToZero(leaf_value_[num_leaves_ - 1] + val); if (is_linear_) { -#pragma omp parallel for schedule(static, 1024) if (num_leaves_ >= 2048) +#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static, 1024) if (num_leaves_ >= 2048) for (int i = 0; i < num_leaves_ - 1; ++i) { leaf_const_[i] = MaybeRoundToZero(leaf_const_[i] + val); } diff --git a/include/LightGBM/utils/common.h b/include/LightGBM/utils/common.h index 4e82734858f3..f1b5a10b5a69 100644 --- a/include/LightGBM/utils/common.h +++ b/include/LightGBM/utils/common.h @@ -691,7 +691,7 @@ static void ParallelSort(_RanIt _First, _RanIt _Last, _Pr _Pred, _VTRanIt*) { size_t inner_size = (len + num_threads - 1) / num_threads; inner_size = std::max(inner_size, kMinInnerLen); num_threads = static_cast((len + inner_size - 1) / inner_size); -#pragma omp parallel for schedule(static, 1) +#pragma omp parallel for num_threads(num_threads) schedule(static, 1) for (int i = 0; i < num_threads; ++i) { size_t left = inner_size*i; size_t right = left + inner_size; @@ -707,7 +707,7 @@ static void ParallelSort(_RanIt _First, _RanIt _Last, _Pr _Pred, _VTRanIt*) { // Recursive merge while (s < len) { int loop_size = static_cast((len + s * 2 - 1) / (s * 2)); - #pragma omp parallel for schedule(static, 1) + #pragma omp parallel for num_threads(num_threads) schedule(static, 1) for (int i = 0; i < loop_size; ++i) { size_t left = i * 2 * s; size_t mid = left + s; diff --git a/include/LightGBM/utils/openmp_wrapper.h b/include/LightGBM/utils/openmp_wrapper.h index a337fc353b75..b9a8ea2982fc 100644 --- a/include/LightGBM/utils/openmp_wrapper.h +++ b/include/LightGBM/utils/openmp_wrapper.h @@ -5,6 +5,15 @@ #ifndef LIGHTGBM_OPENMP_WRAPPER_H_ #define LIGHTGBM_OPENMP_WRAPPER_H_ +#include + +// this can only be changed by LGBM_SetMaxThreads() +LIGHTGBM_EXTERN_C int LGBM_MAX_NUM_THREADS; + +// this is modified by OMP_SET_NUM_THREADS(), for example +// by passing num_thread through params +LIGHTGBM_EXTERN_C int LGBM_DEFAULT_NUM_THREADS; + #ifdef _OPENMP #include @@ -17,22 +26,25 @@ #include #include -inline int OMP_NUM_THREADS() { - int ret = 1; -#pragma omp parallel -#pragma omp master - { ret = omp_get_num_threads(); } - return ret; -} - -inline void OMP_SET_NUM_THREADS(int num_threads) { - static const int default_omp_num_threads = OMP_NUM_THREADS(); - if (num_threads > 0) { - omp_set_num_threads(num_threads); - } else { - omp_set_num_threads(default_omp_num_threads); - } -} +/* + Get number of threads to use in OpenMP parallel regions. + + By default, this will return the result of omp_get_max_threads(), + which is OpenMP-implementation dependent but generally can be controlled + by environment variable OMP_NUM_THREADS. + + ref: + - https://www.openmp.org/spec-html/5.0/openmpsu112.html + - https://gcc.gnu.org/onlinedocs/libgomp/omp_005fget_005fmax_005fthreads.html +*/ +LIGHTGBM_EXTERN_C int OMP_NUM_THREADS(); + +/* + Update the default number of threads that'll be used in OpenMP parallel + regions for LightGBM routines where the number of threads aren't directly + supplied. +*/ +LIGHTGBM_EXTERN_C void OMP_SET_NUM_THREADS(int num_threads); class ThreadExceptionHelper { public: @@ -102,10 +114,7 @@ class ThreadExceptionHelper { /** Fall here if no OPENMP support, so just simulate a single thread running. All #pragma omp should be ignored by the compiler **/ - inline void omp_set_num_threads(int) __GOMP_NOTHROW {} // NOLINT (no cast done here) inline void OMP_SET_NUM_THREADS(int) __GOMP_NOTHROW {} - inline int omp_get_num_threads() __GOMP_NOTHROW {return 1;} - inline int omp_get_max_threads() __GOMP_NOTHROW {return 1;} inline int omp_get_thread_num() __GOMP_NOTHROW {return 0;} inline int OMP_NUM_THREADS() __GOMP_NOTHROW { return 1; } #ifdef __cplusplus diff --git a/include/LightGBM/utils/threading.h b/include/LightGBM/utils/threading.h index a093f87c1c8b..41362ee5db49 100644 --- a/include/LightGBM/utils/threading.h +++ b/include/LightGBM/utils/threading.h @@ -73,7 +73,7 @@ class Threading { INDEX_T num_inner = end - start; BlockInfo(num_inner, min_block_size, &n_block, &num_inner); OMP_INIT_EX(); -#pragma omp parallel for schedule(static, 1) +#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static, 1) for (int i = 0; i < n_block; ++i) { OMP_LOOP_EX_BEGIN(); INDEX_T inner_start = start + num_inner * i; diff --git a/python-package/lightgbm/basic.py b/python-package/lightgbm/basic.py index dac3224a54b2..19a53c6bb70f 100644 --- a/python-package/lightgbm/basic.py +++ b/python-package/lightgbm/basic.py @@ -18,12 +18,21 @@ import numpy as np import scipy.sparse -from .compat import PANDAS_INSTALLED, concat, dt_DataTable, pd_CategoricalDtype, pd_DataFrame, pd_Series +from .compat import (PANDAS_INSTALLED, PYARROW_INSTALLED, arrow_cffi, arrow_is_floating, arrow_is_integer, concat, + dt_DataTable, pa_Array, pa_chunked_array, pa_ChunkedArray, pa_compute, pa_Table, + pd_CategoricalDtype, pd_DataFrame, pd_Series) from .libpath import find_lib_path if TYPE_CHECKING: from typing import Literal + # typing.TypeGuard was only introduced in Python 3.10 + try: + from typing import TypeGuard + except ImportError: + from typing_extensions import TypeGuard + + __all__ = [ 'Booster', 'Dataset', @@ -61,7 +70,9 @@ List[float], List[int], np.ndarray, - pd_Series + pd_Series, + pa_Array, + pa_ChunkedArray, ] _LGBM_PositionType = Union[ np.ndarray, @@ -73,6 +84,9 @@ np.ndarray, pd_Series, pd_DataFrame, + pa_Table, + pa_Array, + pa_ChunkedArray, ] _LGBM_TrainDataType = Union[ str, @@ -83,14 +97,17 @@ scipy.sparse.spmatrix, "Sequence", List["Sequence"], - List[np.ndarray] + List[np.ndarray], + pa_Table ] _LGBM_LabelType = Union[ List[float], List[int], np.ndarray, pd_Series, - pd_DataFrame + pd_DataFrame, + pa_Array, + pa_ChunkedArray, ] _LGBM_PredictDataType = Union[ str, @@ -98,13 +115,16 @@ np.ndarray, pd_DataFrame, dt_DataTable, - scipy.sparse.spmatrix + scipy.sparse.spmatrix, + pa_Table, ] _LGBM_WeightType = Union[ List[float], List[int], np.ndarray, - pd_Series + pd_Series, + pa_Array, + pa_ChunkedArray, ] ZERO_THRESHOLD = 1e-35 @@ -279,6 +299,20 @@ def _is_1d_list(data: Any) -> bool: return isinstance(data, list) and (not data or _is_numeric(data[0])) +def _is_list_of_numpy_arrays(data: Any) -> "TypeGuard[List[np.ndarray]]": + return ( + isinstance(data, list) + and all(isinstance(x, np.ndarray) for x in data) + ) + + +def _is_list_of_sequences(data: Any) -> "TypeGuard[List[Sequence]]": + return ( + isinstance(data, list) + and all(isinstance(x, Sequence) for x in data) + ) + + def _is_1d_collection(data: Any) -> bool: """Check whether data is a 1-D collection.""" return ( @@ -330,6 +364,68 @@ def _is_2d_collection(data: Any) -> bool: ) +def _is_pyarrow_array(data: Any) -> bool: + """Check whether data is a PyArrow array.""" + return isinstance(data, (pa_Array, pa_ChunkedArray)) + + +def _is_pyarrow_table(data: Any) -> bool: + """Check whether data is a PyArrow table.""" + return isinstance(data, pa_Table) + + +class _ArrowCArray: + """Simple wrapper around the C representation of an Arrow type.""" + + n_chunks: int + chunks: arrow_cffi.CData + schema: arrow_cffi.CData + + def __init__(self, n_chunks: int, chunks: arrow_cffi.CData, schema: arrow_cffi.CData): + self.n_chunks = n_chunks + self.chunks = chunks + self.schema = schema + + @property + def chunks_ptr(self) -> int: + """Returns the address of the pointer to the list of chunks making up the array.""" + return int(arrow_cffi.cast("uintptr_t", arrow_cffi.addressof(self.chunks[0]))) + + @property + def schema_ptr(self) -> int: + """Returns the address of the pointer to the schema of the array.""" + return int(arrow_cffi.cast("uintptr_t", self.schema)) + + +def _export_arrow_to_c(data: pa_Table) -> _ArrowCArray: + """Export an Arrow type to its C representation.""" + # Obtain objects to export + if isinstance(data, pa_Array): + export_objects = [data] + elif isinstance(data, pa_ChunkedArray): + export_objects = data.chunks + elif isinstance(data, pa_Table): + export_objects = data.to_batches() + else: + raise ValueError(f"data of type '{type(data)}' cannot be exported to Arrow") + + # Prepare export + chunks = arrow_cffi.new("struct ArrowArray[]", len(export_objects)) + schema = arrow_cffi.new("struct ArrowSchema*") + + # Export all objects + for i, obj in enumerate(export_objects): + chunk_ptr = int(arrow_cffi.cast("uintptr_t", arrow_cffi.addressof(chunks[i]))) + if i == 0: + schema_ptr = int(arrow_cffi.cast("uintptr_t", schema)) + obj._export_to_c(chunk_ptr, schema_ptr) + else: + obj._export_to_c(chunk_ptr) + + return _ArrowCArray(len(chunks), chunks, schema) + + + def _data_to_2d_numpy( data: Any, dtype: "np.typing.DTypeLike", @@ -347,7 +443,7 @@ def _data_to_2d_numpy( "It should be list of lists, numpy 2-D array or pandas DataFrame") -def _cfloat32_array_to_numpy(cptr: "ctypes._Pointer", length: int) -> np.ndarray: +def _cfloat32_array_to_numpy(*, cptr: "ctypes._Pointer", length: int) -> np.ndarray: """Convert a ctypes float pointer array to a numpy array.""" if isinstance(cptr, ctypes.POINTER(ctypes.c_float)): return np.ctypeslib.as_array(cptr, shape=(length,)).copy() @@ -355,7 +451,7 @@ def _cfloat32_array_to_numpy(cptr: "ctypes._Pointer", length: int) -> np.ndarray raise RuntimeError('Expected float pointer') -def _cfloat64_array_to_numpy(cptr: "ctypes._Pointer", length: int) -> np.ndarray: +def _cfloat64_array_to_numpy(*, cptr: "ctypes._Pointer", length: int) -> np.ndarray: """Convert a ctypes double pointer array to a numpy array.""" if isinstance(cptr, ctypes.POINTER(ctypes.c_double)): return np.ctypeslib.as_array(cptr, shape=(length,)).copy() @@ -363,7 +459,7 @@ def _cfloat64_array_to_numpy(cptr: "ctypes._Pointer", length: int) -> np.ndarray raise RuntimeError('Expected double pointer') -def _cint32_array_to_numpy(cptr: "ctypes._Pointer", length: int) -> np.ndarray: +def _cint32_array_to_numpy(*, cptr: "ctypes._Pointer", length: int) -> np.ndarray: """Convert a ctypes int pointer array to a numpy array.""" if isinstance(cptr, ctypes.POINTER(ctypes.c_int32)): return np.ctypeslib.as_array(cptr, shape=(length,)).copy() @@ -371,7 +467,7 @@ def _cint32_array_to_numpy(cptr: "ctypes._Pointer", length: int) -> np.ndarray: raise RuntimeError('Expected int32 pointer') -def _cint64_array_to_numpy(cptr: "ctypes._Pointer", length: int) -> np.ndarray: +def _cint64_array_to_numpy(*, cptr: "ctypes._Pointer", length: int) -> np.ndarray: """Convert a ctypes int pointer array to a numpy array.""" if isinstance(cptr, ctypes.POINTER(ctypes.c_int64)): return np.ctypeslib.as_array(cptr, shape=(length,)).copy() @@ -458,7 +554,7 @@ def _get_all_param_aliases() -> Dict[str, List[str]]: buffer_len = 1 << 20 tmp_out_len = ctypes.c_int64(0) string_buffer = ctypes.create_string_buffer(buffer_len) - ptr_string_buffer = ctypes.c_char_p(*[ctypes.addressof(string_buffer)]) + ptr_string_buffer = ctypes.c_char_p(ctypes.addressof(string_buffer)) _safe_call(_LIB.LGBM_DumpParamAliases( ctypes.c_int64(buffer_len), ctypes.byref(tmp_out_len), @@ -467,7 +563,7 @@ def _get_all_param_aliases() -> Dict[str, List[str]]: # if buffer length is not long enough, re-allocate a buffer if actual_len > buffer_len: string_buffer = ctypes.create_string_buffer(actual_len) - ptr_string_buffer = ctypes.c_char_p(*[ctypes.addressof(string_buffer)]) + ptr_string_buffer = ctypes.c_char_p(ctypes.addressof(string_buffer)) _safe_call(_LIB.LGBM_DumpParamAliases( ctypes.c_int64(actual_len), ctypes.byref(tmp_out_len), @@ -668,22 +764,43 @@ def _check_for_bad_pandas_dtypes(pandas_dtypes_series: pd_Series) -> None: f'Fields with bad pandas dtypes: {", ".join(bad_pandas_dtypes)}') +def _pandas_to_numpy( + data: pd_DataFrame, + target_dtype: "np.typing.DTypeLike" +) -> np.ndarray: + _check_for_bad_pandas_dtypes(data.dtypes) + try: + # most common case (no nullable dtypes) + return data.to_numpy(dtype=target_dtype, copy=False) + except TypeError: + # 1.0 <= pd version < 1.1 and nullable dtypes, least common case + # raises error because array is casted to type(pd.NA) and there's no na_value argument + return data.astype(target_dtype, copy=False).values + except ValueError: + # data has nullable dtypes, but we can specify na_value argument and copy will be made + return data.to_numpy(dtype=target_dtype, na_value=np.nan) + + def _data_from_pandas( data: pd_DataFrame, feature_name: _LGBM_FeatureNameConfiguration, categorical_feature: _LGBM_CategoricalFeatureConfiguration, pandas_categorical: Optional[List[List]] -) -> Tuple[np.ndarray, List[str], List[str], List[List]]: +) -> Tuple[np.ndarray, List[str], Union[List[str], List[int]], List[List]]: if len(data.shape) != 2 or data.shape[0] < 1: raise ValueError('Input data must be 2 dimensional and non empty.') + # take shallow copy in case we modify categorical columns + # whole column modifications don't change the original df + data = data.copy(deep=False) + # determine feature names if feature_name == 'auto': feature_name = [str(col) for col in data.columns] # determine categorical features cat_cols = [col for col, dtype in zip(data.columns, data.dtypes) if isinstance(dtype, pd_CategoricalDtype)] - cat_cols_not_ordered = [col for col in cat_cols if not data[col].cat.ordered] + cat_cols_not_ordered: List[str] = [col for col in cat_cols if not data[col].cat.ordered] if pandas_categorical is None: # train dataset pandas_categorical = [list(data[col].cat.categories) for col in cat_cols] else: @@ -693,29 +810,23 @@ def _data_from_pandas( if list(data[col].cat.categories) != list(category): data[col] = data[col].cat.set_categories(category) if len(cat_cols): # cat_cols is list - data = data.copy(deep=False) # not alter origin DataFrame data[cat_cols] = data[cat_cols].apply(lambda x: x.cat.codes).replace({-1: np.nan}) - if categorical_feature == 'auto': # use cat cols from DataFrame + + # use cat cols from DataFrame + if categorical_feature == 'auto': categorical_feature = cat_cols_not_ordered - else: # use cat cols specified by user - categorical_feature = list(categorical_feature) # type: ignore[assignment] - # get numpy representation of the data - _check_for_bad_pandas_dtypes(data.dtypes) df_dtypes = [dtype.type for dtype in data.dtypes] - df_dtypes.append(np.float32) # so that the target dtype considers floats + # so that the target dtype considers floats + df_dtypes.append(np.float32) target_dtype = np.result_type(*df_dtypes) - try: - # most common case (no nullable dtypes) - data = data.to_numpy(dtype=target_dtype, copy=False) - except TypeError: - # 1.0 <= pd version < 1.1 and nullable dtypes, least common case - # raises error because array is casted to type(pd.NA) and there's no na_value argument - data = data.astype(target_dtype, copy=False).values - except ValueError: - # data has nullable dtypes, but we can specify na_value argument and copy will be made - data = data.to_numpy(dtype=target_dtype, na_value=np.nan) - return data, feature_name, categorical_feature, pandas_categorical + + return ( + _pandas_to_numpy(data, target_dtype=target_dtype), + feature_name, + categorical_feature, + pandas_categorical + ) def _dump_pandas_categorical( @@ -959,7 +1070,7 @@ def predict( Parameters ---------- - data : str, pathlib.Path, numpy array, pandas DataFrame, H2O DataTable's Frame or scipy.sparse + data : str, pathlib.Path, numpy array, pandas DataFrame, pyarrow Table, H2O DataTable's Frame or scipy.sparse Data source for prediction. If str or pathlib.Path, it represents the path to a text file (CSV, TSV, or LibSVM). start_iteration : int, optional (default=0) @@ -1051,6 +1162,13 @@ def predict( num_iteration=num_iteration, predict_type=predict_type ) + elif _is_pyarrow_table(data): + preds, nrow = self.__pred_for_pyarrow_table( + table=data, + start_iteration=start_iteration, + num_iteration=num_iteration, + predict_type=predict_type + ) elif isinstance(data, list): try: data = np.array(data) @@ -1208,18 +1326,18 @@ def __create_sparse_native( data_indices_len = out_shape[0] indptr_len = out_shape[1] if indptr_type == _C_API_DTYPE_INT32: - out_indptr = _cint32_array_to_numpy(out_ptr_indptr, indptr_len) + out_indptr = _cint32_array_to_numpy(cptr=out_ptr_indptr, length=indptr_len) elif indptr_type == _C_API_DTYPE_INT64: - out_indptr = _cint64_array_to_numpy(out_ptr_indptr, indptr_len) + out_indptr = _cint64_array_to_numpy(cptr=out_ptr_indptr, length=indptr_len) else: raise TypeError("Expected int32 or int64 type for indptr") if data_type == _C_API_DTYPE_FLOAT32: - out_data = _cfloat32_array_to_numpy(out_ptr_data, data_indices_len) + out_data = _cfloat32_array_to_numpy(cptr=out_ptr_data, length=data_indices_len) elif data_type == _C_API_DTYPE_FLOAT64: - out_data = _cfloat64_array_to_numpy(out_ptr_data, data_indices_len) + out_data = _cfloat64_array_to_numpy(cptr=out_ptr_data, length=data_indices_len) else: raise TypeError("Expected float32 or float64 type for data") - out_indices = _cint32_array_to_numpy(out_ptr_indices, data_indices_len) + out_indices = _cint32_array_to_numpy(cptr=out_ptr_indices, length=data_indices_len) # break up indptr based on number of rows (note more than one matrix in multiclass case) per_class_indptr_shape = cs.indptr.shape[0] # for CSC there is extra column added @@ -1504,6 +1622,48 @@ def __pred_for_csc( if n_preds != out_num_preds.value: raise ValueError("Wrong length for predict results") return preds, nrow + + def __pred_for_pyarrow_table( + self, + table: pa_Table, + start_iteration: int, + num_iteration: int, + predict_type: int + ) -> Tuple[np.ndarray, int]: + """Predict for a PyArrow table.""" + if not PYARROW_INSTALLED: + raise LightGBMError("Cannot predict from Arrow without `pyarrow` installed.") + + # Check that the input is valid: we only handle numbers (for now) + if not all(arrow_is_integer(t) or arrow_is_floating(t) for t in table.schema.types): + raise ValueError("Arrow table may only have integer or floating point datatypes") + + # Prepare prediction output array + n_preds = self.__get_num_preds( + start_iteration=start_iteration, + num_iteration=num_iteration, + nrow=table.num_rows, + predict_type=predict_type + ) + preds = np.empty(n_preds, dtype=np.float64) + out_num_preds = ctypes.c_int64(0) + + # Export Arrow table to C and run prediction + c_array = _export_arrow_to_c(table) + _safe_call(_LIB.LGBM_BoosterPredictForArrow( + self._handle, + ctypes.c_int64(c_array.n_chunks), + ctypes.c_void_p(c_array.chunks_ptr), + ctypes.c_void_p(c_array.schema_ptr), + ctypes.c_int(predict_type), + ctypes.c_int(start_iteration), + ctypes.c_int(num_iteration), + _c_str(self.pred_parameter), + ctypes.byref(out_num_preds), + preds.ctypes.data_as(ctypes.POINTER(ctypes.c_double)))) + if n_preds != out_num_preds.value: + raise ValueError("Wrong length for predict results") + return preds, table.num_rows def current_iteration(self) -> int: """Get the index of the current iteration. @@ -1541,26 +1701,26 @@ def __init__( Parameters ---------- - data : str, pathlib.Path, numpy array, pandas DataFrame, H2O DataTable's Frame, scipy.sparse, Sequence, list of Sequence or list of numpy array + data : str, pathlib.Path, numpy array, pandas DataFrame, H2O DataTable's Frame, scipy.sparse, Sequence, list of Sequence, list of numpy array or pyarrow Table Data source of Dataset. If str or pathlib.Path, it represents the path to a text file (CSV, TSV, or LibSVM) or a LightGBM Dataset binary file. - label : list, numpy 1-D array, pandas Series / one-column DataFrame or None, optional (default=None) + label : list, numpy 1-D array, pandas Series / one-column DataFrame, pyarrow Array, pyarrow ChunkedArray or None, optional (default=None) Label of the data. reference : Dataset or None, optional (default=None) If this is Dataset for validation, training data should be used as reference. - weight : list, numpy 1-D array, pandas Series or None, optional (default=None) + weight : list, numpy 1-D array, pandas Series, pyarrow Array, pyarrow ChunkedArray or None, optional (default=None) Weight for each instance. Weights should be non-negative. - group : list, numpy 1-D array, pandas Series or None, optional (default=None) + group : list, numpy 1-D array, pandas Series, pyarrow Array, pyarrow ChunkedArray or None, optional (default=None) Group/query data. Only used in the learning-to-rank task. sum(group) = n_samples. For example, if you have a 100-document dataset with ``group = [10, 20, 40, 10, 10, 10]``, that means that you have 6 groups, where the first 10 records are in the first group, records 11-30 are in the second group, records 31-70 are in the third group, etc. - init_score : list, list of lists (for multi-class task), numpy array, pandas Series, pandas DataFrame (for multi-class task), or None, optional (default=None) + init_score : list, list of lists (for multi-class task), numpy array, pandas Series, pandas DataFrame (for multi-class task), pyarrow Array, pyarrow ChunkedArray, pyarrow Table (for multi-class task) or None, optional (default=None) Init score for Dataset. feature_name : list of str, or 'auto', optional (default="auto") Feature names. - If 'auto' and data is pandas DataFrame, data columns names are used. + If 'auto' and data is pandas DataFrame or pyarrow Table, data columns names are used. categorical_feature : list of str or int, or 'auto', optional (default="auto") Categorical features. If list of int, interpreted as indices. @@ -1917,10 +2077,13 @@ def _lazy_init( self.__init_from_csc(data, params_str, ref_dataset) elif isinstance(data, np.ndarray): self.__init_from_np2d(data, params_str, ref_dataset) + elif _is_pyarrow_table(data): + self.__init_from_pyarrow_table(data, params_str, ref_dataset) + feature_name = data.column_names elif isinstance(data, list) and len(data) > 0: - if all(isinstance(x, np.ndarray) for x in data): + if _is_list_of_numpy_arrays(data): self.__init_from_list_np2d(data, params_str, ref_dataset) - elif all(isinstance(x, Sequence) for x in data): + elif _is_list_of_sequences(data): self.__init_from_seqs(data, ref_dataset) else: raise TypeError('Data list can only be of ndarray or Sequence') @@ -2177,10 +2340,36 @@ def __init_from_csc( ctypes.byref(self._handle))) return self + def __init_from_pyarrow_table( + self, + table: pa_Table, + params_str: str, + ref_dataset: Optional[_DatasetHandle] + ) -> "Dataset": + """Initialize data from a PyArrow table.""" + if not PYARROW_INSTALLED: + raise LightGBMError("Cannot init dataframe from Arrow without `pyarrow` installed.") + + # Check that the input is valid: we only handle numbers (for now) + if not all(arrow_is_integer(t) or arrow_is_floating(t) for t in table.schema.types): + raise ValueError("Arrow table may only have integer or floating point datatypes") + + # Export Arrow table to C + c_array = _export_arrow_to_c(table) + self._handle = ctypes.c_void_p() + _safe_call(_LIB.LGBM_DatasetCreateFromArrow( + ctypes.c_int64(c_array.n_chunks), + ctypes.c_void_p(c_array.chunks_ptr), + ctypes.c_void_p(c_array.schema_ptr), + _c_str(params_str), + ref_dataset, + ctypes.byref(self._handle))) + return self + @staticmethod def _compare_params_for_warning( - params: Optional[Dict[str, Any]], - other_params: Optional[Dict[str, Any]], + params: Dict[str, Any], + other_params: Dict[str, Any], ignore_keys: Set[str] ) -> bool: """Compare two dictionaries with params ignoring some keys. @@ -2189,9 +2378,9 @@ def _compare_params_for_warning( Parameters ---------- - params : dict or None + params : dict One dictionary with parameters to compare. - other_params : dict or None + other_params : dict Another dictionary with parameters to compare. ignore_keys : set Keys that should be ignored during comparing two dictionaries. @@ -2201,10 +2390,6 @@ def _compare_params_for_warning( compare_result : bool Returns whether two dictionaries with params are equal. """ - if params is None: - params = {} - if other_params is None: - other_params = {} for k in other_params: if k not in ignore_keys: if k not in params or params[k] != other_params[k]: @@ -2301,17 +2486,17 @@ def create_valid( data : str, pathlib.Path, numpy array, pandas DataFrame, H2O DataTable's Frame, scipy.sparse, Sequence, list of Sequence or list of numpy array Data source of Dataset. If str or pathlib.Path, it represents the path to a text file (CSV, TSV, or LibSVM) or a LightGBM Dataset binary file. - label : list, numpy 1-D array, pandas Series / one-column DataFrame or None, optional (default=None) + label : list, numpy 1-D array, pandas Series / one-column DataFrame, pyarrow Array, pyarrow ChunkedArray or None, optional (default=None) Label of the data. - weight : list, numpy 1-D array, pandas Series or None, optional (default=None) + weight : list, numpy 1-D array, pandas Series, pyarrow Array, pyarrow ChunkedArray or None, optional (default=None) Weight for each instance. Weights should be non-negative. - group : list, numpy 1-D array, pandas Series or None, optional (default=None) + group : list, numpy 1-D array, pandas Series, pyarrow Array, pyarrow ChunkedArray or None, optional (default=None) Group/query data. Only used in the learning-to-rank task. sum(group) = n_samples. For example, if you have a 100-document dataset with ``group = [10, 20, 40, 10, 10, 10]``, that means that you have 6 groups, where the first 10 records are in the first group, records 11-30 are in the second group, records 31-70 are in the third group, etc. - init_score : list, list of lists (for multi-class task), numpy array, pandas Series, pandas DataFrame (for multi-class task), or None, optional (default=None) + init_score : list, list of lists (for multi-class task), numpy array, pandas Series, pandas DataFrame (for multi-class task), pyarrow Array, pyarrow ChunkedArray, pyarrow Table (for multi-class task) or None, optional (default=None) Init score for Dataset. params : dict or None, optional (default=None) Other parameters for validation Dataset. @@ -2418,7 +2603,7 @@ def _reverse_update_params(self) -> "Dataset": def set_field( self, field_name: str, - data: Optional[Union[List[List[float]], List[List[int]], List[float], List[int], np.ndarray, pd_Series, pd_DataFrame]] + data: Optional[Union[List[List[float]], List[List[int]], List[float], List[int], np.ndarray, pd_Series, pd_DataFrame, pa_Table, pa_Array, pa_ChunkedArray]] ) -> "Dataset": """Set property into the Dataset. @@ -2426,7 +2611,7 @@ def set_field( ---------- field_name : str The field name of the information. - data : list, list of lists (for multi-class task), numpy array, pandas Series, pandas DataFrame (for multi-class task), or None + data : list, list of lists (for multi-class task), numpy array, pandas Series, pandas DataFrame (for multi-class task), pyarrow Array, pyarrow ChunkedArray or None The data to be set. Returns @@ -2445,6 +2630,29 @@ def set_field( ctypes.c_int(0), ctypes.c_int(_FIELD_TYPE_MAPPER[field_name]))) return self + + # If the data is a arrow data, we can just pass it to C + if _is_pyarrow_array(data) or _is_pyarrow_table(data): + # If a table is being passed, we concatenate the columns. This is only valid for + # 'init_score'. + if _is_pyarrow_table(data): + if field_name != "init_score": + raise ValueError(f"pyarrow tables are not supported for field '{field_name}'") + data = pa_chunked_array([ + chunk for array in data.columns for chunk in array.chunks # type: ignore + ]) + + c_array = _export_arrow_to_c(data) + _safe_call(_LIB.LGBM_DatasetSetFieldFromArrow( + self._handle, + _c_str(field_name), + ctypes.c_int64(c_array.n_chunks), + ctypes.c_void_p(c_array.chunks_ptr), + ctypes.c_void_p(c_array.schema_ptr), + )) + self.version += 1 + return self + dtype: "np.typing.DTypeLike" if field_name == 'init_score': dtype = np.float64 @@ -2483,6 +2691,12 @@ def set_field( def get_field(self, field_name: str) -> Optional[np.ndarray]: """Get property from the Dataset. + Can only be run on a constructed Dataset. + + Unlike ``get_group()``, ``get_init_score()``, ``get_label()``, ``get_position()``, and ``get_weight()``, + this method ignores any raw data passed into ``lgb.Dataset()`` on the Python side, and will only read + data from the constructed C++ ``Dataset`` object. + Parameters ---------- field_name : str @@ -2509,11 +2723,20 @@ def get_field(self, field_name: str) -> Optional[np.ndarray]: if tmp_out_len.value == 0: return None if out_type.value == _C_API_DTYPE_INT32: - arr = _cint32_array_to_numpy(ctypes.cast(ret, ctypes.POINTER(ctypes.c_int32)), tmp_out_len.value) + arr = _cint32_array_to_numpy( + cptr=ctypes.cast(ret, ctypes.POINTER(ctypes.c_int32)), + length=tmp_out_len.value + ) elif out_type.value == _C_API_DTYPE_FLOAT32: - arr = _cfloat32_array_to_numpy(ctypes.cast(ret, ctypes.POINTER(ctypes.c_float)), tmp_out_len.value) + arr = _cfloat32_array_to_numpy( + cptr=ctypes.cast(ret, ctypes.POINTER(ctypes.c_float)), + length=tmp_out_len.value + ) elif out_type.value == _C_API_DTYPE_FLOAT64: - arr = _cfloat64_array_to_numpy(ctypes.cast(ret, ctypes.POINTER(ctypes.c_double)), tmp_out_len.value) + arr = _cfloat64_array_to_numpy( + cptr=ctypes.cast(ret, ctypes.POINTER(ctypes.c_double)), + length=tmp_out_len.value + ) else: raise TypeError("Unknown type") if field_name == 'init_score': @@ -2646,7 +2869,7 @@ def set_label(self, label: Optional[_LGBM_LabelType]) -> "Dataset": Parameters ---------- - label : list, numpy 1-D array, pandas Series / one-column DataFrame or None + label : list, numpy 1-D array, pandas Series / one-column DataFrame, pyarrow Array, pyarrow ChunkedArray or None The label information to be set into Dataset. Returns @@ -2659,18 +2882,9 @@ def set_label(self, label: Optional[_LGBM_LabelType]) -> "Dataset": if isinstance(label, pd_DataFrame): if len(label.columns) > 1: raise ValueError('DataFrame for label cannot have multiple columns') - _check_for_bad_pandas_dtypes(label.dtypes) - try: - # most common case (no nullable dtypes) - label = label.to_numpy(dtype=np.float32, copy=False) - except TypeError: - # 1.0 <= pd version < 1.1 and nullable dtypes, least common case - # raises error because array is casted to type(pd.NA) and there's no na_value argument - label = label.astype(np.float32, copy=False).values - except ValueError: - # data has nullable dtypes, but we can specify na_value argument and copy will be made - label = label.to_numpy(dtype=np.float32, na_value=np.nan) - label_array = np.ravel(label) + label_array = np.ravel(_pandas_to_numpy(label, target_dtype=np.float32)) + elif _is_pyarrow_array(label): + label_array = label else: label_array = _list_to_1d_numpy(label, dtype=np.float32, name='label') self.set_field('label', label_array) @@ -2685,7 +2899,7 @@ def set_weight( Parameters ---------- - weight : list, numpy 1-D array, pandas Series or None + weight : list, numpy 1-D array, pandas Series, pyarrow Array, pyarrow ChunkedArray or None Weight to be set for each data point. Weights should be non-negative. Returns @@ -2693,11 +2907,19 @@ def set_weight( self : Dataset Dataset with set weight. """ - if weight is not None and np.all(weight == 1): - weight = None + # Check if the weight contains values other than one + if weight is not None: + if _is_pyarrow_array(weight): + if pa_compute.all(pa_compute.equal(weight, 1)).as_py(): + weight = None + elif np.all(weight == 1): + weight = None self.weight = weight + + # Set field if self._handle is not None and weight is not None: - weight = _list_to_1d_numpy(weight, dtype=np.float32, name='weight') + if not _is_pyarrow_array(weight): + weight = _list_to_1d_numpy(weight, dtype=np.float32, name='weight') self.set_field('weight', weight) self.weight = self.get_field('weight') # original values can be modified at cpp side return self @@ -2710,7 +2932,7 @@ def set_init_score( Parameters ---------- - init_score : list, list of lists (for multi-class task), numpy array, pandas Series, pandas DataFrame (for multi-class task), or None + init_score : list, list of lists (for multi-class task), numpy array, pandas Series, pandas DataFrame (for multi-class task), pyarrow Array, pyarrow ChunkedArray, pyarrow Table (for multi-class task) or None Init score for Booster. Returns @@ -2732,7 +2954,7 @@ def set_group( Parameters ---------- - group : list, numpy 1-D array, pandas Series or None + group : list, numpy 1-D array, pandas Series, pyarrow Array, pyarrow ChunkedArray or None Group/query data. Only used in the learning-to-rank task. sum(group) = n_samples. @@ -2746,8 +2968,13 @@ def set_group( """ self.group = group if self._handle is not None and group is not None: - group = _list_to_1d_numpy(group, dtype=np.int32, name='group') + if not _is_pyarrow_array(group): + group = _list_to_1d_numpy(group, dtype=np.int32, name='group') self.set_field('group', group) + # original values can be modified at cpp side + constructed_group = self.get_field('group') + if constructed_group is not None: + self.group = np.diff(constructed_group) return self def set_position( @@ -2787,7 +3014,7 @@ def get_feature_name(self) -> List[str]: reserved_string_buffer_size = 255 required_string_buffer_size = ctypes.c_size_t(0) string_buffers = [ctypes.create_string_buffer(reserved_string_buffer_size) for _ in range(num_feature)] - ptr_string_buffers = (ctypes.c_char_p * num_feature)(*map(ctypes.addressof, string_buffers)) + ptr_string_buffers = (ctypes.c_char_p * num_feature)(*map(ctypes.addressof, string_buffers)) # type: ignore[misc] _safe_call(_LIB.LGBM_DatasetGetFeatureNames( self._handle, ctypes.c_int(num_feature), @@ -2801,7 +3028,7 @@ def get_feature_name(self) -> List[str]: # if buffer length is not long enough, reallocate buffers if reserved_string_buffer_size < actual_string_buffer_size: string_buffers = [ctypes.create_string_buffer(actual_string_buffer_size) for _ in range(num_feature)] - ptr_string_buffers = (ctypes.c_char_p * num_feature)(*map(ctypes.addressof, string_buffers)) + ptr_string_buffers = (ctypes.c_char_p * num_feature)(*map(ctypes.addressof, string_buffers)) # type: ignore[misc] _safe_call(_LIB.LGBM_DatasetGetFeatureNames( self._handle, ctypes.c_int(num_feature), @@ -2811,37 +3038,40 @@ def get_feature_name(self) -> List[str]: ptr_string_buffers)) return [string_buffers[i].value.decode('utf-8') for i in range(num_feature)] - def get_label(self) -> Optional[np.ndarray]: + def get_label(self) -> Optional[_LGBM_LabelType]: """Get the label of the Dataset. Returns ------- - label : numpy array or None + label : list, numpy 1-D array, pandas Series / one-column DataFrame or None The label information from the Dataset. + For a constructed ``Dataset``, this will only return a numpy array. """ if self.label is None: self.label = self.get_field('label') return self.label - def get_weight(self) -> Optional[np.ndarray]: + def get_weight(self) -> Optional[_LGBM_WeightType]: """Get the weight of the Dataset. Returns ------- - weight : numpy array or None + weight : list, numpy 1-D array, pandas Series or None Weight for each data point from the Dataset. Weights should be non-negative. + For a constructed ``Dataset``, this will only return ``None`` or a numpy array. """ if self.weight is None: self.weight = self.get_field('weight') return self.weight - def get_init_score(self) -> Optional[np.ndarray]: + def get_init_score(self) -> Optional[_LGBM_InitScoreType]: """Get the initial score of the Dataset. Returns ------- - init_score : numpy array or None + init_score : list, list of lists (for multi-class task), numpy array, pandas Series, pandas DataFrame (for multi-class task), or None Init score of Booster. + For a constructed ``Dataset``, this will only return ``None`` or a numpy array. """ if self.init_score is None: self.init_score = self.get_field('init_score') @@ -2868,7 +3098,7 @@ def get_data(self) -> Optional[_LGBM_TrainDataType]: self.data = self.data[self.used_indices, :] elif isinstance(self.data, Sequence): self.data = self.data[self.used_indices] - elif isinstance(self.data, list) and len(self.data) > 0 and all(isinstance(x, Sequence) for x in self.data): + elif _is_list_of_sequences(self.data) and len(self.data) > 0: self.data = np.array(list(self._yield_row_from_seqlist(self.data, self.used_indices))) else: _log_warning(f"Cannot subset {type(self.data).__name__} type of raw data.\n" @@ -2879,17 +3109,18 @@ def get_data(self) -> Optional[_LGBM_TrainDataType]: "set free_raw_data=False when construct Dataset to avoid this.") return self.data - def get_group(self) -> Optional[np.ndarray]: + def get_group(self) -> Optional[_LGBM_GroupType]: """Get the group of the Dataset. Returns ------- - group : numpy array or None + group : list, numpy 1-D array, pandas Series or None Group/query data. Only used in the learning-to-rank task. sum(group) = n_samples. For example, if you have a 100-document dataset with ``group = [10, 20, 40, 10, 10, 10]``, that means that you have 6 groups, where the first 10 records are in the first group, records 11-30 are in the second group, records 31-70 are in the third group, etc. + For a constructed ``Dataset``, this will only return ``None`` or a numpy array. """ if self.group is None: self.group = self.get_field('group') @@ -2898,13 +3129,14 @@ def get_group(self) -> Optional[np.ndarray]: self.group = np.diff(self.group) return self.group - def get_position(self) -> Optional[np.ndarray]: + def get_position(self) -> Optional[_LGBM_PositionType]: """Get the position of the Dataset. Returns ------- - position : numpy 1-D array or None + position : numpy 1-D array, pandas Series or None Position of items used in unbiased learning-to-rank task. + For a constructed ``Dataset``, this will only return ``None`` or a numpy array. """ if self.position is None: self.position = self.get_field('position') @@ -3292,7 +3524,7 @@ def _get_loaded_param(self) -> Dict[str, Any]: buffer_len = 1 << 20 tmp_out_len = ctypes.c_int64(0) string_buffer = ctypes.create_string_buffer(buffer_len) - ptr_string_buffer = ctypes.c_char_p(*[ctypes.addressof(string_buffer)]) + ptr_string_buffer = ctypes.c_char_p(ctypes.addressof(string_buffer)) _safe_call(_LIB.LGBM_BoosterGetLoadedParam( self._handle, ctypes.c_int64(buffer_len), @@ -3302,7 +3534,7 @@ def _get_loaded_param(self) -> Dict[str, Any]: # if buffer length is not long enough, re-allocate a buffer if actual_len > buffer_len: string_buffer = ctypes.create_string_buffer(actual_len) - ptr_string_buffer = ctypes.c_char_p(*[ctypes.addressof(string_buffer)]) + ptr_string_buffer = ctypes.c_char_p(ctypes.addressof(string_buffer)) _safe_call(_LIB.LGBM_BoosterGetLoadedParam( self._handle, ctypes.c_int64(actual_len), @@ -4055,7 +4287,7 @@ def model_to_string( buffer_len = 1 << 20 tmp_out_len = ctypes.c_int64(0) string_buffer = ctypes.create_string_buffer(buffer_len) - ptr_string_buffer = ctypes.c_char_p(*[ctypes.addressof(string_buffer)]) + ptr_string_buffer = ctypes.c_char_p(ctypes.addressof(string_buffer)) _safe_call(_LIB.LGBM_BoosterSaveModelToString( self._handle, ctypes.c_int(start_iteration), @@ -4068,7 +4300,7 @@ def model_to_string( # if buffer length is not long enough, re-allocate a buffer if actual_len > buffer_len: string_buffer = ctypes.create_string_buffer(actual_len) - ptr_string_buffer = ctypes.c_char_p(*[ctypes.addressof(string_buffer)]) + ptr_string_buffer = ctypes.c_char_p(ctypes.addressof(string_buffer)) _safe_call(_LIB.LGBM_BoosterSaveModelToString( self._handle, ctypes.c_int(start_iteration), @@ -4123,7 +4355,7 @@ def dump_model( buffer_len = 1 << 20 tmp_out_len = ctypes.c_int64(0) string_buffer = ctypes.create_string_buffer(buffer_len) - ptr_string_buffer = ctypes.c_char_p(*[ctypes.addressof(string_buffer)]) + ptr_string_buffer = ctypes.c_char_p(ctypes.addressof(string_buffer)) _safe_call(_LIB.LGBM_BoosterDumpModel( self._handle, ctypes.c_int(start_iteration), @@ -4136,7 +4368,7 @@ def dump_model( # if buffer length is not long enough, reallocate a buffer if actual_len > buffer_len: string_buffer = ctypes.create_string_buffer(actual_len) - ptr_string_buffer = ctypes.c_char_p(*[ctypes.addressof(string_buffer)]) + ptr_string_buffer = ctypes.c_char_p(ctypes.addressof(string_buffer)) _safe_call(_LIB.LGBM_BoosterDumpModel( self._handle, ctypes.c_int(start_iteration), @@ -4166,7 +4398,7 @@ def predict( Parameters ---------- - data : str, pathlib.Path, numpy array, pandas DataFrame, H2O DataTable's Frame or scipy.sparse + data : str, pathlib.Path, numpy array, pandas DataFrame, pyarrow Table, H2O DataTable's Frame or scipy.sparse Data source for prediction. If str or pathlib.Path, it represents the path to a text file (CSV, TSV, or LibSVM). start_iteration : int, optional (default=0) @@ -4250,7 +4482,7 @@ def refit( data : str, pathlib.Path, numpy array, pandas DataFrame, H2O DataTable's Frame, scipy.sparse, Sequence, list of Sequence or list of numpy array Data source for refit. If str or pathlib.Path, it represents the path to a text file (CSV, TSV, or LibSVM). - label : list, numpy 1-D array or pandas Series / one-column DataFrame + label : list, numpy 1-D array, pandas Series / one-column DataFrame, pyarrow Array or pyarrow ChunkedArray Label for refit. decay_rate : float, optional (default=0.9) Decay rate of refit, @@ -4260,12 +4492,12 @@ def refit( .. versionadded:: 4.0.0 - weight : list, numpy 1-D array, pandas Series or None, optional (default=None) + weight : list, numpy 1-D array, pandas Series, pyarrow Array, pyarrow ChunkedArray or None, optional (default=None) Weight for each ``data`` instance. Weights should be non-negative. .. versionadded:: 4.0.0 - group : list, numpy 1-D array, pandas Series or None, optional (default=None) + group : list, numpy 1-D array, pandas Series, pyarrow Array, pyarrow ChunkedArray or None, optional (default=None) Group/query size for ``data``. Only used in the learning-to-rank task. sum(group) = n_samples. @@ -4274,7 +4506,7 @@ def refit( .. versionadded:: 4.0.0 - init_score : list, list of lists (for multi-class task), numpy array, pandas Series, pandas DataFrame (for multi-class task), or None, optional (default=None) + init_score : list, list of lists (for multi-class task), numpy array, pandas Series, pandas DataFrame (for multi-class task), pyarrow Array, pyarrow ChunkedArray, pyarrow Table (for multi-class task) or None, optional (default=None) Init score for ``data``. .. versionadded:: 4.0.0 @@ -4462,7 +4694,7 @@ def feature_name(self) -> List[str]: reserved_string_buffer_size = 255 required_string_buffer_size = ctypes.c_size_t(0) string_buffers = [ctypes.create_string_buffer(reserved_string_buffer_size) for _ in range(num_feature)] - ptr_string_buffers = (ctypes.c_char_p * num_feature)(*map(ctypes.addressof, string_buffers)) + ptr_string_buffers = (ctypes.c_char_p * num_feature)(*map(ctypes.addressof, string_buffers)) # type: ignore[misc] _safe_call(_LIB.LGBM_BoosterGetFeatureNames( self._handle, ctypes.c_int(num_feature), @@ -4476,7 +4708,7 @@ def feature_name(self) -> List[str]: # if buffer length is not long enough, reallocate buffers if reserved_string_buffer_size < actual_string_buffer_size: string_buffers = [ctypes.create_string_buffer(actual_string_buffer_size) for _ in range(num_feature)] - ptr_string_buffers = (ctypes.c_char_p * num_feature)(*map(ctypes.addressof, string_buffers)) + ptr_string_buffers = (ctypes.c_char_p * num_feature)(*map(ctypes.addressof, string_buffers)) # type: ignore[misc] _safe_call(_LIB.LGBM_BoosterGetFeatureNames( self._handle, ctypes.c_int(num_feature), @@ -4686,7 +4918,7 @@ def __get_eval_info(self) -> None: string_buffers = [ ctypes.create_string_buffer(reserved_string_buffer_size) for _ in range(self.__num_inner_eval) ] - ptr_string_buffers = (ctypes.c_char_p * self.__num_inner_eval)(*map(ctypes.addressof, string_buffers)) + ptr_string_buffers = (ctypes.c_char_p * self.__num_inner_eval)(*map(ctypes.addressof, string_buffers)) # type: ignore[misc] _safe_call(_LIB.LGBM_BoosterGetEvalNames( self._handle, ctypes.c_int(self.__num_inner_eval), @@ -4702,7 +4934,7 @@ def __get_eval_info(self) -> None: string_buffers = [ ctypes.create_string_buffer(actual_string_buffer_size) for _ in range(self.__num_inner_eval) ] - ptr_string_buffers = (ctypes.c_char_p * self.__num_inner_eval)(*map(ctypes.addressof, string_buffers)) + ptr_string_buffers = (ctypes.c_char_p * self.__num_inner_eval)(*map(ctypes.addressof, string_buffers)) # type: ignore[misc] _safe_call(_LIB.LGBM_BoosterGetEvalNames( self._handle, ctypes.c_int(self.__num_inner_eval), diff --git a/python-package/lightgbm/callback.py b/python-package/lightgbm/callback.py index 2f77ee740c75..b68bb63c7f41 100644 --- a/python-package/lightgbm/callback.py +++ b/python-package/lightgbm/callback.py @@ -229,7 +229,12 @@ def __call__(self, env: CallbackEnv) -> None: if new_param != env.params.get(key, None): new_parameters[key] = new_param if new_parameters: - env.model.reset_parameter(new_parameters) + if isinstance(env.model, Booster): + env.model.reset_parameter(new_parameters) + else: + # CVBooster holds a list of Booster objects, each needs to be updated + for booster in env.model.boosters: + booster.reset_parameter(new_parameters) env.params.update(new_parameters) @@ -267,6 +272,10 @@ def __init__( verbose: bool = True, min_delta: Union[float, List[float]] = 0.0 ) -> None: + + if not isinstance(stopping_rounds, int) or stopping_rounds <= 0: + raise ValueError(f"stopping_rounds should be an integer and greater than 0. got: {stopping_rounds}") + self.order = 30 self.before_iteration = False @@ -291,32 +300,45 @@ def _gt_delta(self, curr_score: float, best_score: float, delta: float) -> bool: def _lt_delta(self, curr_score: float, best_score: float, delta: float) -> bool: return curr_score < best_score - delta - def _is_train_set(self, ds_name: str, eval_name: str, train_name: str) -> bool: - return (ds_name == "cv_agg" and eval_name == "train") or ds_name == train_name + def _is_train_set(self, ds_name: str, eval_name: str, env: CallbackEnv) -> bool: + """Check, by name, if a given Dataset is the training data.""" + # for lgb.cv() with eval_train_metric=True, evaluation is also done on the training set + # and those metrics are considered for early stopping + if ds_name == "cv_agg" and eval_name == "train": + return True + + # for lgb.train(), it's possible to pass the training data via valid_sets with any eval_name + if isinstance(env.model, Booster) and ds_name == env.model._train_data_name: + return True + + return False def _init(self, env: CallbackEnv) -> None: if env.evaluation_result_list is None or env.evaluation_result_list == []: raise ValueError( "For early stopping, at least one dataset and eval metric is required for evaluation" ) + is_dart = any(env.params.get(alias, "") == 'dart' for alias in _ConfigAliases.get("boosting")) - only_train_set = ( - len(env.evaluation_result_list) == 1 - and self._is_train_set( - ds_name=env.evaluation_result_list[0][0], - eval_name=env.evaluation_result_list[0][1].split(" ")[0], - train_name=env.model._train_data_name) - ) - self.enabled = not is_dart and not only_train_set - if not self.enabled: - if is_dart: - _log_warning('Early stopping is not available in dart mode') - elif only_train_set: - _log_warning('Only training set found, disabling early stopping.') + if is_dart: + self.enabled = False + _log_warning('Early stopping is not available in dart mode') return - if self.stopping_rounds <= 0: - raise ValueError("stopping_rounds should be greater than zero.") + # validation sets are guaranteed to not be identical to the training data in cv() + if isinstance(env.model, Booster): + only_train_set = ( + len(env.evaluation_result_list) == 1 + and self._is_train_set( + ds_name=env.evaluation_result_list[0][0], + eval_name=env.evaluation_result_list[0][1].split(" ")[0], + env=env + ) + ) + if only_train_set: + self.enabled = False + _log_warning('Only training set found, disabling early stopping.') + return if self.verbose: _log_info(f"Training until validation scores don't improve for {self.stopping_rounds} rounds") @@ -395,7 +417,11 @@ def __call__(self, env: CallbackEnv) -> None: eval_name_splitted = env.evaluation_result_list[i][1].split(" ") if self.first_metric_only and self.first_metric != eval_name_splitted[-1]: continue # use only the first metric for early stopping - if self._is_train_set(env.evaluation_result_list[i][0], eval_name_splitted[0], env.model._train_data_name): + if self._is_train_set( + ds_name=env.evaluation_result_list[i][0], + eval_name=eval_name_splitted[0], + env=env + ): continue # train data for lgb.cv or sklearn wrapper (underlying lgb.train) elif env.iteration - self.best_iter[i] >= self.stopping_rounds: if self.verbose: diff --git a/python-package/lightgbm/compat.py b/python-package/lightgbm/compat.py index 0a55ccd1e421..bd1b29a1e802 100644 --- a/python-package/lightgbm/compat.py +++ b/python-package/lightgbm/compat.py @@ -36,6 +36,16 @@ def __init__(self, *args, **kwargs): concat = None +"""numpy""" +try: + from numpy.random import Generator as np_random_Generator +except ImportError: + class np_random_Generator: # type: ignore + """Dummy class for np.random.Generator.""" + + def __init__(self, *args, **kwargs): + pass + """matplotlib""" try: import matplotlib # noqa: F401 @@ -185,6 +195,59 @@ class dask_Series: # type: ignore def __init__(self, *args, **kwargs): pass +"""pyarrow""" +try: + import pyarrow.compute as pa_compute + from pyarrow import Array as pa_Array + from pyarrow import ChunkedArray as pa_ChunkedArray + from pyarrow import Table as pa_Table + from pyarrow import chunked_array as pa_chunked_array + from pyarrow.cffi import ffi as arrow_cffi + from pyarrow.types import is_floating as arrow_is_floating + from pyarrow.types import is_integer as arrow_is_integer + PYARROW_INSTALLED = True +except ImportError: + PYARROW_INSTALLED = False + + class pa_Array: # type: ignore + """Dummy class for pa.Array.""" + + def __init__(self, *args, **kwargs): + pass + + class pa_ChunkedArray: # type: ignore + """Dummy class for pa.ChunkedArray.""" + + def __init__(self, *args, **kwargs): + pass + + class pa_Table: # type: ignore + """Dummy class for pa.Table.""" + + def __init__(self, *args, **kwargs): + pass + + class arrow_cffi: # type: ignore + """Dummy class for pyarrow.cffi.ffi.""" + + CData = None + addressof = None + cast = None + new = None + + def __init__(self, *args, **kwargs): + pass + + class pa_compute: # type: ignore + """Dummy class for pyarrow.compute.""" + + all = None + equal = None + + pa_chunked_array = None + arrow_is_integer = None + arrow_is_floating = None + """cpu_count()""" try: from joblib import cpu_count diff --git a/python-package/lightgbm/dask.py b/python-package/lightgbm/dask.py index ce5f7137b20c..094d49223952 100644 --- a/python-package/lightgbm/dask.py +++ b/python-package/lightgbm/dask.py @@ -1142,7 +1142,7 @@ def __init__( colsample_bytree: float = 1., reg_alpha: float = 0., reg_lambda: float = 0., - random_state: Optional[Union[int, np.random.RandomState]] = None, + random_state: Optional[Union[int, np.random.RandomState, 'np.random.Generator']] = None, n_jobs: Optional[int] = None, importance_type: str = 'split', validation_fraction: Optional[float] = 0.1, @@ -1348,7 +1348,7 @@ def __init__( colsample_bytree: float = 1., reg_alpha: float = 0., reg_lambda: float = 0., - random_state: Optional[Union[int, np.random.RandomState]] = None, + random_state: Optional[Union[int, np.random.RandomState, 'np.random.Generator']] = None, n_jobs: Optional[int] = None, importance_type: str = 'split', validation_fraction: Optional[float] = 0.1, @@ -1519,7 +1519,7 @@ def __init__( colsample_bytree: float = 1., reg_alpha: float = 0., reg_lambda: float = 0., - random_state: Optional[Union[int, np.random.RandomState]] = None, + random_state: Optional[Union[int, np.random.RandomState, 'np.random.Generator']] = None, n_jobs: Optional[int] = None, importance_type: str = 'split', validation_fraction: Optional[float] = 0.1, diff --git a/python-package/lightgbm/libpath.py b/python-package/lightgbm/libpath.py index c096a6f1b5e2..21222228b0c2 100644 --- a/python-package/lightgbm/libpath.py +++ b/python-package/lightgbm/libpath.py @@ -16,8 +16,7 @@ def find_lib_path() -> List[str]: List of all found library paths to LightGBM. """ curr_path = Path(__file__).absolute() - dll_path = [curr_path, - curr_path.parents[1], + dll_path = [curr_path.parents[1], curr_path.parents[0] / 'bin', curr_path.parents[0] / 'lib'] if system() in ('Windows', 'Microsoft'): diff --git a/python-package/lightgbm/sklearn.py b/python-package/lightgbm/sklearn.py index f1a132637aa1..64be9e55e8af 100644 --- a/python-package/lightgbm/sklearn.py +++ b/python-package/lightgbm/sklearn.py @@ -15,8 +15,8 @@ from .compat import (SKLEARN_INSTALLED, LGBMNotFittedError, _LGBMAssertAllFinite, _LGBMCheckArray, _LGBMCheckClassificationTargets, _LGBMCheckSampleWeight, _LGBMCheckXY, _LGBMClassifierBase, _LGBMComputeSampleWeight, _LGBMCpuCount, _LGBMLabelEncoder, _LGBMModelBase, _LGBMRegressorBase, - dt_DataTable, pd_DataFrame) -from .engine import _make_n_folds, train + dt_DataTable, np_random_Generator, pd_DataFrame) +from .engine import train __all__ = [ 'LGBMClassifier', @@ -86,6 +86,36 @@ _LGBM_ScikitValidSet = Tuple[_LGBM_ScikitMatrixLike, _LGBM_LabelType] +def _get_group_from_constructed_dataset(dataset: Dataset) -> Optional[np.ndarray]: + group = dataset.get_group() + error_msg = ( + "Estimators in lightgbm.sklearn should only retrieve query groups from a constructed Dataset. " + "If you're seeing this message, it's a bug in lightgbm. Please report it at https://github.com/microsoft/LightGBM/issues." + ) + assert (group is None or isinstance(group, np.ndarray)), error_msg + return group + + +def _get_label_from_constructed_dataset(dataset: Dataset) -> np.ndarray: + label = dataset.get_label() + error_msg = ( + "Estimators in lightgbm.sklearn should only retrieve labels from a constructed Dataset. " + "If you're seeing this message, it's a bug in lightgbm. Please report it at https://github.com/microsoft/LightGBM/issues." + ) + assert isinstance(label, np.ndarray), error_msg + return label + + +def _get_weight_from_constructed_dataset(dataset: Dataset) -> Optional[np.ndarray]: + weight = dataset.get_weight() + error_msg = ( + "Estimators in lightgbm.sklearn should only retrieve weights from a constructed Dataset. " + "If you're seeing this message, it's a bug in lightgbm. Please report it at https://github.com/microsoft/LightGBM/issues." + ) + assert (weight is None or isinstance(weight, np.ndarray)), error_msg + return weight + + class _ObjectiveFunctionWrapper: """Proxy class for objective function.""" @@ -151,17 +181,22 @@ def __call__(self, preds: np.ndarray, dataset: Dataset) -> Tuple[np.ndarray, np. The value of the second order derivative (Hessian) of the loss with respect to the elements of preds for each sample point. """ - labels = dataset.get_label() + labels = _get_label_from_constructed_dataset(dataset) argc = len(signature(self.func).parameters) if argc == 2: grad, hess = self.func(labels, preds) # type: ignore[call-arg] - elif argc == 3: - grad, hess = self.func(labels, preds, dataset.get_weight()) # type: ignore[call-arg] - elif argc == 4: - grad, hess = self.func(labels, preds, dataset.get_weight(), dataset.get_group()) # type: ignore [call-arg] - else: - raise TypeError(f"Self-defined objective function should have 2, 3 or 4 arguments, got {argc}") - return grad, hess + return grad, hess + + weight = _get_weight_from_constructed_dataset(dataset) + if argc == 3: + grad, hess = self.func(labels, preds, weight) # type: ignore[call-arg] + return grad, hess + + if argc == 4: + group = _get_group_from_constructed_dataset(dataset) + return self.func(labels, preds, weight, group) # type: ignore[call-arg] + + raise TypeError(f"Self-defined objective function should have 2, 3 or 4 arguments, got {argc}") class _EvalFunctionWrapper: @@ -229,16 +264,20 @@ def __call__( is_higher_better : bool Is eval result higher better, e.g. AUC is ``is_higher_better``. """ - labels = dataset.get_label() + labels = _get_label_from_constructed_dataset(dataset) argc = len(signature(self.func).parameters) if argc == 2: return self.func(labels, preds) # type: ignore[call-arg] - elif argc == 3: - return self.func(labels, preds, dataset.get_weight()) # type: ignore[call-arg] - elif argc == 4: - return self.func(labels, preds, dataset.get_weight(), dataset.get_group()) # type: ignore[call-arg] - else: - raise TypeError(f"Self-defined eval function should have 2, 3 or 4 arguments, got {argc}") + + weight = _get_weight_from_constructed_dataset(dataset) + if argc == 3: + return self.func(labels, preds, weight) # type: ignore[call-arg] + + if argc == 4: + group = _get_group_from_constructed_dataset(dataset) + return self.func(labels, preds, weight, group) # type: ignore[call-arg] + + raise TypeError(f"Self-defined eval function should have 2, 3 or 4 arguments, got {argc}") # documentation templates for LGBMModel methods are shared between the classes in @@ -409,7 +448,7 @@ def __init__( colsample_bytree: float = 1., reg_alpha: float = 0., reg_lambda: float = 0., - random_state: Optional[Union[int, np.random.RandomState]] = None, + random_state: Optional[Union[int, np.random.RandomState, 'np.random.Generator']] = None, n_jobs: Optional[int] = None, importance_type: str = 'split', validation_fraction: Optional[float] = 0.1, @@ -471,7 +510,7 @@ def __init__( random_state : int, RandomState object or None, optional (default=None) Random number seed. If int, this number is used to seed the C++ code. - If RandomState object (numpy), a random integer is picked based on its state to seed the C++ code. + If RandomState or Generator object (numpy), a random integer is picked based on its state to seed the C++ code. If None, default seeds in C++ code are used. n_jobs : int or None, optional (default=None) Number of parallel threads to use for training (can be changed at prediction time by @@ -678,6 +717,10 @@ def _process_params(self, stage: str) -> Dict[str, Any]: if isinstance(params['random_state'], np.random.RandomState): params['random_state'] = params['random_state'].randint(np.iinfo(np.int32).max) + elif isinstance(params['random_state'], np_random_Generator): + params['random_state'] = int( + params['random_state'].integers(np.iinfo(np.int32).max) + ) params = _choose_param_value( main_param_name="early_stopping_round", diff --git a/python-package/pyproject.toml b/python-package/pyproject.toml index 6e43dc242d1b..d5586aa3857d 100644 --- a/python-package/pyproject.toml +++ b/python-package/pyproject.toml @@ -30,9 +30,13 @@ maintainers = [ name = "lightgbm" readme = "README.rst" requires-python = ">=3.6" -version = "4.1.0.99" +version = "4.2.0.99" [project.optional-dependencies] +arrow = [ + "cffi>=1.15.1", + "pyarrow>=6.0.1" +] dask = [ "dask[array,dataframe,distributed]>=2.0.0", "pandas>=0.24.0" @@ -59,7 +63,7 @@ build-backend = "scikit_build_core.build" # based on https://github.com/scikit-build/scikit-build-core#configuration [tool.scikit-build] -cmake.minimum-version = "3.15" +cmake.minimum-version = "3.18" ninja.minimum-version = "1.11" ninja.make-fallback = true cmake.args = [ diff --git a/src/application/application.cpp b/src/application/application.cpp index 3e51136afc96..0bb9eca13bf2 100644 --- a/src/application/application.cpp +++ b/src/application/application.cpp @@ -227,7 +227,7 @@ void Application::Predict() { TextReader result_reader(config_.output_result.c_str(), false); result_reader.ReadAllLines(); std::vector> pred_leaf(result_reader.Lines().size()); - #pragma omp parallel for schedule(static) + #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) for (int i = 0; i < static_cast(result_reader.Lines().size()); ++i) { pred_leaf[i] = Common::StringToArray(result_reader.Lines()[i], '\t'); // Free memory diff --git a/src/application/predictor.hpp b/src/application/predictor.hpp index d1a8aca4d041..18eb01e30179 100644 --- a/src/application/predictor.hpp +++ b/src/application/predictor.hpp @@ -233,7 +233,7 @@ class Predictor { std::vector> oneline_features; std::vector result_to_write(lines.size()); OMP_INIT_EX(); - #pragma omp parallel for schedule(static) firstprivate(oneline_features) + #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) firstprivate(oneline_features) for (data_size_t i = 0; i < static_cast(lines.size()); ++i) { OMP_LOOP_EX_BEGIN(); oneline_features.clear(); diff --git a/src/boosting/cuda/cuda_score_updater.hpp b/src/boosting/cuda/cuda_score_updater.hpp index ec728777e66c..cb79b43b9f36 100644 --- a/src/boosting/cuda/cuda_score_updater.hpp +++ b/src/boosting/cuda/cuda_score_updater.hpp @@ -8,7 +8,7 @@ #ifdef USE_CUDA -#include +#include #include "../score_updater.hpp" diff --git a/src/boosting/gbdt.cpp b/src/boosting/gbdt.cpp index 1f0a5405bf49..b75adab6d971 100644 --- a/src/boosting/gbdt.cpp +++ b/src/boosting/gbdt.cpp @@ -255,7 +255,7 @@ void GBDT::RefitTree(const std::vector>& tree_leaf_prediction) std::vector leaf_pred(num_data_); if (linear_tree_) { std::vector max_leaves_by_thread = std::vector(OMP_NUM_THREADS(), 0); - #pragma omp parallel for schedule(static) + #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) for (int i = 0; i < static_cast(tree_leaf_prediction.size()); ++i) { int tid = omp_get_thread_num(); for (size_t j = 0; j < tree_leaf_prediction[i].size(); ++j) { @@ -270,7 +270,7 @@ void GBDT::RefitTree(const std::vector>& tree_leaf_prediction) Boosting(); for (int tree_id = 0; tree_id < num_tree_per_iteration_; ++tree_id) { int model_index = iter * num_tree_per_iteration_ + tree_id; - #pragma omp parallel for schedule(static) + #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) for (int i = 0; i < num_data_; ++i) { leaf_pred[i] = tree_leaf_prediction[i][model_index]; CHECK_LT(leaf_pred[i], models_[model_index]->num_leaves()); @@ -348,7 +348,7 @@ bool GBDT::TrainOneIter(const score_t* gradients, const score_t* hessians) { if (data_sample_strategy_->IsHessianChange()) { // need to copy customized gradients when using GOSS int64_t total_size = static_cast(num_data_) * num_tree_per_iteration_; - #pragma omp parallel for schedule(static) + #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) for (int64_t i = 0; i < total_size; ++i) { gradients_[i] = gradients[i]; hessians_[i] = hessians[i]; @@ -669,7 +669,7 @@ void GBDT::GetPredictAt(int data_idx, double* out_result, int64_t* out_len) { } #endif // USE_CUDA if (objective_function_ != nullptr) { - #pragma omp parallel for schedule(static) + #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) for (data_size_t i = 0; i < num_data; ++i) { std::vector tree_pred(num_tree_per_iteration_); for (int j = 0; j < num_tree_per_iteration_; ++j) { @@ -682,7 +682,7 @@ void GBDT::GetPredictAt(int data_idx, double* out_result, int64_t* out_len) { } } } else { - #pragma omp parallel for schedule(static) + #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) for (data_size_t i = 0; i < num_data; ++i) { for (int j = 0; j < num_tree_per_iteration_; ++j) { out_result[j * num_data + i] = static_cast(raw_scores[j * num_data + i]); diff --git a/src/boosting/gbdt.h b/src/boosting/gbdt.h index e38b26be3e14..28ebee446fad 100644 --- a/src/boosting/gbdt.h +++ b/src/boosting/gbdt.h @@ -179,15 +179,20 @@ class GBDT : public GBDTBase { const auto pair = Common::Split(line.c_str(), ":"); if (pair[1] == " ]") continue; + const auto param = pair[0].substr(1); + const auto value_str = pair[1].substr(1, pair[1].size() - 2); + auto iter = param_types.find(param); + if (iter == param_types.end()) { + Log::Warning("Ignoring unrecognized parameter '%s' found in model string.", param.c_str()); + continue; + } + std::string param_type = iter->second; if (first) { first = false; str_buf << "\""; } else { str_buf << ",\""; } - const auto param = pair[0].substr(1); - const auto value_str = pair[1].substr(1, pair[1].size() - 2); - const auto param_type = param_types.at(param); str_buf << param << "\": "; if (param_type == "string") { str_buf << "\"" << value_str << "\""; @@ -429,7 +434,7 @@ class GBDT : public GBDTBase { } start_iteration_for_pred_ = start_iteration; if (is_pred_contrib) { - #pragma omp parallel for schedule(static) + #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) for (int i = 0; i < static_cast(models_.size()); ++i) { models_[i]->RecomputeMaxDepth(); } diff --git a/src/boosting/gbdt_model_text.cpp b/src/boosting/gbdt_model_text.cpp index 73c3ea98d3f6..27be5afe066e 100644 --- a/src/boosting/gbdt_model_text.cpp +++ b/src/boosting/gbdt_model_text.cpp @@ -354,7 +354,7 @@ std::string GBDT::SaveModelToString(int start_iteration, int num_iteration, int std::vector tree_strs(num_used_model - start_model); std::vector tree_sizes(num_used_model - start_model); // output tree models - #pragma omp parallel for schedule(static) + #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) for (int i = start_model; i < num_used_model; ++i) { const int idx = i - start_model; tree_strs[idx] = "Tree=" + std::to_string(idx) + '\n'; @@ -552,7 +552,7 @@ bool GBDT::LoadModelFromString(const char* buffer, size_t len) { models_.emplace_back(nullptr); } OMP_INIT_EX(); - #pragma omp parallel for schedule(static) + #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) for (int i = 0; i < num_trees; ++i) { OMP_LOOP_EX_BEGIN(); auto cur_p = p + tree_boundries[i]; diff --git a/src/boosting/rf.hpp b/src/boosting/rf.hpp index 88ece154e432..e6101dc30a39 100644 --- a/src/boosting/rf.hpp +++ b/src/boosting/rf.hpp @@ -97,7 +97,7 @@ class RF : public GBDT { } size_t total_size = static_cast(num_data_) * num_tree_per_iteration_; std::vector tmp_scores(total_size, 0.0f); - #pragma omp parallel for schedule(static) + #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) for (int j = 0; j < num_tree_per_iteration_; ++j) { size_t offset = static_cast(j)* num_data_; for (data_size_t i = 0; i < num_data_; ++i) { diff --git a/src/boosting/score_updater.hpp b/src/boosting/score_updater.hpp index 6e475455df7a..2333b36ec029 100644 --- a/src/boosting/score_updater.hpp +++ b/src/boosting/score_updater.hpp @@ -39,7 +39,7 @@ class ScoreUpdater { Log::Fatal("Number of class for initial score error"); } has_init_score_ = true; -#pragma omp parallel for schedule(static, 512) if (total_size >= 1024) +#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static, 512) if (total_size >= 1024) for (int64_t i = 0; i < total_size; ++i) { score_[i] = init_score[i]; } @@ -54,7 +54,7 @@ class ScoreUpdater { virtual inline void AddScore(double val, int cur_tree_id) { Common::FunctionTimer fun_timer("ScoreUpdater::AddScore", global_timer); const size_t offset = static_cast(num_data_) * cur_tree_id; -#pragma omp parallel for schedule(static, 512) if (num_data_ >= 1024) +#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static, 512) if (num_data_ >= 1024) for (int i = 0; i < num_data_; ++i) { score_[offset + i] += val; } @@ -62,7 +62,7 @@ class ScoreUpdater { virtual inline void MultiplyScore(double val, int cur_tree_id) { const size_t offset = static_cast(num_data_) * cur_tree_id; -#pragma omp parallel for schedule(static, 512) if (num_data_ >= 1024) +#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static, 512) if (num_data_ >= 1024) for (int i = 0; i < num_data_; ++i) { score_[offset + i] *= val; } diff --git a/src/c_api.cpp b/src/c_api.cpp index 8c4eee96b4c9..67b18003588a 100644 --- a/src/c_api.cpp +++ b/src/c_api.cpp @@ -4,6 +4,7 @@ */ #include +#include #include #include #include @@ -437,7 +438,7 @@ class Booster { int64_t num_pred_in_one_row = boosting_->NumPredictOneRow(start_iteration, num_iteration, is_predict_leaf, predict_contrib); auto pred_fun = predictor.GetPredictFunction(); OMP_INIT_EX(); - #pragma omp parallel for schedule(static) + #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) for (int i = 0; i < nrow; ++i) { OMP_LOOP_EX_BEGIN(); auto one_row = get_row_fun(i); @@ -459,7 +460,7 @@ class Booster { auto pred_sparse_fun = predictor.GetPredictSparseFunction(); std::vector>>& agg = *agg_ptr; OMP_INIT_EX(); - #pragma omp parallel for schedule(static) + #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) for (int64_t i = 0; i < nrow; ++i) { OMP_LOOP_EX_BEGIN(); auto one_row = get_row_fun(i); @@ -551,7 +552,7 @@ class Booster { indptr_index++; int64_t matrix_start_index = m * static_cast(agg.size()); OMP_INIT_EX(); - #pragma omp parallel for schedule(static) + #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) for (int64_t i = 0; i < static_cast(agg.size()); ++i) { OMP_LOOP_EX_BEGIN(); auto row_vector = agg[i]; @@ -663,7 +664,7 @@ class Booster { } // Note: we parallelize across matrices instead of rows because of the column_counts[m][col_idx] increment inside the loop OMP_INIT_EX(); - #pragma omp parallel for schedule(static) + #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) for (int m = 0; m < num_matrices; ++m) { OMP_LOOP_EX_BEGIN(); for (int64_t i = 0; i < static_cast(agg.size()); ++i) { @@ -832,6 +833,8 @@ class Booster { // explicitly declare symbols from LightGBM namespace using LightGBM::AllgatherFunction; +using LightGBM::ArrowChunkedArray; +using LightGBM::ArrowTable; using LightGBM::Booster; using LightGBM::Common::CheckElementsIntervalClosed; using LightGBM::Common::RemoveQuotationSymbol; @@ -1074,7 +1077,7 @@ int LGBM_DatasetPushRows(DatasetHandle dataset, p_dataset->ResizeRaw(p_dataset->num_numeric_features() + nrow); } OMP_INIT_EX(); - #pragma omp parallel for schedule(static) + #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) for (int i = 0; i < nrow; ++i) { OMP_LOOP_EX_BEGIN(); const int tid = omp_get_thread_num(); @@ -1116,7 +1119,7 @@ int LGBM_DatasetPushRowsWithMetadata(DatasetHandle dataset, const int max_omp_threads = p_dataset->omp_max_threads() > 0 ? p_dataset->omp_max_threads() : OMP_NUM_THREADS(); OMP_INIT_EX(); -#pragma omp parallel for schedule(static) +#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) for (int i = 0; i < nrow; ++i) { OMP_LOOP_EX_BEGIN(); // convert internal thread id to be unique based on external thread id @@ -1153,7 +1156,7 @@ int LGBM_DatasetPushRowsByCSR(DatasetHandle dataset, p_dataset->ResizeRaw(p_dataset->num_numeric_features() + nrow); } OMP_INIT_EX(); - #pragma omp parallel for schedule(static) + #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) for (int i = 0; i < nrow; ++i) { OMP_LOOP_EX_BEGIN(); const int tid = omp_get_thread_num(); @@ -1199,7 +1202,7 @@ int LGBM_DatasetPushRowsByCSRWithMetadata(DatasetHandle dataset, const int max_omp_threads = p_dataset->omp_max_threads() > 0 ? p_dataset->omp_max_threads() : OMP_NUM_THREADS(); OMP_INIT_EX(); -#pragma omp parallel for schedule(static) +#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) for (int i = 0; i < nrow; ++i) { OMP_LOOP_EX_BEGIN(); // convert internal thread id to be unique based on external thread id @@ -1319,7 +1322,7 @@ int LGBM_DatasetCreateFromMats(int32_t nmat, int32_t start_row = 0; for (int j = 0; j < nmat; ++j) { OMP_INIT_EX(); - #pragma omp parallel for schedule(static) + #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) for (int i = 0; i < nrow[j]; ++i) { OMP_LOOP_EX_BEGIN(); const int tid = omp_get_thread_num(); @@ -1394,7 +1397,7 @@ int LGBM_DatasetCreateFromCSR(const void* indptr, } } OMP_INIT_EX(); - #pragma omp parallel for schedule(static) + #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) for (int i = 0; i < nindptr - 1; ++i) { OMP_LOOP_EX_BEGIN(); const int tid = omp_get_thread_num(); @@ -1465,7 +1468,7 @@ int LGBM_DatasetCreateFromCSRFunc(void* get_row_funptr, OMP_INIT_EX(); std::vector> thread_buffer; - #pragma omp parallel for schedule(static) private(thread_buffer) + #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) private(thread_buffer) for (int i = 0; i < num_rows; ++i) { OMP_LOOP_EX_BEGIN(); { @@ -1506,7 +1509,7 @@ int LGBM_DatasetCreateFromCSC(const void* col_ptr, std::vector> sample_values(ncol_ptr - 1); std::vector> sample_idx(ncol_ptr - 1); OMP_INIT_EX(); - #pragma omp parallel for schedule(static) + #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) for (int i = 0; i < static_cast(sample_values.size()); ++i) { OMP_LOOP_EX_BEGIN(); CSC_RowIterator col_it(col_ptr, col_ptr_type, indices, data, data_type, ncol_ptr, nelem, i); @@ -1534,7 +1537,7 @@ int LGBM_DatasetCreateFromCSC(const void* col_ptr, reinterpret_cast(reference)); } OMP_INIT_EX(); - #pragma omp parallel for schedule(static) + #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) for (int i = 0; i < ncol_ptr - 1; ++i) { OMP_LOOP_EX_BEGIN(); const int tid = omp_get_thread_num(); @@ -1567,6 +1570,98 @@ int LGBM_DatasetCreateFromCSC(const void* col_ptr, API_END(); } +int LGBM_DatasetCreateFromArrow(int64_t n_chunks, + const ArrowArray* chunks, + const ArrowSchema* schema, + const char* parameters, + const DatasetHandle reference, + DatasetHandle *out) { + API_BEGIN(); + + auto param = Config::Str2Map(parameters); + Config config; + config.Set(param); + OMP_SET_NUM_THREADS(config.num_threads); + + std::unique_ptr ret; + + // Prepare the Arrow data + ArrowTable table(n_chunks, chunks, schema); + + // Initialize the dataset + if (reference == nullptr) { + // If there is no reference dataset, we first sample indices + auto sample_indices = CreateSampleIndices(static_cast(table.get_num_rows()), config); + auto sample_count = static_cast(sample_indices.size()); + std::vector> sample_values(table.get_num_columns()); + std::vector> sample_idx(table.get_num_columns()); + + // Then, we obtain sample values by parallelizing across columns + OMP_INIT_EX(); + #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) + for (int64_t j = 0; j < table.get_num_columns(); ++j) { + OMP_LOOP_EX_BEGIN(); + + // Values need to be copied from the record batches. + sample_values[j].reserve(sample_indices.size()); + sample_idx[j].reserve(sample_indices.size()); + + // The chunks are iterated over in the inner loop as columns can be treated independently. + int last_idx = 0; + int i = 0; + auto it = table.get_column(j).begin(); + for (auto idx : sample_indices) { + std::advance(it, idx - last_idx); + auto v = *it; + if (std::fabs(v) > kZeroThreshold || std::isnan(v)) { + sample_values[j].emplace_back(v); + sample_idx[j].emplace_back(i); + } + last_idx = idx; + i++; + } + OMP_LOOP_EX_END(); + } + OMP_THROW_EX(); + + // Finally, we initialize a loader from the sampled values + DatasetLoader loader(config, nullptr, 1, nullptr); + ret.reset(loader.ConstructFromSampleData(Vector2Ptr(&sample_values).data(), + Vector2Ptr(&sample_idx).data(), + table.get_num_columns(), + VectorSize(sample_values).data(), + sample_count, + table.get_num_rows(), + table.get_num_rows())); + } else { + ret.reset(new Dataset(static_cast(table.get_num_rows()))); + ret->CreateValid(reinterpret_cast(reference)); + if (ret->has_raw()) { + ret->ResizeRaw(static_cast(table.get_num_rows())); + } + } + + // After sampling and properly initializing all bins, we can add our data to the dataset. Here, + // we parallelize across rows. + OMP_INIT_EX(); + #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) + for (int64_t j = 0; j < table.get_num_columns(); ++j) { + OMP_LOOP_EX_BEGIN(); + const int tid = omp_get_thread_num(); + data_size_t idx = 0; + auto column = table.get_column(j); + for (auto it = column.begin(), end = column.end(); it != end; ++it) { + ret->PushOneValue(tid, idx++, j, *it); + } + OMP_LOOP_EX_END(); + } + OMP_THROW_EX(); + + ret->FinishLoad(); + *out = ret.release(); + API_END(); +} + int LGBM_DatasetGetSubset( const DatasetHandle handle, const int32_t* used_row_indices, @@ -1686,6 +1781,21 @@ int LGBM_DatasetSetField(DatasetHandle handle, API_END(); } +int LGBM_DatasetSetFieldFromArrow(DatasetHandle handle, + const char* field_name, + int64_t n_chunks, + const ArrowArray* chunks, + const ArrowSchema* schema) { + API_BEGIN(); + auto dataset = reinterpret_cast(handle); + ArrowChunkedArray ca(n_chunks, chunks, schema); + auto is_success = dataset->SetFieldFromArrow(field_name, ca); + if (!is_success) { + Log::Fatal("Input field is not supported"); + } + API_END(); +} + int LGBM_DatasetGetField(DatasetHandle handle, const char* field_name, int* out_len, @@ -2458,6 +2568,57 @@ int LGBM_BoosterPredictForMats(BoosterHandle handle, API_END(); } +int LGBM_BoosterPredictForArrow(BoosterHandle handle, + int64_t n_chunks, + const ArrowArray* chunks, + const ArrowSchema* schema, + int predict_type, + int start_iteration, + int num_iteration, + const char* parameter, + int64_t* out_len, + double* out_result) { + API_BEGIN(); + + // Apply the configuration + auto param = Config::Str2Map(parameter); + Config config; + config.Set(param); + OMP_SET_NUM_THREADS(config.num_threads); + + // Set up chunked array and iterators for all columns + ArrowTable table(n_chunks, chunks, schema); + std::vector> its; + its.reserve(table.get_num_columns()); + for (int64_t j = 0; j < table.get_num_columns(); ++j) { + its.emplace_back(table.get_column(j).begin()); + } + + // Build row function + auto num_columns = table.get_num_columns(); + auto row_fn = [num_columns, &its] (int row_idx) { + std::vector> result; + result.reserve(num_columns); + for (int64_t j = 0; j < num_columns; ++j) { + result.emplace_back(static_cast(j), its[j][row_idx]); + } + return result; + }; + + // Run prediction + Booster* ref_booster = reinterpret_cast(handle); + ref_booster->Predict(start_iteration, + num_iteration, + predict_type, + static_cast(table.get_num_rows()), + static_cast(table.get_num_columns()), + row_fn, + config, + out_result, + out_len); + API_END(); +} + int LGBM_BoosterSaveModel(BoosterHandle handle, int start_iteration, int num_iteration, @@ -2589,6 +2750,23 @@ int LGBM_NetworkInitWithFunctions(int num_machines, int rank, API_END(); } +int LGBM_SetMaxThreads(int num_threads) { + API_BEGIN(); + if (num_threads <= 0) { + LGBM_MAX_NUM_THREADS = -1; + } else { + LGBM_MAX_NUM_THREADS = num_threads; + } + API_END(); +} + +int LGBM_GetMaxThreads(int* out) { + API_BEGIN(); + *out = LGBM_MAX_NUM_THREADS; + API_END(); +} + + // ---- start of some help functions diff --git a/src/cuda/cuda_utils.cpp b/src/cuda/cuda_utils.cpp index a7d0df697e24..b601f9395268 100644 --- a/src/cuda/cuda_utils.cpp +++ b/src/cuda/cuda_utils.cpp @@ -5,7 +5,7 @@ #ifdef USE_CUDA -#include +#include namespace LightGBM { diff --git a/src/io/config.cpp b/src/io/config.cpp index e8578046960a..e25bb6d4fd70 100644 --- a/src/io/config.cpp +++ b/src/io/config.cpp @@ -389,10 +389,6 @@ void Config::CheckParamConflict() { if (deterministic) { Log::Warning("Although \"deterministic\" is set, the results ran by GPU may be non-deterministic."); } - if (use_quantized_grad) { - Log::Warning("Quantized training is not supported by CUDA tree learner. Switch to full precision training."); - use_quantized_grad = false; - } } // linear tree learner must be serial type and run on CPU device if (linear_tree) { diff --git a/src/io/dataset.cpp b/src/io/dataset.cpp index cd692afb031a..058d7bd328ad 100644 --- a/src/io/dataset.cpp +++ b/src/io/dataset.cpp @@ -536,7 +536,7 @@ MultiValBin* Dataset::GetMultiBinFromSparseFeatures(const std::vector& std::vector most_freq_bins; double sum_sparse_rate = 0; for (int i = 0; i < num_feature; ++i) { -#pragma omp parallel for schedule(static, 1) +#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static, 1) for (int tid = 0; tid < num_threads; ++tid) { iters[tid].emplace_back( feature_groups_[multi_group_id]->SubFeatureIterator(i)); @@ -584,7 +584,7 @@ MultiValBin* Dataset::GetMultiBinFromAllFeatures(const std::vector& of for (int fid = 0; fid < feature_groups_[gid]->num_feature_; ++fid) { const auto& bin_mapper = feature_groups_[gid]->bin_mappers_[fid]; most_freq_bins.push_back(bin_mapper->GetMostFreqBin()); -#pragma omp parallel for schedule(static, 1) +#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static, 1) for (int tid = 0; tid < num_threads; ++tid) { iters[tid].emplace_back( feature_groups_[gid]->SubFeatureIterator(fid)); @@ -823,7 +823,7 @@ void Dataset::ReSize(data_size_t num_data) { if (num_data_ != num_data) { num_data_ = num_data; OMP_INIT_EX(); -#pragma omp parallel for schedule(static) +#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) for (int group = 0; group < num_groups_; ++group) { OMP_LOOP_EX_BEGIN(); feature_groups_[group]->ReSize(num_data_); @@ -856,7 +856,7 @@ void Dataset::CopySubrow(const Dataset* fullset, int num_copy_tasks = static_cast(group_ids.size()); OMP_INIT_EX(); - #pragma omp parallel for schedule(dynamic) + #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(dynamic) for (int task_id = 0; task_id < num_copy_tasks; ++task_id) { OMP_LOOP_EX_BEGIN(); int group = group_ids[task_id]; @@ -875,7 +875,7 @@ void Dataset::CopySubrow(const Dataset* fullset, num_numeric_features_ = fullset->num_numeric_features_; if (has_raw_) { ResizeRaw(num_used_indices); -#pragma omp parallel for schedule(static) +#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) for (int i = 0; i < num_used_indices; ++i) { for (int j = 0; j < num_numeric_features_; ++j) { raw_data_[j][i] = fullset->raw_data_[j][used_indices[i]]; @@ -897,6 +897,23 @@ void Dataset::CopySubrow(const Dataset* fullset, #endif // USE_CUDA } +bool Dataset::SetFieldFromArrow(const char* field_name, const ArrowChunkedArray &ca) { + std::string name(field_name); + name = Common::Trim(name); + if (name == std::string("label") || name == std::string("target")) { + metadata_.SetLabel(ca); + } else if (name == std::string("weight") || name == std::string("weights")) { + metadata_.SetWeights(ca); + } else if (name == std::string("init_score")) { + metadata_.SetInitScore(ca); + } else if (name == std::string("query") || name == std::string("group")) { + metadata_.SetQuery(ca); + } else { + return false; + } + return true; +} + bool Dataset::SetFloatField(const char* field_name, const float* field_data, data_size_t num_element) { std::string name(field_name); @@ -1282,7 +1299,7 @@ void Dataset::ConstructHistogramsInner( int16_t* ordered_gradients_and_hessians = reinterpret_cast(ordered_gradients); const int16_t* gradients_and_hessians = reinterpret_cast(gradients); if (USE_INDICES) { - #pragma omp parallel for schedule(static, 512) if (num_data >= 1024) + #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static, 512) if (num_data >= 1024) for (data_size_t i = 0; i < num_data; ++i) { ordered_gradients_and_hessians[i] = gradients_and_hessians[data_indices[i]]; } @@ -1292,7 +1309,7 @@ void Dataset::ConstructHistogramsInner( } else { if (USE_INDICES) { if (USE_HESSIAN) { - #pragma omp parallel for schedule(static, 512) if (num_data >= 1024) + #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static, 512) if (num_data >= 1024) for (data_size_t i = 0; i < num_data; ++i) { ordered_gradients[i] = gradients[data_indices[i]]; ordered_hessians[i] = hessians[data_indices[i]]; @@ -1300,7 +1317,7 @@ void Dataset::ConstructHistogramsInner( ptr_ordered_grad = ordered_gradients; ptr_ordered_hess = ordered_hessians; } else { - #pragma omp parallel for schedule(static, 512) if (num_data >= 1024) + #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static, 512) if (num_data >= 1024) for (data_size_t i = 0; i < num_data; ++i) { ordered_gradients[i] = gradients[data_indices[i]]; } diff --git a/src/io/dataset_loader.cpp b/src/io/dataset_loader.cpp index 354936cfb01a..84bf3907a43c 100644 --- a/src/io/dataset_loader.cpp +++ b/src/io/dataset_loader.cpp @@ -625,7 +625,7 @@ Dataset* DatasetLoader::ConstructFromSampleData(double** sample_values, if (Network::num_machines() == 1) { // if only one machine, find bin locally OMP_INIT_EX(); - #pragma omp parallel for schedule(guided) + #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(guided) for (int i = 0; i < num_col; ++i) { OMP_LOOP_EX_BEGIN(); if (ignore_features_.count(i) > 0) { @@ -674,7 +674,7 @@ Dataset* DatasetLoader::ConstructFromSampleData(double** sample_values, } len[num_machines - 1] = num_total_features - start[num_machines - 1]; OMP_INIT_EX(); - #pragma omp parallel for schedule(guided) + #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(guided) for (int i = 0; i < len[rank]; ++i) { OMP_LOOP_EX_BEGIN(); if (ignore_features_.count(start[rank] + i) > 0) { @@ -1136,7 +1136,7 @@ void DatasetLoader::ConstructBinMappersFromTextData(int rank, int num_machines, if (num_machines == 1) { // if only one machine, find bin locally OMP_INIT_EX(); - #pragma omp parallel for schedule(guided) + #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(guided) for (int i = 0; i < static_cast(sample_values.size()); ++i) { OMP_LOOP_EX_BEGIN(); if (ignore_features_.count(i) > 0) { @@ -1177,7 +1177,7 @@ void DatasetLoader::ConstructBinMappersFromTextData(int rank, int num_machines, } len[num_machines - 1] = dataset->num_total_features_ - start[num_machines - 1]; OMP_INIT_EX(); - #pragma omp parallel for schedule(guided) + #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(guided) for (int i = 0; i < len[rank]; ++i) { OMP_LOOP_EX_BEGIN(); if (ignore_features_.count(start[rank] + i) > 0) { @@ -1268,7 +1268,7 @@ void DatasetLoader::ExtractFeaturesFromMemory(std::vector* text_dat if (!predict_fun_) { OMP_INIT_EX(); // if doesn't need to prediction with initial model - #pragma omp parallel for schedule(static) private(oneline_features) firstprivate(tmp_label, feature_row) + #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) private(oneline_features) firstprivate(tmp_label, feature_row) for (data_size_t i = 0; i < dataset->num_data_; ++i) { OMP_LOOP_EX_BEGIN(); const int tid = omp_get_thread_num(); @@ -1319,7 +1319,7 @@ void DatasetLoader::ExtractFeaturesFromMemory(std::vector* text_dat OMP_INIT_EX(); // if need to prediction with initial model std::vector init_score(static_cast(dataset->num_data_) * num_class_); - #pragma omp parallel for schedule(static) private(oneline_features) firstprivate(tmp_label, feature_row) + #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) private(oneline_features) firstprivate(tmp_label, feature_row) for (data_size_t i = 0; i < dataset->num_data_; ++i) { OMP_LOOP_EX_BEGIN(); const int tid = omp_get_thread_num(); @@ -1394,7 +1394,7 @@ void DatasetLoader::ExtractFeaturesFromFile(const char* filename, const Parser* double tmp_label = 0.0f; std::vector feature_row(dataset->num_features_); OMP_INIT_EX(); - #pragma omp parallel for schedule(static) private(oneline_features) firstprivate(tmp_label, feature_row) + #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) private(oneline_features) firstprivate(tmp_label, feature_row) for (data_size_t i = 0; i < static_cast(lines.size()); ++i) { OMP_LOOP_EX_BEGIN(); const int tid = omp_get_thread_num(); diff --git a/src/io/metadata.cpp b/src/io/metadata.cpp index 1fc47c46787f..55440649f55e 100644 --- a/src/io/metadata.cpp +++ b/src/io/metadata.cpp @@ -101,7 +101,7 @@ void Metadata::Init(const Metadata& fullset, const data_size_t* used_indices, da num_data_ = num_used_indices; label_ = std::vector(num_used_indices); -#pragma omp parallel for schedule(static, 512) if (num_used_indices >= 1024) +#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static, 512) if (num_used_indices >= 1024) for (data_size_t i = 0; i < num_used_indices; ++i) { label_[i] = fullset.label_[used_indices[i]]; } @@ -109,7 +109,7 @@ void Metadata::Init(const Metadata& fullset, const data_size_t* used_indices, da if (!fullset.weights_.empty()) { weights_ = std::vector(num_used_indices); num_weights_ = num_used_indices; -#pragma omp parallel for schedule(static, 512) if (num_used_indices >= 1024) +#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static, 512) if (num_used_indices >= 1024) for (data_size_t i = 0; i < num_used_indices; ++i) { weights_[i] = fullset.weights_[used_indices[i]]; } @@ -121,7 +121,7 @@ void Metadata::Init(const Metadata& fullset, const data_size_t* used_indices, da int num_class = static_cast(fullset.num_init_score_ / fullset.num_data_); init_score_ = std::vector(static_cast(num_used_indices) * num_class); num_init_score_ = static_cast(num_used_indices) * num_class; - #pragma omp parallel for schedule(static) + #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) for (int k = 0; k < num_class; ++k) { const size_t offset_dest = static_cast(k) * num_data_; const size_t offset_src = static_cast(k) * fullset.num_data_; @@ -173,7 +173,7 @@ void Metadata::PartitionLabel(const std::vector& used_indices) { auto old_label = label_; num_data_ = static_cast(used_indices.size()); label_ = std::vector(num_data_); -#pragma omp parallel for schedule(static, 512) if (num_data_ >= 1024) +#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static, 512) if (num_data_ >= 1024) for (data_size_t i = 0; i < num_data_; ++i) { label_[i] = old_label[used_indices[i]]; } @@ -255,7 +255,7 @@ void Metadata::CheckOrPartition(data_size_t num_all_data, const std::vector(num_data_); -#pragma omp parallel for schedule(static, 512) +#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static, 512) for (int i = 0; i < static_cast(used_data_indices.size()); ++i) { weights_[i] = old_weights[used_data_indices[i]]; } @@ -274,7 +274,7 @@ void Metadata::CheckOrPartition(data_size_t num_all_data, const std::vector(num_data_); - #pragma omp parallel for schedule(static, 512) + #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static, 512) for (int i = 0; i < static_cast(used_data_indices.size()); ++i) { positions_[i] = old_positions[used_data_indices[i]]; } @@ -335,7 +335,7 @@ void Metadata::CheckOrPartition(data_size_t num_all_data, const std::vector(num_init_score_ / num_all_data); num_init_score_ = static_cast(num_data_) * num_class; init_score_ = std::vector(num_init_score_); -#pragma omp parallel for schedule(static) +#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) for (int k = 0; k < num_class; ++k) { const size_t offset_dest = static_cast(k) * num_data_; const size_t offset_src = static_cast(k) * num_all_data; @@ -355,32 +355,44 @@ void Metadata::CheckOrPartition(data_size_t num_all_data, const std::vector +void Metadata::SetInitScoresFromIterator(It first, It last) { std::lock_guard lock(mutex_); - // save to nullptr - if (init_score == nullptr || len == 0) { + // Clear init scores on empty input + if (last - first == 0) { init_score_.clear(); num_init_score_ = 0; return; } - if ((len % num_data_) != 0) { + if (((last - first) % num_data_) != 0) { Log::Fatal("Initial score size doesn't match data size"); } - if (init_score_.empty()) { init_score_.resize(len); } - num_init_score_ = len; + if (init_score_.empty()) { + init_score_.resize(last - first); + } + num_init_score_ = last - first; - #pragma omp parallel for schedule(static, 512) if (num_init_score_ >= 1024) + #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static, 512) if (num_init_score_ >= 1024) for (int64_t i = 0; i < num_init_score_; ++i) { - init_score_[i] = Common::AvoidInf(init_score[i]); + init_score_[i] = Common::AvoidInf(first[i]); } init_score_load_from_file_ = false; + #ifdef USE_CUDA if (cuda_metadata_ != nullptr) { - cuda_metadata_->SetInitScore(init_score_.data(), len); + cuda_metadata_->SetInitScore(init_score_.data(), init_score_.size()); } #endif // USE_CUDA } +void Metadata::SetInitScore(const double* init_score, data_size_t len) { + SetInitScoresFromIterator(init_score, init_score + len); +} + +void Metadata::SetInitScore(const ArrowChunkedArray& array) { + SetInitScoresFromIterator(array.begin(), array.end()); +} + void Metadata::InsertInitScores(const double* init_scores, data_size_t start_index, data_size_t len, data_size_t source_size) { if (num_init_score_ <= 0) { Log::Fatal("Inserting initial score data into dataset with no initial scores"); @@ -403,27 +415,39 @@ void Metadata::InsertInitScores(const double* init_scores, data_size_t start_ind // CUDA is handled after all insertions are complete } -void Metadata::SetLabel(const label_t* label, data_size_t len) { +template +void Metadata::SetLabelsFromIterator(It first, It last) { std::lock_guard lock(mutex_); - if (label == nullptr) { - Log::Fatal("label cannot be nullptr"); + if (num_data_ != last - first) { + Log::Fatal("Length of labels differs from the length of #data"); } - if (num_data_ != len) { - Log::Fatal("Length of label is not same with #data"); + if (label_.empty()) { + label_.resize(num_data_); } - if (label_.empty()) { label_.resize(num_data_); } - #pragma omp parallel for schedule(static, 512) if (num_data_ >= 1024) + #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static, 512) if (num_data_ >= 1024) for (data_size_t i = 0; i < num_data_; ++i) { - label_[i] = Common::AvoidInf(label[i]); + label_[i] = Common::AvoidInf(first[i]); } + #ifdef USE_CUDA if (cuda_metadata_ != nullptr) { - cuda_metadata_->SetLabel(label_.data(), len); + cuda_metadata_->SetLabel(label_.data(), label_.size()); } #endif // USE_CUDA } +void Metadata::SetLabel(const label_t* label, data_size_t len) { + if (label == nullptr) { + Log::Fatal("label cannot be nullptr"); + } + SetLabelsFromIterator(label, label + len); +} + +void Metadata::SetLabel(const ArrowChunkedArray& array) { + SetLabelsFromIterator(array.begin(), array.end()); +} + void Metadata::InsertLabels(const label_t* labels, data_size_t start_index, data_size_t len) { if (labels == nullptr) { Log::Fatal("label cannot be nullptr"); @@ -438,33 +462,45 @@ void Metadata::InsertLabels(const label_t* labels, data_size_t start_index, data // CUDA is handled after all insertions are complete } -void Metadata::SetWeights(const label_t* weights, data_size_t len) { +template +void Metadata::SetWeightsFromIterator(It first, It last) { std::lock_guard lock(mutex_); - // save to nullptr - if (weights == nullptr || len == 0) { + // Clear weights on empty input + if (last - first == 0) { weights_.clear(); num_weights_ = 0; return; } - if (num_data_ != len) { - Log::Fatal("Length of weights is not same with #data"); + if (num_data_ != last - first) { + Log::Fatal("Length of weights differs from the length of #data"); + } + if (weights_.empty()) { + weights_.resize(num_data_); } - if (weights_.empty()) { weights_.resize(num_data_); } num_weights_ = num_data_; - #pragma omp parallel for schedule(static, 512) if (num_weights_ >= 1024) + #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static, 512) if (num_weights_ >= 1024) for (data_size_t i = 0; i < num_weights_; ++i) { - weights_[i] = Common::AvoidInf(weights[i]); + weights_[i] = Common::AvoidInf(first[i]); } CalculateQueryWeights(); weight_load_from_file_ = false; + #ifdef USE_CUDA if (cuda_metadata_ != nullptr) { - cuda_metadata_->SetWeights(weights_.data(), len); + cuda_metadata_->SetWeights(weights_.data(), weights_.size()); } #endif // USE_CUDA } +void Metadata::SetWeights(const label_t* weights, data_size_t len) { + SetWeightsFromIterator(weights, weights + len); +} + +void Metadata::SetWeights(const ArrowChunkedArray& array) { + SetWeightsFromIterator(array.begin(), array.end()); +} + void Metadata::InsertWeights(const label_t* weights, data_size_t start_index, data_size_t len) { if (!weights) { Log::Fatal("Passed null weights"); @@ -483,30 +519,34 @@ void Metadata::InsertWeights(const label_t* weights, data_size_t start_index, da // CUDA is handled after all insertions are complete } -void Metadata::SetQuery(const data_size_t* query, data_size_t len) { +template +void Metadata::SetQueriesFromIterator(It first, It last) { std::lock_guard lock(mutex_); - // save to nullptr - if (query == nullptr || len == 0) { + // Clear query boundaries on empty input + if (last - first == 0) { query_boundaries_.clear(); num_queries_ = 0; return; } + data_size_t sum = 0; - #pragma omp parallel for schedule(static) reduction(+:sum) - for (data_size_t i = 0; i < len; ++i) { - sum += query[i]; + #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) reduction(+:sum) + for (data_size_t i = 0; i < last - first; ++i) { + sum += first[i]; } if (num_data_ != sum) { - Log::Fatal("Sum of query counts is not same with #data"); + Log::Fatal("Sum of query counts (%i) differs from the length of #data (%i)", num_data_, sum); } - num_queries_ = len; + num_queries_ = last - first; + query_boundaries_.resize(num_queries_ + 1); query_boundaries_[0] = 0; for (data_size_t i = 0; i < num_queries_; ++i) { - query_boundaries_[i + 1] = query_boundaries_[i] + query[i]; + query_boundaries_[i + 1] = query_boundaries_[i] + first[i]; } CalculateQueryWeights(); query_load_from_file_ = false; + #ifdef USE_CUDA if (cuda_metadata_ != nullptr) { if (query_weights_.size() > 0) { @@ -519,6 +559,14 @@ void Metadata::SetQuery(const data_size_t* query, data_size_t len) { #endif // USE_CUDA } +void Metadata::SetQuery(const data_size_t* query, data_size_t len) { + SetQueriesFromIterator(query, query + len); +} + +void Metadata::SetQuery(const ArrowChunkedArray& array) { + SetQueriesFromIterator(array.begin(), array.end()); +} + void Metadata::SetPosition(const data_size_t* positions, data_size_t len) { std::lock_guard lock(mutex_); // save to nullptr @@ -554,7 +602,7 @@ void Metadata::SetPosition(const data_size_t* positions, data_size_t len) { Log::Debug("number of unique positions found = %ld", position_ids_.size()); - #pragma omp parallel for schedule(static, 512) if (num_positions_ >= 1024) + #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static, 512) if (num_positions_ >= 1024) for (data_size_t i = 0; i < num_positions_; ++i) { positions_[i] = map_id2pos.at(positions[i]); } @@ -590,7 +638,7 @@ void Metadata::LoadWeights() { Log::Info("Loading weights..."); num_weights_ = static_cast(reader.Lines().size()); weights_ = std::vector(num_weights_); - #pragma omp parallel for schedule(static) + #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) for (data_size_t i = 0; i < num_weights_; ++i) { double tmp_weight = 0.0f; Common::Atof(reader.Lines()[i].c_str(), &tmp_weight); @@ -645,7 +693,7 @@ void Metadata::LoadInitialScore(const std::string& data_filename) { init_score_ = std::vector(num_init_score_); if (num_class == 1) { - #pragma omp parallel for schedule(static) + #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) for (data_size_t i = 0; i < num_line; ++i) { double tmp = 0.0f; Common::Atof(reader.Lines()[i].c_str(), &tmp); @@ -653,7 +701,7 @@ void Metadata::LoadInitialScore(const std::string& data_filename) { } } else { std::vector oneline_init_score; - #pragma omp parallel for schedule(static) + #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) for (data_size_t i = 0; i < num_line; ++i) { double tmp = 0.0f; oneline_init_score = Common::Split(reader.Lines()[i].c_str(), '\t'); diff --git a/src/io/multi_val_dense_bin.hpp b/src/io/multi_val_dense_bin.hpp index 780272bdc4e1..2bab45f044cd 100644 --- a/src/io/multi_val_dense_bin.hpp +++ b/src/io/multi_val_dense_bin.hpp @@ -271,7 +271,7 @@ class MultiValDenseBin : public MultiValBin { data_size_t block_size = num_data_; Threading::BlockInfo(num_data_, 1024, &n_block, &block_size); -#pragma omp parallel for schedule(static, 1) +#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static, 1) for (int tid = 0; tid < n_block; ++tid) { data_size_t start = tid * block_size; data_size_t end = std::min(num_data_, start + block_size); diff --git a/src/io/multi_val_sparse_bin.hpp b/src/io/multi_val_sparse_bin.hpp index 32a5a51b4f89..edc48ca84c2a 100644 --- a/src/io/multi_val_sparse_bin.hpp +++ b/src/io/multi_val_sparse_bin.hpp @@ -85,7 +85,7 @@ class MultiValSparseBin : public MultiValBin { offsets[tid + 1] = offsets[tid] + sizes[tid + 1]; } data_.resize(row_ptr_[num_data_]); -#pragma omp parallel for schedule(static, 1) +#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static, 1) for (int tid = 0; tid < static_cast(t_data_.size()); ++tid) { std::copy_n(t_data_[tid].data(), sizes[tid + 1], data_.data() + offsets[tid]); @@ -344,7 +344,7 @@ class MultiValSparseBin : public MultiValBin { num_data_, 1024, &n_block, &block_size); std::vector sizes(t_data_.size() + 1, 0); const int pre_alloc_size = 50; -#pragma omp parallel for schedule(static, 1) +#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static, 1) for (int tid = 0; tid < n_block; ++tid) { data_size_t start = tid * block_size; data_size_t end = std::min(num_data_, start + block_size); diff --git a/src/io/train_share_states.cpp b/src/io/train_share_states.cpp index 71b2e097ef1b..4f2ec456c937 100644 --- a/src/io/train_share_states.cpp +++ b/src/io/train_share_states.cpp @@ -56,7 +56,7 @@ void MultiValBinWrapper::HistMove(const std::vector(hist_buf.data()) + hist_buf.size() / 2 - static_cast(num_bin_aligned_); - #pragma omp parallel for schedule(static) + #pragma omp parallel for schedule(static) num_threads(num_threads_) for (int i = 0; i < static_cast(hist_move_src_.size()); ++i) { std::copy_n(src + hist_move_src_[i] / 2, hist_move_size_[i] / 2, reinterpret_cast(origin_hist_data_) + hist_move_dest_[i] / 2); @@ -65,14 +65,14 @@ void MultiValBinWrapper::HistMove(const std::vector(hist_buf.data()) + hist_buf.size() / 2 - static_cast(num_bin_aligned_); if (is_use_subcol_) { - #pragma omp parallel for schedule(static) + #pragma omp parallel for schedule(static) num_threads(num_threads_) for (int i = 0; i < static_cast(hist_move_src_.size()); ++i) { std::copy_n(src + hist_move_src_[i] / 2, hist_move_size_[i] / 2, reinterpret_cast(origin_hist_data_) + hist_move_dest_[i] / 2); } } else { int32_t* orig_ptr = reinterpret_cast(origin_hist_data_); - #pragma omp parallel for schedule(static) + #pragma omp parallel for schedule(static) num_threads(num_threads_) for (int i = 0; i < num_bin_; ++i) { orig_ptr[i] = src[i]; } @@ -81,7 +81,7 @@ void MultiValBinWrapper::HistMove(const std::vector(num_bin_aligned_); - #pragma omp parallel for schedule(static) + #pragma omp parallel for schedule(static) num_threads(num_threads_) for (int i = 0; i < static_cast(hist_move_src_.size()); ++i) { std::copy_n(src + hist_move_src_[i], hist_move_size_[i], origin_hist_data_ + hist_move_dest_[i]); diff --git a/src/io/tree.cpp b/src/io/tree.cpp index ce45d20cf454..4312b4f65002 100644 --- a/src/io/tree.cpp +++ b/src/io/tree.cpp @@ -153,7 +153,7 @@ int Tree::SplitCategorical(int leaf, int feature, int real_feature, const uint32 void Tree::AddPredictionToScore(const Dataset* data, data_size_t num_data, double* score) const { if (!is_linear_ && num_leaves_ <= 1) { if (leaf_value_[0] != 0.0f) { -#pragma omp parallel for schedule(static, 512) if (num_data >= 1024) +#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static, 512) if (num_data >= 1024) for (data_size_t i = 0; i < num_data; ++i) { score[i] += leaf_value_[0]; } @@ -234,7 +234,7 @@ void Tree::AddPredictionToScore(const Dataset* data, data_size_t num_data, double* score) const { if (!is_linear_ && num_leaves_ <= 1) { if (leaf_value_[0] != 0.0f) { -#pragma omp parallel for schedule(static, 512) if (num_data >= 1024) +#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static, 512) if (num_data >= 1024) for (data_size_t i = 0; i < num_data; ++i) { score[used_data_indices[i]] += leaf_value_[0]; } diff --git a/src/metric/binary_metric.hpp b/src/metric/binary_metric.hpp index 037f54ba091a..2fc840268e38 100644 --- a/src/metric/binary_metric.hpp +++ b/src/metric/binary_metric.hpp @@ -61,13 +61,13 @@ class BinaryMetric: public Metric { double sum_loss = 0.0f; if (objective == nullptr) { if (weights_ == nullptr) { - #pragma omp parallel for schedule(static) reduction(+:sum_loss) + #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) reduction(+:sum_loss) for (data_size_t i = 0; i < num_data_; ++i) { // add loss sum_loss += PointWiseLossCalculator::LossOnPoint(label_[i], score[i]); } } else { - #pragma omp parallel for schedule(static) reduction(+:sum_loss) + #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) reduction(+:sum_loss) for (data_size_t i = 0; i < num_data_; ++i) { // add loss sum_loss += PointWiseLossCalculator::LossOnPoint(label_[i], score[i]) * weights_[i]; @@ -75,7 +75,7 @@ class BinaryMetric: public Metric { } } else { if (weights_ == nullptr) { - #pragma omp parallel for schedule(static) reduction(+:sum_loss) + #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) reduction(+:sum_loss) for (data_size_t i = 0; i < num_data_; ++i) { double prob = 0; objective->ConvertOutput(&score[i], &prob); @@ -83,7 +83,7 @@ class BinaryMetric: public Metric { sum_loss += PointWiseLossCalculator::LossOnPoint(label_[i], prob); } } else { - #pragma omp parallel for schedule(static) reduction(+:sum_loss) + #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) reduction(+:sum_loss) for (data_size_t i = 0; i < num_data_; ++i) { double prob = 0; objective->ConvertOutput(&score[i], &prob); diff --git a/src/metric/cuda/cuda_binary_metric.hpp b/src/metric/cuda/cuda_binary_metric.hpp index 86dbdce986f5..0f61063c620e 100644 --- a/src/metric/cuda/cuda_binary_metric.hpp +++ b/src/metric/cuda/cuda_binary_metric.hpp @@ -10,7 +10,7 @@ #ifdef USE_CUDA #include -#include +#include #include diff --git a/src/metric/cuda/cuda_pointwise_metric.hpp b/src/metric/cuda/cuda_pointwise_metric.hpp index dae1c6a7fa68..fafeafe635c1 100644 --- a/src/metric/cuda/cuda_pointwise_metric.hpp +++ b/src/metric/cuda/cuda_pointwise_metric.hpp @@ -10,7 +10,7 @@ #ifdef USE_CUDA #include -#include +#include #include diff --git a/src/metric/cuda/cuda_regression_metric.hpp b/src/metric/cuda/cuda_regression_metric.hpp index 4cfd996d837d..e69bd221582c 100644 --- a/src/metric/cuda/cuda_regression_metric.hpp +++ b/src/metric/cuda/cuda_regression_metric.hpp @@ -10,7 +10,7 @@ #ifdef USE_CUDA #include -#include +#include #include diff --git a/src/metric/map_metric.hpp b/src/metric/map_metric.hpp index 18539ee44ee0..76fb4fe8a3e9 100644 --- a/src/metric/map_metric.hpp +++ b/src/metric/map_metric.hpp @@ -111,7 +111,7 @@ class MapMetric:public Metric { } std::vector tmp_map(eval_at_.size(), 0.0f); if (query_weights_ == nullptr) { - #pragma omp parallel for schedule(guided) firstprivate(tmp_map) + #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(guided) firstprivate(tmp_map) for (data_size_t i = 0; i < num_queries_; ++i) { const int tid = omp_get_thread_num(); CalMapAtK(eval_at_, npos_per_query_[i], label_ + query_boundaries_[i], @@ -121,7 +121,7 @@ class MapMetric:public Metric { } } } else { - #pragma omp parallel for schedule(guided) firstprivate(tmp_map) + #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(guided) firstprivate(tmp_map) for (data_size_t i = 0; i < num_queries_; ++i) { const int tid = omp_get_thread_num(); CalMapAtK(eval_at_, npos_per_query_[i], label_ + query_boundaries_[i], diff --git a/src/metric/multiclass_metric.hpp b/src/metric/multiclass_metric.hpp index c83b2c842790..dbc769be2e3e 100644 --- a/src/metric/multiclass_metric.hpp +++ b/src/metric/multiclass_metric.hpp @@ -63,7 +63,7 @@ class MulticlassMetric: public Metric { } if (objective != nullptr) { if (weights_ == nullptr) { - #pragma omp parallel for schedule(static) reduction(+:sum_loss) + #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) reduction(+:sum_loss) for (data_size_t i = 0; i < num_data_; ++i) { std::vector raw_score(num_tree_per_iteration); for (int k = 0; k < num_tree_per_iteration; ++k) { @@ -76,7 +76,7 @@ class MulticlassMetric: public Metric { sum_loss += PointWiseLossCalculator::LossOnPoint(label_[i], &rec, config_); } } else { - #pragma omp parallel for schedule(static) reduction(+:sum_loss) + #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) reduction(+:sum_loss) for (data_size_t i = 0; i < num_data_; ++i) { std::vector raw_score(num_tree_per_iteration); for (int k = 0; k < num_tree_per_iteration; ++k) { @@ -91,7 +91,7 @@ class MulticlassMetric: public Metric { } } else { if (weights_ == nullptr) { - #pragma omp parallel for schedule(static) reduction(+:sum_loss) + #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) reduction(+:sum_loss) for (data_size_t i = 0; i < num_data_; ++i) { std::vector rec(num_tree_per_iteration); for (int k = 0; k < num_tree_per_iteration; ++k) { @@ -102,7 +102,7 @@ class MulticlassMetric: public Metric { sum_loss += PointWiseLossCalculator::LossOnPoint(label_[i], &rec, config_); } } else { - #pragma omp parallel for schedule(static) reduction(+:sum_loss) + #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) reduction(+:sum_loss) for (data_size_t i = 0; i < num_data_; ++i) { std::vector rec(num_tree_per_iteration); for (int k = 0; k < num_tree_per_iteration; ++k) { diff --git a/src/metric/rank_metric.hpp b/src/metric/rank_metric.hpp index 888849950be3..e2adb8c082d4 100644 --- a/src/metric/rank_metric.hpp +++ b/src/metric/rank_metric.hpp @@ -57,7 +57,7 @@ class NDCGMetric:public Metric { } inverse_max_dcgs_.resize(num_queries_); // cache the inverse max DCG for all queries, used to calculate NDCG - #pragma omp parallel for schedule(static) + #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) for (data_size_t i = 0; i < num_queries_; ++i) { inverse_max_dcgs_[i].resize(eval_at_.size(), 0.0f); DCGCalculator::CalMaxDCG(eval_at_, label_ + query_boundaries_[i], @@ -92,7 +92,7 @@ class NDCGMetric:public Metric { } std::vector tmp_dcg(eval_at_.size(), 0.0f); if (query_weights_ == nullptr) { - #pragma omp parallel for schedule(static) firstprivate(tmp_dcg) + #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) firstprivate(tmp_dcg) for (data_size_t i = 0; i < num_queries_; ++i) { const int tid = omp_get_thread_num(); // if all doc in this query are all negative, let its NDCG=1 @@ -112,7 +112,7 @@ class NDCGMetric:public Metric { } } } else { - #pragma omp parallel for schedule(static) firstprivate(tmp_dcg) + #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) firstprivate(tmp_dcg) for (data_size_t i = 0; i < num_queries_; ++i) { const int tid = omp_get_thread_num(); // if all doc in this query are all negative, let its NDCG=1 diff --git a/src/metric/regression_metric.hpp b/src/metric/regression_metric.hpp index 3c4124aad4b9..fbe6f2a062fb 100644 --- a/src/metric/regression_metric.hpp +++ b/src/metric/regression_metric.hpp @@ -59,13 +59,13 @@ class RegressionMetric: public Metric { double sum_loss = 0.0f; if (objective == nullptr) { if (weights_ == nullptr) { - #pragma omp parallel for schedule(static) reduction(+:sum_loss) + #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) reduction(+:sum_loss) for (data_size_t i = 0; i < num_data_; ++i) { // add loss sum_loss += PointWiseLossCalculator::LossOnPoint(label_[i], score[i], config_); } } else { - #pragma omp parallel for schedule(static) reduction(+:sum_loss) + #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) reduction(+:sum_loss) for (data_size_t i = 0; i < num_data_; ++i) { // add loss sum_loss += PointWiseLossCalculator::LossOnPoint(label_[i], score[i], config_) * weights_[i]; @@ -73,7 +73,7 @@ class RegressionMetric: public Metric { } } else { if (weights_ == nullptr) { - #pragma omp parallel for schedule(static) reduction(+:sum_loss) + #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) reduction(+:sum_loss) for (data_size_t i = 0; i < num_data_; ++i) { // add loss double t = 0; @@ -81,7 +81,7 @@ class RegressionMetric: public Metric { sum_loss += PointWiseLossCalculator::LossOnPoint(label_[i], t, config_); } } else { - #pragma omp parallel for schedule(static) reduction(+:sum_loss) + #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) reduction(+:sum_loss) for (data_size_t i = 0; i < num_data_; ++i) { // add loss double t = 0; diff --git a/src/metric/xentropy_metric.hpp b/src/metric/xentropy_metric.hpp index 29d4984c64b3..9c7671c7546d 100644 --- a/src/metric/xentropy_metric.hpp +++ b/src/metric/xentropy_metric.hpp @@ -107,26 +107,26 @@ class CrossEntropyMetric : public Metric { double sum_loss = 0.0f; if (objective == nullptr) { if (weights_ == nullptr) { - #pragma omp parallel for schedule(static) reduction(+:sum_loss) + #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) reduction(+:sum_loss) for (data_size_t i = 0; i < num_data_; ++i) { sum_loss += XentLoss(label_[i], score[i]); // NOTE: does not work unless score is a probability } } else { - #pragma omp parallel for schedule(static) reduction(+:sum_loss) + #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) reduction(+:sum_loss) for (data_size_t i = 0; i < num_data_; ++i) { sum_loss += XentLoss(label_[i], score[i]) * weights_[i]; // NOTE: does not work unless score is a probability } } } else { if (weights_ == nullptr) { - #pragma omp parallel for schedule(static) reduction(+:sum_loss) + #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) reduction(+:sum_loss) for (data_size_t i = 0; i < num_data_; ++i) { double p = 0; objective->ConvertOutput(&score[i], &p); sum_loss += XentLoss(label_[i], p); } } else { - #pragma omp parallel for schedule(static) reduction(+:sum_loss) + #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) reduction(+:sum_loss) for (data_size_t i = 0; i < num_data_; ++i) { double p = 0; objective->ConvertOutput(&score[i], &p); @@ -192,13 +192,13 @@ class CrossEntropyLambdaMetric : public Metric { double sum_loss = 0.0f; if (objective == nullptr) { if (weights_ == nullptr) { - #pragma omp parallel for schedule(static) reduction(+:sum_loss) + #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) reduction(+:sum_loss) for (data_size_t i = 0; i < num_data_; ++i) { double hhat = std::log1p(std::exp(score[i])); // auto-convert sum_loss += XentLambdaLoss(label_[i], 1.0f, hhat); } } else { - #pragma omp parallel for schedule(static) reduction(+:sum_loss) + #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) reduction(+:sum_loss) for (data_size_t i = 0; i < num_data_; ++i) { double hhat = std::log1p(std::exp(score[i])); // auto-convert sum_loss += XentLambdaLoss(label_[i], weights_[i], hhat); @@ -206,14 +206,14 @@ class CrossEntropyLambdaMetric : public Metric { } } else { if (weights_ == nullptr) { - #pragma omp parallel for schedule(static) reduction(+:sum_loss) + #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) reduction(+:sum_loss) for (data_size_t i = 0; i < num_data_; ++i) { double hhat = 0; objective->ConvertOutput(&score[i], &hhat); // NOTE: this only works if objective = "xentlambda" sum_loss += XentLambdaLoss(label_[i], 1.0f, hhat); } } else { - #pragma omp parallel for schedule(static) reduction(+:sum_loss) + #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) reduction(+:sum_loss) for (data_size_t i = 0; i < num_data_; ++i) { double hhat = 0; objective->ConvertOutput(&score[i], &hhat); // NOTE: this only works if objective = "xentlambda" @@ -299,26 +299,26 @@ class KullbackLeiblerDivergence : public Metric { double sum_loss = 0.0f; if (objective == nullptr) { if (weights_ == nullptr) { - #pragma omp parallel for schedule(static) reduction(+:sum_loss) + #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) reduction(+:sum_loss) for (data_size_t i = 0; i < num_data_; ++i) { sum_loss += XentLoss(label_[i], score[i]); // NOTE: does not work unless score is a probability } } else { - #pragma omp parallel for schedule(static) reduction(+:sum_loss) + #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) reduction(+:sum_loss) for (data_size_t i = 0; i < num_data_; ++i) { sum_loss += XentLoss(label_[i], score[i]) * weights_[i]; // NOTE: does not work unless score is a probability } } } else { if (weights_ == nullptr) { - #pragma omp parallel for schedule(static) reduction(+:sum_loss) + #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) reduction(+:sum_loss) for (data_size_t i = 0; i < num_data_; ++i) { double p = 0; objective->ConvertOutput(&score[i], &p); sum_loss += XentLoss(label_[i], p); } } else { - #pragma omp parallel for schedule(static) reduction(+:sum_loss) + #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) reduction(+:sum_loss) for (data_size_t i = 0; i < num_data_; ++i) { double p = 0; objective->ConvertOutput(&score[i], &p); diff --git a/src/objective/binary_objective.hpp b/src/objective/binary_objective.hpp index b770972db9a1..b189cc7701c4 100644 --- a/src/objective/binary_objective.hpp +++ b/src/objective/binary_objective.hpp @@ -63,7 +63,7 @@ class BinaryLogloss: public ObjectiveFunction { data_size_t cnt_positive = 0; data_size_t cnt_negative = 0; // count for positive and negative samples - #pragma omp parallel for schedule(static) reduction(+:cnt_positive, cnt_negative) + #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) reduction(+:cnt_positive, cnt_negative) for (data_size_t i = 0; i < num_data_; ++i) { if (is_pos_(label_[i])) { ++cnt_positive; @@ -107,7 +107,7 @@ class BinaryLogloss: public ObjectiveFunction { return; } if (weights_ == nullptr) { - #pragma omp parallel for schedule(static) + #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) for (data_size_t i = 0; i < num_data_; ++i) { // get label and label weights const int is_pos = is_pos_(label_[i]); @@ -120,7 +120,7 @@ class BinaryLogloss: public ObjectiveFunction { hessians[i] = static_cast(abs_response * (sigmoid_ - abs_response) * label_weight); } } else { - #pragma omp parallel for schedule(static) + #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) for (data_size_t i = 0; i < num_data_; ++i) { // get label and label weights const int is_pos = is_pos_(label_[i]); @@ -140,14 +140,14 @@ class BinaryLogloss: public ObjectiveFunction { double suml = 0.0f; double sumw = 0.0f; if (weights_ != nullptr) { - #pragma omp parallel for schedule(static) reduction(+:suml, sumw) if (!deterministic_) + #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) reduction(+:suml, sumw) if (!deterministic_) for (data_size_t i = 0; i < num_data_; ++i) { suml += is_pos_(label_[i]) * weights_[i]; sumw += weights_[i]; } } else { sumw = static_cast(num_data_); - #pragma omp parallel for schedule(static) reduction(+:suml) if (!deterministic_) + #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) reduction(+:suml) if (!deterministic_) for (data_size_t i = 0; i < num_data_; ++i) { suml += is_pos_(label_[i]); } diff --git a/src/objective/multiclass_objective.hpp b/src/objective/multiclass_objective.hpp index 1d9c8da17556..b2a49a9a40ca 100644 --- a/src/objective/multiclass_objective.hpp +++ b/src/objective/multiclass_objective.hpp @@ -86,7 +86,7 @@ class MulticlassSoftmax: public ObjectiveFunction { void GetGradients(const double* score, score_t* gradients, score_t* hessians) const override { if (weights_ == nullptr) { std::vector rec; - #pragma omp parallel for schedule(static) private(rec) + #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) private(rec) for (data_size_t i = 0; i < num_data_; ++i) { rec.resize(num_class_); for (int k = 0; k < num_class_; ++k) { @@ -107,7 +107,7 @@ class MulticlassSoftmax: public ObjectiveFunction { } } else { std::vector rec; - #pragma omp parallel for schedule(static) private(rec) + #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) private(rec) for (data_size_t i = 0; i < num_data_; ++i) { rec.resize(num_class_); for (int k = 0; k < num_class_; ++k) { diff --git a/src/objective/rank_objective.hpp b/src/objective/rank_objective.hpp index 6bd5324812f8..ae3b74651759 100644 --- a/src/objective/rank_objective.hpp +++ b/src/objective/rank_objective.hpp @@ -58,7 +58,7 @@ class RankingObjective : public ObjectiveFunction { void GetGradients(const double* score, score_t* gradients, score_t* hessians) const override { -#pragma omp parallel for schedule(guided) +#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(guided) for (data_size_t i = 0; i < num_queries_; ++i) { const data_size_t start = query_boundaries_[i]; const data_size_t cnt = query_boundaries_[i + 1] - query_boundaries_[i]; @@ -157,7 +157,7 @@ class LambdarankNDCG : public RankingObjective { DCGCalculator::CheckMetadata(metadata, num_queries_); DCGCalculator::CheckLabel(label_, num_data_); inverse_max_dcgs_.resize(num_queries_); -#pragma omp parallel for schedule(static) +#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) for (data_size_t i = 0; i < num_queries_; ++i) { inverse_max_dcgs_[i] = DCGCalculator::CalMaxDCGAtK( truncation_level_, label_ + query_boundaries_[i], @@ -289,17 +289,12 @@ class LambdarankNDCG : public RankingObjective { void UpdatePositionBiasFactors(const score_t* lambdas, const score_t* hessians) const override { /// get number of threads - int num_threads = 1; - #pragma omp parallel - #pragma omp master - { - num_threads = omp_get_num_threads(); - } + int num_threads = OMP_NUM_THREADS(); // create per-thread buffers for first and second derivatives of utility w.r.t. position bias factors std::vector bias_first_derivatives(num_position_ids_ * num_threads, 0.0); std::vector bias_second_derivatives(num_position_ids_ * num_threads, 0.0); std::vector instance_counts(num_position_ids_ * num_threads, 0); - #pragma omp parallel for schedule(guided) + #pragma omp parallel for schedule(guided) num_threads(num_threads) for (data_size_t i = 0; i < num_data_; i++) { // get thread ID const int tid = omp_get_thread_num(); @@ -310,7 +305,7 @@ class LambdarankNDCG : public RankingObjective { bias_second_derivatives[offset] -= hessians[i]; instance_counts[offset]++; } - #pragma omp parallel for schedule(guided) + #pragma omp parallel for schedule(guided) num_threads(num_threads) for (data_size_t i = 0; i < num_position_ids_; i++) { double bias_first_derivative = 0.0; double bias_second_derivative = 0.0; diff --git a/src/objective/regression_objective.hpp b/src/objective/regression_objective.hpp index eb149756c205..4f53319bbc49 100644 --- a/src/objective/regression_objective.hpp +++ b/src/objective/regression_objective.hpp @@ -115,7 +115,7 @@ class RegressionL2loss: public ObjectiveFunction { label_ = metadata.label(); if (sqrt_) { trans_label_.resize(num_data_); - #pragma omp parallel for schedule(static) + #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) for (data_size_t i = 0; i < num_data; ++i) { trans_label_[i] = Common::Sign(label_[i]) * std::sqrt(std::fabs(label_[i])); } @@ -127,13 +127,13 @@ class RegressionL2loss: public ObjectiveFunction { void GetGradients(const double* score, score_t* gradients, score_t* hessians) const override { if (weights_ == nullptr) { - #pragma omp parallel for schedule(static) + #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) for (data_size_t i = 0; i < num_data_; ++i) { gradients[i] = static_cast(score[i] - label_[i]); hessians[i] = 1.0f; } } else { - #pragma omp parallel for schedule(static) + #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) for (data_size_t i = 0; i < num_data_; ++i) { gradients[i] = static_cast(static_cast((score[i] - label_[i])) * weights_[i]); hessians[i] = static_cast(weights_[i]); @@ -174,14 +174,14 @@ class RegressionL2loss: public ObjectiveFunction { double suml = 0.0f; double sumw = 0.0f; if (weights_ != nullptr) { - #pragma omp parallel for schedule(static) reduction(+:suml, sumw) if (!deterministic_) + #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) reduction(+:suml, sumw) if (!deterministic_) for (data_size_t i = 0; i < num_data_; ++i) { suml += static_cast(label_[i]) * weights_[i]; sumw += weights_[i]; } } else { sumw = static_cast(num_data_); - #pragma omp parallel for schedule(static) reduction(+:suml) if (!deterministic_) + #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) reduction(+:suml) if (!deterministic_) for (data_size_t i = 0; i < num_data_; ++i) { suml += label_[i]; } @@ -217,14 +217,14 @@ class RegressionL1loss: public RegressionL2loss { void GetGradients(const double* score, score_t* gradients, score_t* hessians) const override { if (weights_ == nullptr) { - #pragma omp parallel for schedule(static) + #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) for (data_size_t i = 0; i < num_data_; ++i) { const double diff = score[i] - label_[i]; gradients[i] = static_cast(Common::Sign(diff)); hessians[i] = 1.0f; } } else { - #pragma omp parallel for schedule(static) + #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) for (data_size_t i = 0; i < num_data_; ++i) { const double diff = score[i] - label_[i]; gradients[i] = static_cast(Common::Sign(diff) * weights_[i]); @@ -313,7 +313,7 @@ class RegressionHuberLoss: public RegressionL2loss { void GetGradients(const double* score, score_t* gradients, score_t* hessians) const override { if (weights_ == nullptr) { - #pragma omp parallel for schedule(static) + #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) for (data_size_t i = 0; i < num_data_; ++i) { const double diff = score[i] - label_[i]; if (std::abs(diff) <= alpha_) { @@ -324,7 +324,7 @@ class RegressionHuberLoss: public RegressionL2loss { hessians[i] = 1.0f; } } else { - #pragma omp parallel for schedule(static) + #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) for (data_size_t i = 0; i < num_data_; ++i) { const double diff = score[i] - label_[i]; if (std::abs(diff) <= alpha_) { @@ -362,14 +362,14 @@ class RegressionFairLoss: public RegressionL2loss { void GetGradients(const double* score, score_t* gradients, score_t* hessians) const override { if (weights_ == nullptr) { - #pragma omp parallel for schedule(static) + #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) for (data_size_t i = 0; i < num_data_; ++i) { const double x = score[i] - label_[i]; gradients[i] = static_cast(c_ * x / (std::fabs(x) + c_)); hessians[i] = static_cast(c_ * c_ / ((std::fabs(x) + c_) * (std::fabs(x) + c_))); } } else { - #pragma omp parallel for schedule(static) + #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) for (data_size_t i = 0; i < num_data_; ++i) { const double x = score[i] - label_[i]; gradients[i] = static_cast(c_ * x / (std::fabs(x) + c_) * weights_[i]); @@ -441,14 +441,14 @@ class RegressionPoissonLoss: public RegressionL2loss { score_t* hessians) const override { double exp_max_delta_step_ = std::exp(max_delta_step_); if (weights_ == nullptr) { - #pragma omp parallel for schedule(static) + #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) for (data_size_t i = 0; i < num_data_; ++i) { double exp_score = std::exp(score[i]); gradients[i] = static_cast(exp_score - label_[i]); hessians[i] = static_cast(exp_score * exp_max_delta_step_); } } else { - #pragma omp parallel for schedule(static) + #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) for (data_size_t i = 0; i < num_data_; ++i) { double exp_score = std::exp(score[i]); gradients[i] = static_cast((exp_score - label_[i]) * weights_[i]); @@ -493,7 +493,7 @@ class RegressionQuantileloss : public RegressionL2loss { void GetGradients(const double* score, score_t* gradients, score_t* hessians) const override { if (weights_ == nullptr) { - #pragma omp parallel for schedule(static) + #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) for (data_size_t i = 0; i < num_data_; ++i) { score_t delta = static_cast(score[i] - label_[i]); if (delta >= 0) { @@ -504,7 +504,7 @@ class RegressionQuantileloss : public RegressionL2loss { hessians[i] = 1.0f; } } else { - #pragma omp parallel for schedule(static) + #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) for (data_size_t i = 0; i < num_data_; ++i) { score_t delta = static_cast(score[i] - label_[i]); if (delta >= 0) { @@ -598,12 +598,12 @@ class RegressionMAPELOSS : public RegressionL1loss { } label_weight_.resize(num_data); if (weights_ == nullptr) { - #pragma omp parallel for schedule(static) + #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) for (data_size_t i = 0; i < num_data_; ++i) { label_weight_[i] = 1.0f / std::max(1.0f, std::fabs(label_[i])); } } else { - #pragma omp parallel for schedule(static) + #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) for (data_size_t i = 0; i < num_data_; ++i) { label_weight_[i] = 1.0f / std::max(1.0f, std::fabs(label_[i])) * weights_[i]; } @@ -613,14 +613,14 @@ class RegressionMAPELOSS : public RegressionL1loss { void GetGradients(const double* score, score_t* gradients, score_t* hessians) const override { if (weights_ == nullptr) { - #pragma omp parallel for schedule(static) + #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) for (data_size_t i = 0; i < num_data_; ++i) { const double diff = score[i] - label_[i]; gradients[i] = static_cast(Common::Sign(diff) * label_weight_[i]); hessians[i] = 1.0f; } } else { - #pragma omp parallel for schedule(static) + #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) for (data_size_t i = 0; i < num_data_; ++i) { const double diff = score[i] - label_[i]; gradients[i] = static_cast(Common::Sign(diff) * label_weight_[i]); @@ -690,14 +690,14 @@ class RegressionGammaLoss : public RegressionPoissonLoss { void GetGradients(const double* score, score_t* gradients, score_t* hessians) const override { if (weights_ == nullptr) { - #pragma omp parallel for schedule(static) + #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) for (data_size_t i = 0; i < num_data_; ++i) { double exp_score = std::exp(-score[i]); gradients[i] = static_cast(1.0 - label_[i] * exp_score); hessians[i] = static_cast(label_[i] * exp_score); } } else { - #pragma omp parallel for schedule(static) + #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) for (data_size_t i = 0; i < num_data_; ++i) { double exp_score = std::exp(-score[i]); gradients[i] = static_cast((1.0 - label_[i] * exp_score) * weights_[i]); @@ -728,7 +728,7 @@ class RegressionTweedieLoss: public RegressionPoissonLoss { void GetGradients(const double* score, score_t* gradients, score_t* hessians) const override { if (weights_ == nullptr) { - #pragma omp parallel for schedule(static) + #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) for (data_size_t i = 0; i < num_data_; ++i) { double exp_1_score = std::exp((1 - rho_) * score[i]); double exp_2_score = std::exp((2 - rho_) * score[i]); @@ -737,7 +737,7 @@ class RegressionTweedieLoss: public RegressionPoissonLoss { (2 - rho_) * exp_2_score); } } else { - #pragma omp parallel for schedule(static) + #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) for (data_size_t i = 0; i < num_data_; ++i) { double exp_1_score = std::exp((1 - rho_) * score[i]); double exp_2_score = std::exp((2 - rho_) * score[i]); diff --git a/src/objective/xentropy_objective.hpp b/src/objective/xentropy_objective.hpp index 513ccc1c2462..8edb0950609e 100644 --- a/src/objective/xentropy_objective.hpp +++ b/src/objective/xentropy_objective.hpp @@ -77,7 +77,7 @@ class CrossEntropy: public ObjectiveFunction { void GetGradients(const double* score, score_t* gradients, score_t* hessians) const override { if (weights_ == nullptr) { // compute pointwise gradients and Hessians with implied unit weights - #pragma omp parallel for schedule(static) + #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) for (data_size_t i = 0; i < num_data_; ++i) { const double z = 1.0f / (1.0f + std::exp(-score[i])); gradients[i] = static_cast(z - label_[i]); @@ -85,7 +85,7 @@ class CrossEntropy: public ObjectiveFunction { } } else { // compute pointwise gradients and Hessians with given weights - #pragma omp parallel for schedule(static) + #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) for (data_size_t i = 0; i < num_data_; ++i) { const double z = 1.0f / (1.0f + std::exp(-score[i])); gradients[i] = static_cast((z - label_[i]) * weights_[i]); @@ -114,7 +114,7 @@ class CrossEntropy: public ObjectiveFunction { double suml = 0.0f; double sumw = 0.0f; if (weights_ != nullptr) { - #pragma omp parallel for schedule(static) reduction(+:suml, sumw) if (!deterministic_) + #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) reduction(+:suml, sumw) if (!deterministic_) for (data_size_t i = 0; i < num_data_; ++i) { suml += static_cast(label_[i]) * weights_[i]; @@ -122,7 +122,7 @@ class CrossEntropy: public ObjectiveFunction { } } else { sumw = static_cast(num_data_); - #pragma omp parallel for schedule(static) reduction(+:suml) if (!deterministic_) + #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) reduction(+:suml) if (!deterministic_) for (data_size_t i = 0; i < num_data_; ++i) { suml += label_[i]; @@ -190,7 +190,7 @@ class CrossEntropyLambda: public ObjectiveFunction { void GetGradients(const double* score, score_t* gradients, score_t* hessians) const override { if (weights_ == nullptr) { // compute pointwise gradients and Hessians with implied unit weights; exactly equivalent to CrossEntropy with unit weights - #pragma omp parallel for schedule(static) + #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) for (data_size_t i = 0; i < num_data_; ++i) { const double z = 1.0f / (1.0f + std::exp(-score[i])); gradients[i] = static_cast(z - label_[i]); @@ -198,7 +198,7 @@ class CrossEntropyLambda: public ObjectiveFunction { } } else { // compute pointwise gradients and Hessians with given weights - #pragma omp parallel for schedule(static) + #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) for (data_size_t i = 0; i < num_data_; ++i) { const double w = weights_[i]; const double y = label_[i]; @@ -244,7 +244,7 @@ class CrossEntropyLambda: public ObjectiveFunction { double suml = 0.0f; double sumw = 0.0f; if (weights_ != nullptr) { - #pragma omp parallel for schedule(static) reduction(+:suml, sumw) if (!deterministic_) + #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) reduction(+:suml, sumw) if (!deterministic_) for (data_size_t i = 0; i < num_data_; ++i) { suml += static_cast(label_[i]) * weights_[i]; @@ -252,7 +252,7 @@ class CrossEntropyLambda: public ObjectiveFunction { } } else { sumw = static_cast(num_data_); - #pragma omp parallel for schedule(static) reduction(+:suml) if (!deterministic_) + #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) reduction(+:suml) if (!deterministic_) for (data_size_t i = 0; i < num_data_; ++i) { suml += label_[i]; diff --git a/src/treelearner/col_sampler.hpp b/src/treelearner/col_sampler.hpp index 6debe9db60ca..c70b07e50efa 100644 --- a/src/treelearner/col_sampler.hpp +++ b/src/treelearner/col_sampler.hpp @@ -79,7 +79,7 @@ class ColSampler { static_cast(valid_feature_indices_.size()), used_cnt_bytree_); int omp_loop_size = static_cast(used_feature_indices_.size()); -#pragma omp parallel for schedule(static, 512) if (omp_loop_size >= 1024) +#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static, 512) if (omp_loop_size >= 1024) for (int i = 0; i < omp_loop_size; ++i) { int used_feature = valid_feature_indices_[used_feature_indices_[i]]; int inner_feature_index = train_data_->InnerFeatureIndex(used_feature); @@ -142,7 +142,7 @@ class ColSampler { auto sampled_indices = random_.Sample( static_cast((*allowed_used_feature_indices).size()), used_feature_cnt); int omp_loop_size = static_cast(sampled_indices.size()); -#pragma omp parallel for schedule(static, 512) if (omp_loop_size >= 1024) +#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static, 512) if (omp_loop_size >= 1024) for (int i = 0; i < omp_loop_size; ++i) { int used_feature = valid_feature_indices_[(*allowed_used_feature_indices)[sampled_indices[i]]]; @@ -168,7 +168,7 @@ class ColSampler { auto sampled_indices = random_.Sample( static_cast((*allowed_valid_feature_indices).size()), used_feature_cnt); int omp_loop_size = static_cast(sampled_indices.size()); -#pragma omp parallel for schedule(static, 512) if (omp_loop_size >= 1024) +#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static, 512) if (omp_loop_size >= 1024) for (int i = 0; i < omp_loop_size; ++i) { int used_feature = (*allowed_valid_feature_indices)[sampled_indices[i]]; int inner_feature_index = train_data_->InnerFeatureIndex(used_feature); diff --git a/src/treelearner/cuda/cuda_best_split_finder.cpp b/src/treelearner/cuda/cuda_best_split_finder.cpp index 761b62f21c2e..95758542849c 100644 --- a/src/treelearner/cuda/cuda_best_split_finder.cpp +++ b/src/treelearner/cuda/cuda_best_split_finder.cpp @@ -40,6 +40,9 @@ CUDABestSplitFinder::CUDABestSplitFinder( select_features_by_node_(select_features_by_node), cuda_hist_(cuda_hist) { InitFeatureMetaInfo(train_data); + if (has_categorical_feature_ && config->use_quantized_grad) { + Log::Fatal("Quantized training on GPU with categorical features is not supported yet."); + } cuda_leaf_best_split_info_ = nullptr; cuda_best_split_info_ = nullptr; cuda_best_split_info_buffer_ = nullptr; @@ -326,13 +329,23 @@ void CUDABestSplitFinder::FindBestSplitsForLeaf( const data_size_t num_data_in_smaller_leaf, const data_size_t num_data_in_larger_leaf, const double sum_hessians_in_smaller_leaf, - const double sum_hessians_in_larger_leaf) { + const double sum_hessians_in_larger_leaf, + const score_t* grad_scale, + const score_t* hess_scale, + const uint8_t smaller_num_bits_in_histogram_bins, + const uint8_t larger_num_bits_in_histogram_bins) { const bool is_smaller_leaf_valid = (num_data_in_smaller_leaf > min_data_in_leaf_ && sum_hessians_in_smaller_leaf > min_sum_hessian_in_leaf_); const bool is_larger_leaf_valid = (num_data_in_larger_leaf > min_data_in_leaf_ && sum_hessians_in_larger_leaf > min_sum_hessian_in_leaf_ && larger_leaf_index >= 0); - LaunchFindBestSplitsForLeafKernel(smaller_leaf_splits, larger_leaf_splits, - smaller_leaf_index, larger_leaf_index, is_smaller_leaf_valid, is_larger_leaf_valid); + if (grad_scale != nullptr && hess_scale != nullptr) { + LaunchFindBestSplitsDiscretizedForLeafKernel(smaller_leaf_splits, larger_leaf_splits, + smaller_leaf_index, larger_leaf_index, is_smaller_leaf_valid, is_larger_leaf_valid, + grad_scale, hess_scale, smaller_num_bits_in_histogram_bins, larger_num_bits_in_histogram_bins); + } else { + LaunchFindBestSplitsForLeafKernel(smaller_leaf_splits, larger_leaf_splits, + smaller_leaf_index, larger_leaf_index, is_smaller_leaf_valid, is_larger_leaf_valid); + } global_timer.Start("CUDABestSplitFinder::LaunchSyncBestSplitForLeafKernel"); LaunchSyncBestSplitForLeafKernel(smaller_leaf_index, larger_leaf_index, is_smaller_leaf_valid, is_larger_leaf_valid); SynchronizeCUDADevice(__FILE__, __LINE__); diff --git a/src/treelearner/cuda/cuda_best_split_finder.cu b/src/treelearner/cuda/cuda_best_split_finder.cu index 3fee5562953c..d5c819d392c9 100644 --- a/src/treelearner/cuda/cuda_best_split_finder.cu +++ b/src/treelearner/cuda/cuda_best_split_finder.cu @@ -320,6 +320,175 @@ __device__ void FindBestSplitsForLeafKernelInner( } } +template +__device__ void FindBestSplitsDiscretizedForLeafKernelInner( + // input feature information + const BIN_HIST_TYPE* feature_hist_ptr, + // input task information + const SplitFindTask* task, + CUDARandom* cuda_random, + // input config parameter values + const double lambda_l1, + const double lambda_l2, + const double path_smooth, + const data_size_t min_data_in_leaf, + const double min_sum_hessian_in_leaf, + const double min_gain_to_split, + // input parent node information + const double parent_gain, + const int64_t sum_gradients_hessians, + const data_size_t num_data, + const double parent_output, + // gradient scale + const double grad_scale, + const double hess_scale, + // output parameters + CUDASplitInfo* cuda_best_split_info) { + const double sum_hessians = static_cast(sum_gradients_hessians & 0x00000000ffffffff) * hess_scale; + const double cnt_factor = num_data / sum_hessians; + const double min_gain_shift = parent_gain + min_gain_to_split; + + cuda_best_split_info->is_valid = false; + + ACC_HIST_TYPE local_grad_hess_hist = 0; + double local_gain = 0.0f; + bool threshold_found = false; + uint32_t threshold_value = 0; + __shared__ int rand_threshold; + if (USE_RAND && threadIdx.x == 0) { + if (task->num_bin - 2 > 0) { + rand_threshold = cuda_random->NextInt(0, task->num_bin - 2); + } + } + __shared__ uint32_t best_thread_index; + __shared__ double shared_double_buffer[32]; + __shared__ bool shared_bool_buffer[32]; + __shared__ uint32_t shared_int_buffer[64]; + const unsigned int threadIdx_x = threadIdx.x; + const bool skip_sum = REVERSE ? + (task->skip_default_bin && (task->num_bin - 1 - threadIdx_x) == static_cast(task->default_bin)) : + (task->skip_default_bin && (threadIdx_x + task->mfb_offset) == static_cast(task->default_bin)); + const uint32_t feature_num_bin_minus_offset = task->num_bin - task->mfb_offset; + if (!REVERSE) { + if (threadIdx_x < feature_num_bin_minus_offset && !skip_sum) { + const unsigned int bin_offset = threadIdx_x; + if (USE_16BIT_BIN_HIST && !USE_16BIT_ACC_HIST) { + const int32_t local_grad_hess_hist_int32 = feature_hist_ptr[bin_offset]; + local_grad_hess_hist = (static_cast(static_cast(local_grad_hess_hist_int32 >> 16)) << 32) | (static_cast(local_grad_hess_hist_int32 & 0x0000ffff)); + } else { + local_grad_hess_hist = feature_hist_ptr[bin_offset]; + } + } + } else { + if (threadIdx_x >= static_cast(task->na_as_missing) && + threadIdx_x < feature_num_bin_minus_offset && !skip_sum) { + const unsigned int read_index = feature_num_bin_minus_offset - 1 - threadIdx_x; + if (USE_16BIT_BIN_HIST && !USE_16BIT_ACC_HIST) { + const int32_t local_grad_hess_hist_int32 = feature_hist_ptr[read_index]; + local_grad_hess_hist = (static_cast(static_cast(local_grad_hess_hist_int32 >> 16)) << 32) | (static_cast(local_grad_hess_hist_int32 & 0x0000ffff)); + } else { + local_grad_hess_hist = feature_hist_ptr[read_index]; + } + } + } + __syncthreads(); + local_gain = kMinScore; + local_grad_hess_hist = ShufflePrefixSum(local_grad_hess_hist, reinterpret_cast(shared_int_buffer)); + double sum_left_gradient = 0.0f; + double sum_left_hessian = 0.0f; + double sum_right_gradient = 0.0f; + double sum_right_hessian = 0.0f; + data_size_t left_count = 0; + data_size_t right_count = 0; + int64_t sum_left_gradient_hessian = 0; + int64_t sum_right_gradient_hessian = 0; + if (REVERSE) { + if (threadIdx_x >= static_cast(task->na_as_missing) && threadIdx_x <= task->num_bin - 2 && !skip_sum) { + sum_right_gradient_hessian = USE_16BIT_ACC_HIST ? + (static_cast(static_cast(local_grad_hess_hist >> 16)) << 32) | static_cast(local_grad_hess_hist & 0x0000ffff) : + local_grad_hess_hist; + sum_right_gradient = static_cast(static_cast((sum_right_gradient_hessian & 0xffffffff00000000) >> 32)) * grad_scale; + sum_right_hessian = static_cast(static_cast(sum_right_gradient_hessian & 0x00000000ffffffff)) * hess_scale; + right_count = static_cast(__double2int_rn(sum_right_hessian * cnt_factor)); + sum_left_gradient_hessian = sum_gradients_hessians - sum_right_gradient_hessian; + sum_left_gradient = static_cast(static_cast((sum_left_gradient_hessian & 0xffffffff00000000)>> 32)) * grad_scale; + sum_left_hessian = static_cast(static_cast(sum_left_gradient_hessian & 0x00000000ffffffff)) * hess_scale; + left_count = num_data - right_count; + if (sum_left_hessian >= min_sum_hessian_in_leaf && left_count >= min_data_in_leaf && + sum_right_hessian >= min_sum_hessian_in_leaf && right_count >= min_data_in_leaf && + (!USE_RAND || static_cast(task->num_bin - 2 - threadIdx_x) == rand_threshold)) { + double current_gain = CUDALeafSplits::GetSplitGains( + sum_left_gradient, sum_left_hessian + kEpsilon, sum_right_gradient, + sum_right_hessian + kEpsilon, lambda_l1, + lambda_l2, path_smooth, left_count, right_count, parent_output); + // gain with split is worse than without split + if (current_gain > min_gain_shift) { + local_gain = current_gain - min_gain_shift; + threshold_value = static_cast(task->num_bin - 2 - threadIdx_x); + threshold_found = true; + } + } + } + } else { + if (threadIdx_x <= feature_num_bin_minus_offset - 2 && !skip_sum) { + sum_left_gradient_hessian = USE_16BIT_ACC_HIST ? + (static_cast(static_cast(local_grad_hess_hist >> 16)) << 32) | static_cast(local_grad_hess_hist & 0x0000ffff) : + local_grad_hess_hist; + sum_left_gradient = static_cast(static_cast((sum_left_gradient_hessian & 0xffffffff00000000) >> 32)) * grad_scale; + sum_left_hessian = static_cast(static_cast(sum_left_gradient_hessian & 0x00000000ffffffff)) * hess_scale; + left_count = static_cast(__double2int_rn(sum_left_hessian * cnt_factor)); + sum_right_gradient_hessian = sum_gradients_hessians - sum_left_gradient_hessian; + sum_right_gradient = static_cast(static_cast((sum_right_gradient_hessian & 0xffffffff00000000) >> 32)) * grad_scale; + sum_right_hessian = static_cast(static_cast(sum_right_gradient_hessian & 0x00000000ffffffff)) * hess_scale; + right_count = num_data - left_count; + if (sum_left_hessian >= min_sum_hessian_in_leaf && left_count >= min_data_in_leaf && + sum_right_hessian >= min_sum_hessian_in_leaf && right_count >= min_data_in_leaf && + (!USE_RAND || static_cast(threadIdx_x + task->mfb_offset) == rand_threshold)) { + double current_gain = CUDALeafSplits::GetSplitGains( + sum_left_gradient, sum_left_hessian + kEpsilon, sum_right_gradient, + sum_right_hessian + kEpsilon, lambda_l1, + lambda_l2, path_smooth, left_count, right_count, parent_output); + // gain with split is worse than without split + if (current_gain > min_gain_shift) { + local_gain = current_gain - min_gain_shift; + threshold_value = static_cast(threadIdx_x + task->mfb_offset); + threshold_found = true; + } + } + } + } + __syncthreads(); + const uint32_t result = ReduceBestGain(local_gain, threshold_found, threadIdx_x, shared_double_buffer, shared_bool_buffer, shared_int_buffer); + if (threadIdx_x == 0) { + best_thread_index = result; + } + __syncthreads(); + if (threshold_found && threadIdx_x == best_thread_index) { + cuda_best_split_info->is_valid = true; + cuda_best_split_info->threshold = threshold_value; + cuda_best_split_info->gain = local_gain; + cuda_best_split_info->default_left = task->assume_out_default_left; + const double left_output = CUDALeafSplits::CalculateSplittedLeafOutput(sum_left_gradient, + sum_left_hessian, lambda_l1, lambda_l2, path_smooth, left_count, parent_output); + const double right_output = CUDALeafSplits::CalculateSplittedLeafOutput(sum_right_gradient, + sum_right_hessian, lambda_l1, lambda_l2, path_smooth, right_count, parent_output); + cuda_best_split_info->left_sum_gradients = sum_left_gradient; + cuda_best_split_info->left_sum_hessians = sum_left_hessian; + cuda_best_split_info->left_sum_of_gradients_hessians = sum_left_gradient_hessian; + cuda_best_split_info->left_count = left_count; + cuda_best_split_info->right_sum_gradients = sum_right_gradient; + cuda_best_split_info->right_sum_hessians = sum_right_hessian; + cuda_best_split_info->right_sum_of_gradients_hessians = sum_right_gradient_hessian; + cuda_best_split_info->right_count = right_count; + cuda_best_split_info->left_value = left_output; + cuda_best_split_info->left_gain = CUDALeafSplits::GetLeafGainGivenOutput(sum_left_gradient, + sum_left_hessian, lambda_l1, lambda_l2, left_output); + cuda_best_split_info->right_value = right_output; + cuda_best_split_info->right_gain = CUDALeafSplits::GetLeafGainGivenOutput(sum_right_gradient, + sum_right_hessian, lambda_l1, lambda_l2, right_output); + } +} + template __device__ void FindBestSplitsForLeafKernelCategoricalInner( // input feature information @@ -715,6 +884,169 @@ __global__ void FindBestSplitsForLeafKernel( } } + +template +__global__ void FindBestSplitsDiscretizedForLeafKernel( + // input feature information + const int8_t* is_feature_used_bytree, + // input task information + const int num_tasks, + const SplitFindTask* tasks, + CUDARandom* cuda_randoms, + // input leaf information + const CUDALeafSplitsStruct* smaller_leaf_splits, + const CUDALeafSplitsStruct* larger_leaf_splits, + const uint8_t smaller_leaf_num_bits_in_histogram_bin, + const uint8_t larger_leaf_num_bits_in_histogram_bin, + // input config parameter values + const data_size_t min_data_in_leaf, + const double min_sum_hessian_in_leaf, + const double min_gain_to_split, + const double lambda_l1, + const double lambda_l2, + const double path_smooth, + const double cat_smooth, + const double cat_l2, + const int max_cat_threshold, + const int min_data_per_group, + const int max_cat_to_onehot, + // gradient scale + const score_t* grad_scale, + const score_t* hess_scale, + // output + CUDASplitInfo* cuda_best_split_info) { + const unsigned int task_index = blockIdx.x; + const SplitFindTask* task = tasks + task_index; + const int inner_feature_index = task->inner_feature_index; + const double parent_gain = IS_LARGER ? larger_leaf_splits->gain : smaller_leaf_splits->gain; + const int64_t sum_gradients_hessians = IS_LARGER ? larger_leaf_splits->sum_of_gradients_hessians : smaller_leaf_splits->sum_of_gradients_hessians; + const data_size_t num_data = IS_LARGER ? larger_leaf_splits->num_data_in_leaf : smaller_leaf_splits->num_data_in_leaf; + const double parent_output = IS_LARGER ? larger_leaf_splits->leaf_value : smaller_leaf_splits->leaf_value; + const unsigned int output_offset = IS_LARGER ? (task_index + num_tasks) : task_index; + CUDASplitInfo* out = cuda_best_split_info + output_offset; + CUDARandom* cuda_random = USE_RAND ? + (IS_LARGER ? cuda_randoms + task_index * 2 + 1 : cuda_randoms + task_index * 2) : nullptr; + const bool use_16bit_bin = IS_LARGER ? (larger_leaf_num_bits_in_histogram_bin <= 16) : (smaller_leaf_num_bits_in_histogram_bin <= 16); + if (is_feature_used_bytree[inner_feature_index]) { + if (task->is_categorical) { + __threadfence(); // ensure store issued before trap + asm("trap;"); + } else { + if (!task->reverse) { + if (use_16bit_bin) { + const int32_t* hist_ptr = + reinterpret_cast(IS_LARGER ? larger_leaf_splits->hist_in_leaf : smaller_leaf_splits->hist_in_leaf) + task->hist_offset; + FindBestSplitsDiscretizedForLeafKernelInner( + // input feature information + hist_ptr, + // input task information + task, + cuda_random, + // input config parameter values + lambda_l1, + lambda_l2, + path_smooth, + min_data_in_leaf, + min_sum_hessian_in_leaf, + min_gain_to_split, + // input parent node information + parent_gain, + sum_gradients_hessians, + num_data, + parent_output, + // gradient scale + *grad_scale, + *hess_scale, + // output parameters + out); + } else { + const int32_t* hist_ptr = + reinterpret_cast(IS_LARGER ? larger_leaf_splits->hist_in_leaf : smaller_leaf_splits->hist_in_leaf) + task->hist_offset; + FindBestSplitsDiscretizedForLeafKernelInner( + // input feature information + hist_ptr, + // input task information + task, + cuda_random, + // input config parameter values + lambda_l1, + lambda_l2, + path_smooth, + min_data_in_leaf, + min_sum_hessian_in_leaf, + min_gain_to_split, + // input parent node information + parent_gain, + sum_gradients_hessians, + num_data, + parent_output, + // gradient scale + *grad_scale, + *hess_scale, + // output parameters + out); + } + } else { + if (use_16bit_bin) { + const int32_t* hist_ptr = + reinterpret_cast(IS_LARGER ? larger_leaf_splits->hist_in_leaf : smaller_leaf_splits->hist_in_leaf) + task->hist_offset; + FindBestSplitsDiscretizedForLeafKernelInner( + // input feature information + hist_ptr, + // input task information + task, + cuda_random, + // input config parameter values + lambda_l1, + lambda_l2, + path_smooth, + min_data_in_leaf, + min_sum_hessian_in_leaf, + min_gain_to_split, + // input parent node information + parent_gain, + sum_gradients_hessians, + num_data, + parent_output, + // gradient scale + *grad_scale, + *hess_scale, + // output parameters + out); + } else { + const int32_t* hist_ptr = + reinterpret_cast(IS_LARGER ? larger_leaf_splits->hist_in_leaf : smaller_leaf_splits->hist_in_leaf) + task->hist_offset; + FindBestSplitsDiscretizedForLeafKernelInner( + // input feature information + hist_ptr, + // input task information + task, + cuda_random, + // input config parameter values + lambda_l1, + lambda_l2, + path_smooth, + min_data_in_leaf, + min_sum_hessian_in_leaf, + min_gain_to_split, + // input parent node information + parent_gain, + sum_gradients_hessians, + num_data, + parent_output, + // gradient scale + *grad_scale, + *hess_scale, + // output parameters + out); + } + } + } + } else { + out->is_valid = false; + } +} + template __device__ void FindBestSplitsForLeafKernelInner_GlobalMemory( // input feature information @@ -1466,6 +1798,108 @@ void CUDABestSplitFinder::LaunchFindBestSplitsForLeafKernelInner2(LaunchFindBest #undef FindBestSplitsForLeafKernel_ARGS #undef GlobalMemory_Buffer_ARGS + +#define LaunchFindBestSplitsDiscretizedForLeafKernel_PARAMS \ + const CUDALeafSplitsStruct* smaller_leaf_splits, \ + const CUDALeafSplitsStruct* larger_leaf_splits, \ + const int smaller_leaf_index, \ + const int larger_leaf_index, \ + const bool is_smaller_leaf_valid, \ + const bool is_larger_leaf_valid, \ + const score_t* grad_scale, \ + const score_t* hess_scale, \ + const uint8_t smaller_num_bits_in_histogram_bins, \ + const uint8_t larger_num_bits_in_histogram_bins + +#define LaunchFindBestSplitsDiscretizedForLeafKernel_ARGS \ + smaller_leaf_splits, \ + larger_leaf_splits, \ + smaller_leaf_index, \ + larger_leaf_index, \ + is_smaller_leaf_valid, \ + is_larger_leaf_valid, \ + grad_scale, \ + hess_scale, \ + smaller_num_bits_in_histogram_bins, \ + larger_num_bits_in_histogram_bins + +#define FindBestSplitsDiscretizedForLeafKernel_ARGS \ + cuda_is_feature_used_bytree_, \ + num_tasks_, \ + cuda_split_find_tasks_.RawData(), \ + cuda_randoms_.RawData(), \ + smaller_leaf_splits, \ + larger_leaf_splits, \ + smaller_num_bits_in_histogram_bins, \ + larger_num_bits_in_histogram_bins, \ + min_data_in_leaf_, \ + min_sum_hessian_in_leaf_, \ + min_gain_to_split_, \ + lambda_l1_, \ + lambda_l2_, \ + path_smooth_, \ + cat_smooth_, \ + cat_l2_, \ + max_cat_threshold_, \ + min_data_per_group_, \ + max_cat_to_onehot_, \ + grad_scale, \ + hess_scale, \ + cuda_best_split_info_ + +void CUDABestSplitFinder::LaunchFindBestSplitsDiscretizedForLeafKernel(LaunchFindBestSplitsDiscretizedForLeafKernel_PARAMS) { + if (!is_smaller_leaf_valid && !is_larger_leaf_valid) { + return; + } + if (!extra_trees_) { + LaunchFindBestSplitsDiscretizedForLeafKernelInner0(LaunchFindBestSplitsDiscretizedForLeafKernel_ARGS); + } else { + LaunchFindBestSplitsDiscretizedForLeafKernelInner0(LaunchFindBestSplitsDiscretizedForLeafKernel_ARGS); + } +} + +template +void CUDABestSplitFinder::LaunchFindBestSplitsDiscretizedForLeafKernelInner0(LaunchFindBestSplitsDiscretizedForLeafKernel_PARAMS) { + if (lambda_l1_ <= 0.0f) { + LaunchFindBestSplitsDiscretizedForLeafKernelInner1(LaunchFindBestSplitsDiscretizedForLeafKernel_ARGS); + } else { + LaunchFindBestSplitsDiscretizedForLeafKernelInner1(LaunchFindBestSplitsDiscretizedForLeafKernel_ARGS); + } +} + +template +void CUDABestSplitFinder::LaunchFindBestSplitsDiscretizedForLeafKernelInner1(LaunchFindBestSplitsDiscretizedForLeafKernel_PARAMS) { + if (!use_smoothing_) { + LaunchFindBestSplitsDiscretizedForLeafKernelInner2(LaunchFindBestSplitsDiscretizedForLeafKernel_ARGS); + } else { + LaunchFindBestSplitsDiscretizedForLeafKernelInner2(LaunchFindBestSplitsDiscretizedForLeafKernel_ARGS); + } +} + +template +void CUDABestSplitFinder::LaunchFindBestSplitsDiscretizedForLeafKernelInner2(LaunchFindBestSplitsDiscretizedForLeafKernel_PARAMS) { + if (!use_global_memory_) { + if (is_smaller_leaf_valid) { + FindBestSplitsDiscretizedForLeafKernel + <<>> + (FindBestSplitsDiscretizedForLeafKernel_ARGS); + } + SynchronizeCUDADevice(__FILE__, __LINE__); + if (is_larger_leaf_valid) { + FindBestSplitsDiscretizedForLeafKernel + <<>> + (FindBestSplitsDiscretizedForLeafKernel_ARGS); + } + } else { + // TODO(shiyu1994) + } +} + +#undef LaunchFindBestSplitsDiscretizedForLeafKernel_PARAMS +#undef LaunchFindBestSplitsDiscretizedForLeafKernel_ARGS +#undef FindBestSplitsDiscretizedForLeafKernel_ARGS + + __device__ void ReduceBestSplit(bool* found, double* gain, uint32_t* shared_read_index, uint32_t num_features_aligned) { const uint32_t threadIdx_x = threadIdx.x; diff --git a/src/treelearner/cuda/cuda_best_split_finder.hpp b/src/treelearner/cuda/cuda_best_split_finder.hpp index 69f8169f8d85..2d9940312533 100644 --- a/src/treelearner/cuda/cuda_best_split_finder.hpp +++ b/src/treelearner/cuda/cuda_best_split_finder.hpp @@ -67,7 +67,11 @@ class CUDABestSplitFinder { const data_size_t num_data_in_smaller_leaf, const data_size_t num_data_in_larger_leaf, const double sum_hessians_in_smaller_leaf, - const double sum_hessians_in_larger_leaf); + const double sum_hessians_in_larger_leaf, + const score_t* grad_scale, + const score_t* hess_scale, + const uint8_t smaller_num_bits_in_histogram_bins, + const uint8_t larger_num_bits_in_histogram_bins); const CUDASplitInfo* FindBestFromAllSplits( const int cur_num_leaves, @@ -114,6 +118,31 @@ class CUDABestSplitFinder { #undef LaunchFindBestSplitsForLeafKernel_PARAMS + #define LaunchFindBestSplitsDiscretizedForLeafKernel_PARAMS \ + const CUDALeafSplitsStruct* smaller_leaf_splits, \ + const CUDALeafSplitsStruct* larger_leaf_splits, \ + const int smaller_leaf_index, \ + const int larger_leaf_index, \ + const bool is_smaller_leaf_valid, \ + const bool is_larger_leaf_valid, \ + const score_t* grad_scale, \ + const score_t* hess_scale, \ + const uint8_t smaller_num_bits_in_histogram_bins, \ + const uint8_t larger_num_bits_in_histogram_bins + + void LaunchFindBestSplitsDiscretizedForLeafKernel(LaunchFindBestSplitsDiscretizedForLeafKernel_PARAMS); + + template + void LaunchFindBestSplitsDiscretizedForLeafKernelInner0(LaunchFindBestSplitsDiscretizedForLeafKernel_PARAMS); + + template + void LaunchFindBestSplitsDiscretizedForLeafKernelInner1(LaunchFindBestSplitsDiscretizedForLeafKernel_PARAMS); + + template + void LaunchFindBestSplitsDiscretizedForLeafKernelInner2(LaunchFindBestSplitsDiscretizedForLeafKernel_PARAMS); + + #undef LaunchFindBestSplitsDiscretizedForLeafKernel_PARAMS + void LaunchSyncBestSplitForLeafKernel( const int host_smaller_leaf_index, const int host_larger_leaf_index, diff --git a/src/treelearner/cuda/cuda_data_partition.cpp b/src/treelearner/cuda/cuda_data_partition.cpp index 3ad157ef0105..c09021ad356f 100644 --- a/src/treelearner/cuda/cuda_data_partition.cpp +++ b/src/treelearner/cuda/cuda_data_partition.cpp @@ -368,6 +368,12 @@ void CUDADataPartition::ResetByLeafPred(const std::vector& leaf_pred, int n cur_num_leaves_ = num_leaves; } +void CUDADataPartition::ReduceLeafGradStat( + const score_t* gradients, const score_t* hessians, + CUDATree* tree, double* leaf_grad_stat_buffer, double* leaf_hess_state_buffer) const { + LaunchReduceLeafGradStat(gradients, hessians, tree, leaf_grad_stat_buffer, leaf_hess_state_buffer); +} + } // namespace LightGBM #endif // USE_CUDA diff --git a/src/treelearner/cuda/cuda_data_partition.cu b/src/treelearner/cuda/cuda_data_partition.cu index b1d3fa496ab9..3090b7a84176 100644 --- a/src/treelearner/cuda/cuda_data_partition.cu +++ b/src/treelearner/cuda/cuda_data_partition.cu @@ -1069,6 +1069,53 @@ void CUDADataPartition::LaunchAddPredictionToScoreKernel(const double* leaf_valu global_timer.Stop("CUDADataPartition::AddPredictionToScoreKernel"); } +__global__ void RenewDiscretizedTreeLeavesKernel( + const score_t* gradients, + const score_t* hessians, + const data_size_t* data_indices, + const data_size_t* leaf_data_start, + const data_size_t* leaf_num_data, + double* leaf_grad_stat_buffer, + double* leaf_hess_stat_buffer, + double* leaf_values) { + __shared__ double shared_mem_buffer[32]; + const int leaf_index = static_cast(blockIdx.x); + const data_size_t* data_indices_in_leaf = data_indices + leaf_data_start[leaf_index]; + const data_size_t num_data_in_leaf = leaf_num_data[leaf_index]; + double sum_gradients = 0.0f; + double sum_hessians = 0.0f; + for (data_size_t inner_data_index = static_cast(threadIdx.x); + inner_data_index < num_data_in_leaf; inner_data_index += static_cast(blockDim.x)) { + const data_size_t data_index = data_indices_in_leaf[inner_data_index]; + const score_t gradient = gradients[data_index]; + const score_t hessian = hessians[data_index]; + sum_gradients += static_cast(gradient); + sum_hessians += static_cast(hessian); + } + sum_gradients = ShuffleReduceSum(sum_gradients, shared_mem_buffer, blockDim.x); + __syncthreads(); + sum_hessians = ShuffleReduceSum(sum_hessians, shared_mem_buffer, blockDim.x); + if (threadIdx.x == 0) { + leaf_grad_stat_buffer[leaf_index] = sum_gradients; + leaf_hess_stat_buffer[leaf_index] = sum_hessians; + } +} + +void CUDADataPartition::LaunchReduceLeafGradStat( + const score_t* gradients, const score_t* hessians, + CUDATree* tree, double* leaf_grad_stat_buffer, double* leaf_hess_state_buffer) const { + const int num_blocks = tree->num_leaves(); + RenewDiscretizedTreeLeavesKernel<<>>( + gradients, + hessians, + cuda_data_indices_, + cuda_leaf_data_start_, + cuda_leaf_num_data_, + leaf_grad_stat_buffer, + leaf_hess_state_buffer, + tree->cuda_leaf_value_ref()); +} + } // namespace LightGBM #endif // USE_CUDA diff --git a/src/treelearner/cuda/cuda_data_partition.hpp b/src/treelearner/cuda/cuda_data_partition.hpp index 84050565c085..f6bbab9b8c65 100644 --- a/src/treelearner/cuda/cuda_data_partition.hpp +++ b/src/treelearner/cuda/cuda_data_partition.hpp @@ -78,6 +78,10 @@ class CUDADataPartition { void ResetByLeafPred(const std::vector& leaf_pred, int num_leaves); + void ReduceLeafGradStat( + const score_t* gradients, const score_t* hessians, + CUDATree* tree, double* leaf_grad_stat_buffer, double* leaf_hess_state_buffer) const; + data_size_t root_num_data() const { if (use_bagging_) { return num_used_indices_; @@ -292,6 +296,10 @@ class CUDADataPartition { void LaunchFillDataIndexToLeafIndex(); + void LaunchReduceLeafGradStat( + const score_t* gradients, const score_t* hessians, + CUDATree* tree, double* leaf_grad_stat_buffer, double* leaf_hess_state_buffer) const; + // Host memory // dataset information diff --git a/src/treelearner/cuda/cuda_gradient_discretizer.cu b/src/treelearner/cuda/cuda_gradient_discretizer.cu new file mode 100644 index 000000000000..bcea706b4285 --- /dev/null +++ b/src/treelearner/cuda/cuda_gradient_discretizer.cu @@ -0,0 +1,171 @@ +/*! + * Copyright (c) 2021 Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See LICENSE file in the project root for + * license information. + */ + +#ifdef USE_CUDA + +#include + +#include + +#include "cuda_gradient_discretizer.hpp" + +namespace LightGBM { + +__global__ void ReduceMinMaxKernel( + const data_size_t num_data, + const score_t* input_gradients, + const score_t* input_hessians, + score_t* grad_min_block_buffer, + score_t* grad_max_block_buffer, + score_t* hess_min_block_buffer, + score_t* hess_max_block_buffer) { + __shared__ score_t shared_mem_buffer[32]; + const data_size_t index = static_cast(threadIdx.x + blockIdx.x * blockDim.x); + score_t grad_max_val = kMinScore; + score_t grad_min_val = kMaxScore; + score_t hess_max_val = kMinScore; + score_t hess_min_val = kMaxScore; + if (index < num_data) { + grad_max_val = input_gradients[index]; + grad_min_val = input_gradients[index]; + hess_max_val = input_hessians[index]; + hess_min_val = input_hessians[index]; + } + grad_min_val = ShuffleReduceMin(grad_min_val, shared_mem_buffer, blockDim.x); + __syncthreads(); + grad_max_val = ShuffleReduceMax(grad_max_val, shared_mem_buffer, blockDim.x); + __syncthreads(); + hess_min_val = ShuffleReduceMin(hess_min_val, shared_mem_buffer, blockDim.x); + __syncthreads(); + hess_max_val = ShuffleReduceMax(hess_max_val, shared_mem_buffer, blockDim.x); + if (threadIdx.x == 0) { + grad_min_block_buffer[blockIdx.x] = grad_min_val; + grad_max_block_buffer[blockIdx.x] = grad_max_val; + hess_min_block_buffer[blockIdx.x] = hess_min_val; + hess_max_block_buffer[blockIdx.x] = hess_max_val; + } +} + +__global__ void ReduceBlockMinMaxKernel( + const int num_blocks, + const int grad_discretize_bins, + score_t* grad_min_block_buffer, + score_t* grad_max_block_buffer, + score_t* hess_min_block_buffer, + score_t* hess_max_block_buffer) { + __shared__ score_t shared_mem_buffer[32]; + score_t grad_max_val = kMinScore; + score_t grad_min_val = kMaxScore; + score_t hess_max_val = kMinScore; + score_t hess_min_val = kMaxScore; + for (int block_index = static_cast(threadIdx.x); block_index < num_blocks; block_index += static_cast(blockDim.x)) { + grad_min_val = min(grad_min_val, grad_min_block_buffer[block_index]); + grad_max_val = max(grad_max_val, grad_max_block_buffer[block_index]); + hess_min_val = min(hess_min_val, hess_min_block_buffer[block_index]); + hess_max_val = max(hess_max_val, hess_max_block_buffer[block_index]); + } + grad_min_val = ShuffleReduceMin(grad_min_val, shared_mem_buffer, blockDim.x); + __syncthreads(); + grad_max_val = ShuffleReduceMax(grad_max_val, shared_mem_buffer, blockDim.x); + __syncthreads(); + hess_max_val = ShuffleReduceMax(hess_max_val, shared_mem_buffer, blockDim.x); + __syncthreads(); + hess_max_val = ShuffleReduceMax(hess_max_val, shared_mem_buffer, blockDim.x); + if (threadIdx.x == 0) { + const score_t grad_abs_max = max(fabs(grad_min_val), fabs(grad_max_val)); + const score_t hess_abs_max = max(fabs(hess_min_val), fabs(hess_max_val)); + grad_min_block_buffer[0] = 1.0f / (grad_abs_max / (grad_discretize_bins / 2)); + grad_max_block_buffer[0] = (grad_abs_max / (grad_discretize_bins / 2)); + hess_min_block_buffer[0] = 1.0f / (hess_abs_max / (grad_discretize_bins)); + hess_max_block_buffer[0] = (hess_abs_max / (grad_discretize_bins)); + } +} + +template +__global__ void DiscretizeGradientsKernel( + const data_size_t num_data, + const score_t* input_gradients, + const score_t* input_hessians, + const score_t* grad_scale_ptr, + const score_t* hess_scale_ptr, + const int iter, + const int* random_values_use_start, + const score_t* gradient_random_values, + const score_t* hessian_random_values, + const int grad_discretize_bins, + int8_t* output_gradients_and_hessians) { + const int start = random_values_use_start[iter]; + const data_size_t index = static_cast(threadIdx.x + blockIdx.x * blockDim.x); + const score_t grad_scale = *grad_scale_ptr; + const score_t hess_scale = *hess_scale_ptr; + int16_t* output_gradients_and_hessians_ptr = reinterpret_cast(output_gradients_and_hessians); + if (index < num_data) { + if (STOCHASTIC_ROUNDING) { + const data_size_t index_offset = (index + start) % num_data; + const score_t gradient = input_gradients[index]; + const score_t hessian = input_hessians[index]; + const score_t gradient_random_value = gradient_random_values[index_offset]; + const score_t hessian_random_value = hessian_random_values[index_offset]; + output_gradients_and_hessians_ptr[2 * index + 1] = gradient > 0.0f ? + static_cast(gradient * grad_scale + gradient_random_value) : + static_cast(gradient * grad_scale - gradient_random_value); + output_gradients_and_hessians_ptr[2 * index] = static_cast(hessian * hess_scale + hessian_random_value); + } else { + const score_t gradient = input_gradients[index]; + const score_t hessian = input_hessians[index]; + output_gradients_and_hessians_ptr[2 * index + 1] = gradient > 0.0f ? + static_cast(gradient * grad_scale + 0.5) : + static_cast(gradient * grad_scale - 0.5); + output_gradients_and_hessians_ptr[2 * index] = static_cast(hessian * hess_scale + 0.5); + } + } +} + +void CUDAGradientDiscretizer::DiscretizeGradients( + const data_size_t num_data, + const score_t* input_gradients, + const score_t* input_hessians) { + ReduceMinMaxKernel<<>>( + num_data, input_gradients, input_hessians, + grad_min_block_buffer_.RawData(), + grad_max_block_buffer_.RawData(), + hess_min_block_buffer_.RawData(), + hess_max_block_buffer_.RawData()); + SynchronizeCUDADevice(__FILE__, __LINE__); + ReduceBlockMinMaxKernel<<<1, CUDA_GRADIENT_DISCRETIZER_BLOCK_SIZE>>>( + num_reduce_blocks_, + num_grad_quant_bins_, + grad_min_block_buffer_.RawData(), + grad_max_block_buffer_.RawData(), + hess_min_block_buffer_.RawData(), + hess_max_block_buffer_.RawData()); + SynchronizeCUDADevice(__FILE__, __LINE__); + + #define DiscretizeGradientsKernel_ARGS \ + num_data, \ + input_gradients, \ + input_hessians, \ + grad_min_block_buffer_.RawData(), \ + hess_min_block_buffer_.RawData(), \ + iter_, \ + random_values_use_start_.RawData(), \ + gradient_random_values_.RawData(), \ + hessian_random_values_.RawData(), \ + num_grad_quant_bins_, \ + discretized_gradients_and_hessians_.RawData() + + if (stochastic_rounding_) { + DiscretizeGradientsKernel<<>>(DiscretizeGradientsKernel_ARGS); + } else { + DiscretizeGradientsKernel<<>>(DiscretizeGradientsKernel_ARGS); + } + SynchronizeCUDADevice(__FILE__, __LINE__); + ++iter_; +} + +} // namespace LightGBM + +#endif // USE_CUDA diff --git a/src/treelearner/cuda/cuda_gradient_discretizer.hpp b/src/treelearner/cuda/cuda_gradient_discretizer.hpp new file mode 100644 index 000000000000..d5c2fb0e041a --- /dev/null +++ b/src/treelearner/cuda/cuda_gradient_discretizer.hpp @@ -0,0 +1,118 @@ +/*! + * Copyright (c) 2021 Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See LICENSE file in the project root for + * license information. + */ + +#ifndef LIGHTGBM_TREELEARNER_CUDA_CUDA_GRADIENT_DISCRETIZER_HPP_ +#define LIGHTGBM_TREELEARNER_CUDA_CUDA_GRADIENT_DISCRETIZER_HPP_ + +#ifdef USE_CUDA + +#include +#include +#include +#include + +#include +#include +#include + +#include "cuda_leaf_splits.hpp" +#include "../gradient_discretizer.hpp" + +namespace LightGBM { + +#define CUDA_GRADIENT_DISCRETIZER_BLOCK_SIZE (1024) + +class CUDAGradientDiscretizer: public GradientDiscretizer { + public: + CUDAGradientDiscretizer(int num_grad_quant_bins, int num_trees, int random_seed, bool is_constant_hessian, bool stochastic_roudning): + GradientDiscretizer(num_grad_quant_bins, num_trees, random_seed, is_constant_hessian, stochastic_roudning) { + } + + void DiscretizeGradients( + const data_size_t num_data, + const score_t* input_gradients, + const score_t* input_hessians) override; + + const int8_t* discretized_gradients_and_hessians() const override { return discretized_gradients_and_hessians_.RawData(); } + + double grad_scale() const override { + Log::Fatal("grad_scale() of CUDAGradientDiscretizer should not be called."); + return 0.0; + } + + double hess_scale() const override { + Log::Fatal("hess_scale() of CUDAGradientDiscretizer should not be called."); + return 0.0; + } + + const score_t* grad_scale_ptr() const { return grad_max_block_buffer_.RawData(); } + + const score_t* hess_scale_ptr() const { return hess_max_block_buffer_.RawData(); } + + void Init(const data_size_t num_data, const int num_leaves, + const int num_features, const Dataset* train_data) override { + GradientDiscretizer::Init(num_data, num_leaves, num_features, train_data); + discretized_gradients_and_hessians_.Resize(num_data * 2); + num_reduce_blocks_ = (num_data + CUDA_GRADIENT_DISCRETIZER_BLOCK_SIZE - 1) / CUDA_GRADIENT_DISCRETIZER_BLOCK_SIZE; + grad_min_block_buffer_.Resize(num_reduce_blocks_); + grad_max_block_buffer_.Resize(num_reduce_blocks_); + hess_min_block_buffer_.Resize(num_reduce_blocks_); + hess_max_block_buffer_.Resize(num_reduce_blocks_); + random_values_use_start_.Resize(num_trees_); + gradient_random_values_.Resize(num_data); + hessian_random_values_.Resize(num_data); + + std::vector gradient_random_values(num_data, 0.0f); + std::vector hessian_random_values(num_data, 0.0f); + std::vector random_values_use_start(num_trees_, 0); + + const int num_threads = OMP_NUM_THREADS(); + + std::mt19937 random_values_use_start_eng = std::mt19937(random_seed_); + std::uniform_int_distribution random_values_use_start_dist = std::uniform_int_distribution(0, num_data); + for (int tree_index = 0; tree_index < num_trees_; ++tree_index) { + random_values_use_start[tree_index] = random_values_use_start_dist(random_values_use_start_eng); + } + + int num_blocks = 0; + data_size_t block_size = 0; + Threading::BlockInfo(num_data, 512, &num_blocks, &block_size); + #pragma omp parallel for schedule(static, 1) num_threads(num_threads) + for (int thread_id = 0; thread_id < num_blocks; ++thread_id) { + const data_size_t start = thread_id * block_size; + const data_size_t end = std::min(start + block_size, num_data); + std::mt19937 gradient_random_values_eng(random_seed_ + thread_id); + std::uniform_real_distribution gradient_random_values_dist(0.0f, 1.0f); + std::mt19937 hessian_random_values_eng(random_seed_ + thread_id + num_threads); + std::uniform_real_distribution hessian_random_values_dist(0.0f, 1.0f); + for (data_size_t i = start; i < end; ++i) { + gradient_random_values[i] = gradient_random_values_dist(gradient_random_values_eng); + hessian_random_values[i] = hessian_random_values_dist(hessian_random_values_eng); + } + } + + CopyFromHostToCUDADevice(gradient_random_values_.RawData(), gradient_random_values.data(), gradient_random_values.size(), __FILE__, __LINE__); + CopyFromHostToCUDADevice(hessian_random_values_.RawData(), hessian_random_values.data(), hessian_random_values.size(), __FILE__, __LINE__); + CopyFromHostToCUDADevice(random_values_use_start_.RawData(), random_values_use_start.data(), random_values_use_start.size(), __FILE__, __LINE__); + iter_ = 0; + } + + protected: + mutable CUDAVector discretized_gradients_and_hessians_; + mutable CUDAVector grad_min_block_buffer_; + mutable CUDAVector grad_max_block_buffer_; + mutable CUDAVector hess_min_block_buffer_; + mutable CUDAVector hess_max_block_buffer_; + CUDAVector random_values_use_start_; + CUDAVector gradient_random_values_; + CUDAVector hessian_random_values_; + int num_reduce_blocks_; +}; + +} // namespace LightGBM + +#endif // USE_CUDA +#endif // LIGHTGBM_TREELEARNER_CUDA_CUDA_GRADIENT_DISCRETIZER_HPP_ diff --git a/src/treelearner/cuda/cuda_histogram_constructor.cpp b/src/treelearner/cuda/cuda_histogram_constructor.cpp index 7e6be1c1069c..659db2aad24c 100644 --- a/src/treelearner/cuda/cuda_histogram_constructor.cpp +++ b/src/treelearner/cuda/cuda_histogram_constructor.cpp @@ -20,7 +20,9 @@ CUDAHistogramConstructor::CUDAHistogramConstructor( const int min_data_in_leaf, const double min_sum_hessian_in_leaf, const int gpu_device_id, - const bool gpu_use_dp): + const bool gpu_use_dp, + const bool use_quantized_grad, + const int num_grad_quant_bins): num_data_(train_data->num_data()), num_features_(train_data->num_features()), num_leaves_(num_leaves), @@ -28,24 +30,14 @@ CUDAHistogramConstructor::CUDAHistogramConstructor( min_data_in_leaf_(min_data_in_leaf), min_sum_hessian_in_leaf_(min_sum_hessian_in_leaf), gpu_device_id_(gpu_device_id), - gpu_use_dp_(gpu_use_dp) { + gpu_use_dp_(gpu_use_dp), + use_quantized_grad_(use_quantized_grad), + num_grad_quant_bins_(num_grad_quant_bins) { InitFeatureMetaInfo(train_data, feature_hist_offsets); cuda_row_data_.reset(nullptr); - cuda_feature_num_bins_ = nullptr; - cuda_feature_hist_offsets_ = nullptr; - cuda_feature_most_freq_bins_ = nullptr; - cuda_hist_ = nullptr; - cuda_need_fix_histogram_features_ = nullptr; - cuda_need_fix_histogram_features_num_bin_aligned_ = nullptr; } CUDAHistogramConstructor::~CUDAHistogramConstructor() { - DeallocateCUDAMemory(&cuda_feature_num_bins_, __FILE__, __LINE__); - DeallocateCUDAMemory(&cuda_feature_hist_offsets_, __FILE__, __LINE__); - DeallocateCUDAMemory(&cuda_feature_most_freq_bins_, __FILE__, __LINE__); - DeallocateCUDAMemory(&cuda_hist_, __FILE__, __LINE__); - DeallocateCUDAMemory(&cuda_need_fix_histogram_features_, __FILE__, __LINE__); - DeallocateCUDAMemory(&cuda_need_fix_histogram_features_num_bin_aligned_, __FILE__, __LINE__); gpuAssert(cudaStreamDestroy(cuda_stream_), __FILE__, __LINE__); } @@ -84,54 +76,70 @@ void CUDAHistogramConstructor::InitFeatureMetaInfo(const Dataset* train_data, co void CUDAHistogramConstructor::BeforeTrain(const score_t* gradients, const score_t* hessians) { cuda_gradients_ = gradients; cuda_hessians_ = hessians; - SetCUDAMemory(cuda_hist_, 0, num_total_bin_ * 2 * num_leaves_, __FILE__, __LINE__); + cuda_hist_.SetValue(0); } void CUDAHistogramConstructor::Init(const Dataset* train_data, TrainingShareStates* share_state) { - AllocateCUDAMemory(&cuda_hist_, num_total_bin_ * 2 * num_leaves_, __FILE__, __LINE__); - SetCUDAMemory(cuda_hist_, 0, num_total_bin_ * 2 * num_leaves_, __FILE__, __LINE__); + cuda_hist_.Resize(static_cast(num_total_bin_ * 2 * num_leaves_)); + cuda_hist_.SetValue(0); - InitCUDAMemoryFromHostMemory(&cuda_feature_num_bins_, - feature_num_bins_.data(), feature_num_bins_.size(), __FILE__, __LINE__); - - InitCUDAMemoryFromHostMemory(&cuda_feature_hist_offsets_, - feature_hist_offsets_.data(), feature_hist_offsets_.size(), __FILE__, __LINE__); - - InitCUDAMemoryFromHostMemory(&cuda_feature_most_freq_bins_, - feature_most_freq_bins_.data(), feature_most_freq_bins_.size(), __FILE__, __LINE__); + cuda_feature_num_bins_.InitFromHostVector(feature_num_bins_); + cuda_feature_hist_offsets_.InitFromHostVector(feature_hist_offsets_); + cuda_feature_most_freq_bins_.InitFromHostVector(feature_most_freq_bins_); cuda_row_data_.reset(new CUDARowData(train_data, share_state, gpu_device_id_, gpu_use_dp_)); cuda_row_data_->Init(train_data, share_state); CUDASUCCESS_OR_FATAL(cudaStreamCreate(&cuda_stream_)); - InitCUDAMemoryFromHostMemory(&cuda_need_fix_histogram_features_, need_fix_histogram_features_.data(), need_fix_histogram_features_.size(), __FILE__, __LINE__); - InitCUDAMemoryFromHostMemory(&cuda_need_fix_histogram_features_num_bin_aligned_, need_fix_histogram_features_num_bin_aligend_.data(), - need_fix_histogram_features_num_bin_aligend_.size(), __FILE__, __LINE__); + cuda_need_fix_histogram_features_.InitFromHostVector(need_fix_histogram_features_); + cuda_need_fix_histogram_features_num_bin_aligned_.InitFromHostVector(need_fix_histogram_features_num_bin_aligend_); if (cuda_row_data_->NumLargeBinPartition() > 0) { int grid_dim_x = 0, grid_dim_y = 0, block_dim_x = 0, block_dim_y = 0; CalcConstructHistogramKernelDim(&grid_dim_x, &grid_dim_y, &block_dim_x, &block_dim_y, num_data_); - const size_t buffer_size = static_cast(grid_dim_y) * static_cast(num_total_bin_) * 2; - AllocateCUDAMemory(&cuda_hist_buffer_, buffer_size, __FILE__, __LINE__); + const size_t buffer_size = static_cast(grid_dim_y) * static_cast(num_total_bin_); + if (!use_quantized_grad_) { + if (gpu_use_dp_) { + // need to double the size of histogram buffer in global memory when using double precision in histogram construction + cuda_hist_buffer_.Resize(buffer_size * 4); + } else { + cuda_hist_buffer_.Resize(buffer_size * 2); + } + } else { + // use only half the size of histogram buffer in global memory when quantized training since each gradient and hessian takes only 2 bytes + cuda_hist_buffer_.Resize(buffer_size); + } } + hist_buffer_for_num_bit_change_.Resize(num_total_bin_ * 2); } void CUDAHistogramConstructor::ConstructHistogramForLeaf( const CUDALeafSplitsStruct* cuda_smaller_leaf_splits, - const CUDALeafSplitsStruct* cuda_larger_leaf_splits, + const CUDALeafSplitsStruct* /*cuda_larger_leaf_splits*/, const data_size_t num_data_in_smaller_leaf, const data_size_t num_data_in_larger_leaf, const double sum_hessians_in_smaller_leaf, - const double sum_hessians_in_larger_leaf) { + const double sum_hessians_in_larger_leaf, + const uint8_t num_bits_in_histogram_bins) { if ((num_data_in_smaller_leaf <= min_data_in_leaf_ || sum_hessians_in_smaller_leaf <= min_sum_hessian_in_leaf_) && (num_data_in_larger_leaf <= min_data_in_leaf_ || sum_hessians_in_larger_leaf <= min_sum_hessian_in_leaf_)) { return; } - LaunchConstructHistogramKernel(cuda_smaller_leaf_splits, num_data_in_smaller_leaf); + LaunchConstructHistogramKernel(cuda_smaller_leaf_splits, num_data_in_smaller_leaf, num_bits_in_histogram_bins); SynchronizeCUDADevice(__FILE__, __LINE__); +} + +void CUDAHistogramConstructor::SubtractHistogramForLeaf( + const CUDALeafSplitsStruct* cuda_smaller_leaf_splits, + const CUDALeafSplitsStruct* cuda_larger_leaf_splits, + const bool use_quantized_grad, + const uint8_t parent_num_bits_in_histogram_bins, + const uint8_t smaller_num_bits_in_histogram_bins, + const uint8_t larger_num_bits_in_histogram_bins) { global_timer.Start("CUDAHistogramConstructor::ConstructHistogramForLeaf::LaunchSubtractHistogramKernel"); - LaunchSubtractHistogramKernel(cuda_smaller_leaf_splits, cuda_larger_leaf_splits); + LaunchSubtractHistogramKernel(cuda_smaller_leaf_splits, cuda_larger_leaf_splits, use_quantized_grad, + parent_num_bits_in_histogram_bins, smaller_num_bits_in_histogram_bins, larger_num_bits_in_histogram_bins); global_timer.Stop("CUDAHistogramConstructor::ConstructHistogramForLeaf::LaunchSubtractHistogramKernel"); } @@ -152,33 +160,18 @@ void CUDAHistogramConstructor::ResetTrainingData(const Dataset* train_data, Trai num_data_ = train_data->num_data(); num_features_ = train_data->num_features(); InitFeatureMetaInfo(train_data, share_states->feature_hist_offsets()); - if (feature_num_bins_.size() > 0) { - DeallocateCUDAMemory(&cuda_feature_num_bins_, __FILE__, __LINE__); - DeallocateCUDAMemory(&cuda_feature_hist_offsets_, __FILE__, __LINE__); - DeallocateCUDAMemory(&cuda_feature_most_freq_bins_, __FILE__, __LINE__); - DeallocateCUDAMemory(&cuda_need_fix_histogram_features_, __FILE__, __LINE__); - DeallocateCUDAMemory(&cuda_need_fix_histogram_features_num_bin_aligned_, __FILE__, __LINE__); - DeallocateCUDAMemory(&cuda_hist_, __FILE__, __LINE__); - } - - AllocateCUDAMemory(&cuda_hist_, num_total_bin_ * 2 * num_leaves_, __FILE__, __LINE__); - SetCUDAMemory(cuda_hist_, 0, num_total_bin_ * 2 * num_leaves_, __FILE__, __LINE__); - - InitCUDAMemoryFromHostMemory(&cuda_feature_num_bins_, - feature_num_bins_.data(), feature_num_bins_.size(), __FILE__, __LINE__); - - InitCUDAMemoryFromHostMemory(&cuda_feature_hist_offsets_, - feature_hist_offsets_.data(), feature_hist_offsets_.size(), __FILE__, __LINE__); - InitCUDAMemoryFromHostMemory(&cuda_feature_most_freq_bins_, - feature_most_freq_bins_.data(), feature_most_freq_bins_.size(), __FILE__, __LINE__); + cuda_hist_.Resize(static_cast(num_total_bin_ * 2 * num_leaves_)); + cuda_hist_.SetValue(0); + cuda_feature_num_bins_.InitFromHostVector(feature_num_bins_); + cuda_feature_hist_offsets_.InitFromHostVector(feature_hist_offsets_); + cuda_feature_most_freq_bins_.InitFromHostVector(feature_most_freq_bins_); cuda_row_data_.reset(new CUDARowData(train_data, share_states, gpu_device_id_, gpu_use_dp_)); cuda_row_data_->Init(train_data, share_states); - InitCUDAMemoryFromHostMemory(&cuda_need_fix_histogram_features_, need_fix_histogram_features_.data(), need_fix_histogram_features_.size(), __FILE__, __LINE__); - InitCUDAMemoryFromHostMemory(&cuda_need_fix_histogram_features_num_bin_aligned_, need_fix_histogram_features_num_bin_aligend_.data(), - need_fix_histogram_features_num_bin_aligend_.size(), __FILE__, __LINE__); + cuda_need_fix_histogram_features_.InitFromHostVector(need_fix_histogram_features_); + cuda_need_fix_histogram_features_num_bin_aligned_.InitFromHostVector(need_fix_histogram_features_num_bin_aligend_); } void CUDAHistogramConstructor::ResetConfig(const Config* config) { @@ -186,9 +179,8 @@ void CUDAHistogramConstructor::ResetConfig(const Config* config) { num_leaves_ = config->num_leaves; min_data_in_leaf_ = config->min_data_in_leaf; min_sum_hessian_in_leaf_ = config->min_sum_hessian_in_leaf; - DeallocateCUDAMemory(&cuda_hist_, __FILE__, __LINE__); - AllocateCUDAMemory(&cuda_hist_, num_total_bin_ * 2 * num_leaves_, __FILE__, __LINE__); - SetCUDAMemory(cuda_hist_, 0, num_total_bin_ * 2 * num_leaves_, __FILE__, __LINE__); + cuda_hist_.Resize(static_cast(num_total_bin_ * 2 * num_leaves_)); + cuda_hist_.SetValue(0); } } // namespace LightGBM diff --git a/src/treelearner/cuda/cuda_histogram_constructor.cu b/src/treelearner/cuda/cuda_histogram_constructor.cu index c884383304a4..03d3b8979439 100644 --- a/src/treelearner/cuda/cuda_histogram_constructor.cu +++ b/src/treelearner/cuda/cuda_histogram_constructor.cu @@ -125,7 +125,7 @@ __global__ void CUDAConstructHistogramSparseKernel( } } -template +template __global__ void CUDAConstructHistogramDenseKernel_GlobalMemory( const CUDALeafSplitsStruct* smaller_leaf_splits, const score_t* cuda_gradients, @@ -135,7 +135,7 @@ __global__ void CUDAConstructHistogramDenseKernel_GlobalMemory( const uint32_t* column_hist_offsets_full, const int* feature_partition_column_index_offsets, const data_size_t num_data, - float* global_hist_buffer) { + HIST_TYPE* global_hist_buffer) { const int dim_y = static_cast(gridDim.y * blockDim.y); const data_size_t num_data_in_smaller_leaf = smaller_leaf_splits->num_data_in_leaf; const data_size_t num_data_per_thread = (num_data_in_smaller_leaf + dim_y - 1) / dim_y; @@ -150,7 +150,7 @@ __global__ void CUDAConstructHistogramDenseKernel_GlobalMemory( const uint32_t num_items_in_partition = (partition_hist_end - partition_hist_start) << 1; const unsigned int thread_idx = threadIdx.x + threadIdx.y * blockDim.x; const int num_total_bin = column_hist_offsets_full[gridDim.x]; - float* shared_hist = global_hist_buffer + (blockIdx.y * num_total_bin + partition_hist_start) * 2; + HIST_TYPE* shared_hist = global_hist_buffer + (blockIdx.y * num_total_bin + partition_hist_start) * 2; for (unsigned int i = thread_idx; i < num_items_in_partition; i += num_threads_per_block) { shared_hist[i] = 0.0f; } @@ -166,14 +166,14 @@ __global__ void CUDAConstructHistogramDenseKernel_GlobalMemory( data_size_t inner_data_index = static_cast(threadIdx_y); const int column_index = static_cast(threadIdx.x) + partition_column_start; if (threadIdx.x < static_cast(num_columns_in_partition)) { - float* shared_hist_ptr = shared_hist + (column_hist_offsets[column_index] << 1); + HIST_TYPE* shared_hist_ptr = shared_hist + (column_hist_offsets[column_index] << 1); for (data_size_t i = 0; i < num_iteration_this; ++i) { const data_size_t data_index = data_indices_ref_this_block[inner_data_index]; const score_t grad = cuda_gradients[data_index]; const score_t hess = cuda_hessians[data_index]; const uint32_t bin = static_cast(data_ptr[static_cast(data_index) * num_columns_in_partition + threadIdx.x]); const uint32_t pos = bin << 1; - float* pos_ptr = shared_hist_ptr + pos; + HIST_TYPE* pos_ptr = shared_hist_ptr + pos; atomicAdd_block(pos_ptr, grad); atomicAdd_block(pos_ptr + 1, hess); inner_data_index += blockDim.y; @@ -186,7 +186,7 @@ __global__ void CUDAConstructHistogramDenseKernel_GlobalMemory( } } -template +template __global__ void CUDAConstructHistogramSparseKernel_GlobalMemory( const CUDALeafSplitsStruct* smaller_leaf_splits, const score_t* cuda_gradients, @@ -196,7 +196,7 @@ __global__ void CUDAConstructHistogramSparseKernel_GlobalMemory( const DATA_PTR_TYPE* partition_ptr, const uint32_t* column_hist_offsets_full, const data_size_t num_data, - float* global_hist_buffer) { + HIST_TYPE* global_hist_buffer) { const int dim_y = static_cast(gridDim.y * blockDim.y); const data_size_t num_data_in_smaller_leaf = smaller_leaf_splits->num_data_in_leaf; const data_size_t num_data_per_thread = (num_data_in_smaller_leaf + dim_y - 1) / dim_y; @@ -209,7 +209,7 @@ __global__ void CUDAConstructHistogramSparseKernel_GlobalMemory( const uint32_t num_items_in_partition = (partition_hist_end - partition_hist_start) << 1; const unsigned int thread_idx = threadIdx.x + threadIdx.y * blockDim.x; const int num_total_bin = column_hist_offsets_full[gridDim.x]; - float* shared_hist = global_hist_buffer + (blockIdx.y * num_total_bin + partition_hist_start) * 2; + HIST_TYPE* shared_hist = global_hist_buffer + (blockIdx.y * num_total_bin + partition_hist_start) * 2; for (unsigned int i = thread_idx; i < num_items_in_partition; i += num_threads_per_block) { shared_hist[i] = 0.0f; } @@ -233,7 +233,7 @@ __global__ void CUDAConstructHistogramSparseKernel_GlobalMemory( const score_t hess = cuda_hessians[data_index]; const uint32_t bin = static_cast(data_ptr[row_start + threadIdx.x]); const uint32_t pos = bin << 1; - float* pos_ptr = shared_hist + pos; + HIST_TYPE* pos_ptr = shared_hist + pos; atomicAdd_block(pos_ptr, grad); atomicAdd_block(pos_ptr + 1, hess); } @@ -246,13 +246,278 @@ __global__ void CUDAConstructHistogramSparseKernel_GlobalMemory( } } +template +__global__ void CUDAConstructDiscretizedHistogramDenseKernel( + const CUDALeafSplitsStruct* smaller_leaf_splits, + const int32_t* cuda_gradients_and_hessians, + const BIN_TYPE* data, + const uint32_t* column_hist_offsets, + const uint32_t* column_hist_offsets_full, + const int* feature_partition_column_index_offsets, + const data_size_t num_data) { + const int dim_y = static_cast(gridDim.y * blockDim.y); + const data_size_t num_data_in_smaller_leaf = smaller_leaf_splits->num_data_in_leaf; + const data_size_t num_data_per_thread = (num_data_in_smaller_leaf + dim_y - 1) / dim_y; + const data_size_t* data_indices_ref = smaller_leaf_splits->data_indices_in_leaf; + __shared__ int16_t shared_hist[SHARED_HIST_SIZE]; + int32_t* shared_hist_packed = reinterpret_cast(shared_hist); + const unsigned int num_threads_per_block = blockDim.x * blockDim.y; + const int partition_column_start = feature_partition_column_index_offsets[blockIdx.x]; + const int partition_column_end = feature_partition_column_index_offsets[blockIdx.x + 1]; + const BIN_TYPE* data_ptr = data + partition_column_start * num_data; + const int num_columns_in_partition = partition_column_end - partition_column_start; + const uint32_t partition_hist_start = column_hist_offsets_full[blockIdx.x]; + const uint32_t partition_hist_end = column_hist_offsets_full[blockIdx.x + 1]; + const uint32_t num_items_in_partition = (partition_hist_end - partition_hist_start); + const unsigned int thread_idx = threadIdx.x + threadIdx.y * blockDim.x; + for (unsigned int i = thread_idx; i < num_items_in_partition; i += num_threads_per_block) { + shared_hist_packed[i] = 0; + } + __syncthreads(); + const unsigned int threadIdx_y = threadIdx.y; + const unsigned int blockIdx_y = blockIdx.y; + const data_size_t block_start = (blockIdx_y * blockDim.y) * num_data_per_thread; + const data_size_t* data_indices_ref_this_block = data_indices_ref + block_start; + data_size_t block_num_data = max(0, min(num_data_in_smaller_leaf - block_start, num_data_per_thread * static_cast(blockDim.y))); + const data_size_t num_iteration_total = (block_num_data + blockDim.y - 1) / blockDim.y; + const data_size_t remainder = block_num_data % blockDim.y; + const data_size_t num_iteration_this = remainder == 0 ? num_iteration_total : num_iteration_total - static_cast(threadIdx_y >= remainder); + data_size_t inner_data_index = static_cast(threadIdx_y); + const int column_index = static_cast(threadIdx.x) + partition_column_start; + if (threadIdx.x < static_cast(num_columns_in_partition)) { + int32_t* shared_hist_ptr = shared_hist_packed + (column_hist_offsets[column_index]); + for (data_size_t i = 0; i < num_iteration_this; ++i) { + const data_size_t data_index = data_indices_ref_this_block[inner_data_index]; + const int32_t grad_and_hess = cuda_gradients_and_hessians[data_index]; + const uint32_t bin = static_cast(data_ptr[data_index * num_columns_in_partition + threadIdx.x]); + int32_t* pos_ptr = shared_hist_ptr + bin; + atomicAdd_block(pos_ptr, grad_and_hess); + inner_data_index += blockDim.y; + } + } + __syncthreads(); + if (USE_16BIT_HIST) { + int32_t* feature_histogram_ptr = reinterpret_cast(smaller_leaf_splits->hist_in_leaf) + partition_hist_start; + for (unsigned int i = thread_idx; i < num_items_in_partition; i += num_threads_per_block) { + const int32_t packed_grad_hess = shared_hist_packed[i]; + atomicAdd_system(feature_histogram_ptr + i, packed_grad_hess); + } + } else { + atomic_add_long_t* feature_histogram_ptr = reinterpret_cast(smaller_leaf_splits->hist_in_leaf) + partition_hist_start; + for (unsigned int i = thread_idx; i < num_items_in_partition; i += num_threads_per_block) { + const int32_t packed_grad_hess = shared_hist_packed[i]; + const int64_t packed_grad_hess_int64 = (static_cast(static_cast(packed_grad_hess >> 16)) << 32) | (static_cast(packed_grad_hess & 0x0000ffff)); + atomicAdd_system(feature_histogram_ptr + i, (atomic_add_long_t)(packed_grad_hess_int64)); + } + } +} + +template +__global__ void CUDAConstructDiscretizedHistogramSparseKernel( + const CUDALeafSplitsStruct* smaller_leaf_splits, + const int32_t* cuda_gradients_and_hessians, + const BIN_TYPE* data, + const DATA_PTR_TYPE* row_ptr, + const DATA_PTR_TYPE* partition_ptr, + const uint32_t* column_hist_offsets_full, + const data_size_t num_data) { + const int dim_y = static_cast(gridDim.y * blockDim.y); + const data_size_t num_data_in_smaller_leaf = smaller_leaf_splits->num_data_in_leaf; + const data_size_t num_data_per_thread = (num_data_in_smaller_leaf + dim_y - 1) / dim_y; + const data_size_t* data_indices_ref = smaller_leaf_splits->data_indices_in_leaf; + __shared__ int16_t shared_hist[SHARED_HIST_SIZE]; + int32_t* shared_hist_packed = reinterpret_cast(shared_hist); + const unsigned int num_threads_per_block = blockDim.x * blockDim.y; + const DATA_PTR_TYPE* block_row_ptr = row_ptr + blockIdx.x * (num_data + 1); + const BIN_TYPE* data_ptr = data + partition_ptr[blockIdx.x]; + const uint32_t partition_hist_start = column_hist_offsets_full[blockIdx.x]; + const uint32_t partition_hist_end = column_hist_offsets_full[blockIdx.x + 1]; + const uint32_t num_items_in_partition = (partition_hist_end - partition_hist_start); + const unsigned int thread_idx = threadIdx.x + threadIdx.y * blockDim.x; + for (unsigned int i = thread_idx; i < num_items_in_partition; i += num_threads_per_block) { + shared_hist_packed[i] = 0.0f; + } + __syncthreads(); + const unsigned int threadIdx_y = threadIdx.y; + const unsigned int blockIdx_y = blockIdx.y; + const data_size_t block_start = (blockIdx_y * blockDim.y) * num_data_per_thread; + const data_size_t* data_indices_ref_this_block = data_indices_ref + block_start; + data_size_t block_num_data = max(0, min(num_data_in_smaller_leaf - block_start, num_data_per_thread * static_cast(blockDim.y))); + const data_size_t num_iteration_total = (block_num_data + blockDim.y - 1) / blockDim.y; + const data_size_t remainder = block_num_data % blockDim.y; + const data_size_t num_iteration_this = remainder == 0 ? num_iteration_total : num_iteration_total - static_cast(threadIdx_y >= remainder); + data_size_t inner_data_index = static_cast(threadIdx_y); + for (data_size_t i = 0; i < num_iteration_this; ++i) { + const data_size_t data_index = data_indices_ref_this_block[inner_data_index]; + const DATA_PTR_TYPE row_start = block_row_ptr[data_index]; + const DATA_PTR_TYPE row_end = block_row_ptr[data_index + 1]; + const DATA_PTR_TYPE row_size = row_end - row_start; + if (threadIdx.x < row_size) { + const int32_t grad_and_hess = cuda_gradients_and_hessians[data_index]; + const uint32_t bin = static_cast(data_ptr[row_start + threadIdx.x]); + int32_t* pos_ptr = shared_hist_packed + bin; + atomicAdd_block(pos_ptr, grad_and_hess); + } + inner_data_index += blockDim.y; + } + __syncthreads(); + if (USE_16BIT_HIST) { + int32_t* feature_histogram_ptr = reinterpret_cast(smaller_leaf_splits->hist_in_leaf) + partition_hist_start; + for (unsigned int i = thread_idx; i < num_items_in_partition; i += num_threads_per_block) { + const int32_t packed_grad_hess = shared_hist_packed[i]; + atomicAdd_system(feature_histogram_ptr + i, packed_grad_hess); + } + } else { + atomic_add_long_t* feature_histogram_ptr = reinterpret_cast(smaller_leaf_splits->hist_in_leaf) + partition_hist_start; + for (unsigned int i = thread_idx; i < num_items_in_partition; i += num_threads_per_block) { + const int32_t packed_grad_hess = shared_hist_packed[i]; + const int64_t packed_grad_hess_int64 = (static_cast(static_cast(packed_grad_hess >> 16)) << 32) | (static_cast(packed_grad_hess & 0x0000ffff)); + atomicAdd_system(feature_histogram_ptr + i, (atomic_add_long_t)(packed_grad_hess_int64)); + } + } +} + +template +__global__ void CUDAConstructDiscretizedHistogramDenseKernel_GlobalMemory( + const CUDALeafSplitsStruct* smaller_leaf_splits, + const int32_t* cuda_gradients_and_hessians, + const BIN_TYPE* data, + const uint32_t* column_hist_offsets, + const uint32_t* column_hist_offsets_full, + const int* feature_partition_column_index_offsets, + const data_size_t num_data, + int32_t* global_hist_buffer) { + const int dim_y = static_cast(gridDim.y * blockDim.y); + const data_size_t num_data_in_smaller_leaf = smaller_leaf_splits->num_data_in_leaf; + const data_size_t num_data_per_thread = (num_data_in_smaller_leaf + dim_y - 1) / dim_y; + const data_size_t* data_indices_ref = smaller_leaf_splits->data_indices_in_leaf; + const unsigned int num_threads_per_block = blockDim.x * blockDim.y; + const int partition_column_start = feature_partition_column_index_offsets[blockIdx.x]; + const int partition_column_end = feature_partition_column_index_offsets[blockIdx.x + 1]; + const BIN_TYPE* data_ptr = data + partition_column_start * num_data; + const int num_columns_in_partition = partition_column_end - partition_column_start; + const uint32_t partition_hist_start = column_hist_offsets_full[blockIdx.x]; + const uint32_t partition_hist_end = column_hist_offsets_full[blockIdx.x + 1]; + const uint32_t num_items_in_partition = (partition_hist_end - partition_hist_start); + const int num_total_bin = column_hist_offsets_full[gridDim.x]; + int32_t* shared_hist_packed = global_hist_buffer + (blockIdx.y * num_total_bin + partition_column_start); + const unsigned int thread_idx = threadIdx.x + threadIdx.y * blockDim.x; + for (unsigned int i = thread_idx; i < num_items_in_partition; i += num_threads_per_block) { + shared_hist_packed[i] = 0; + } + __syncthreads(); + const unsigned int threadIdx_y = threadIdx.y; + const unsigned int blockIdx_y = blockIdx.y; + const data_size_t block_start = (blockIdx_y * blockDim.y) * num_data_per_thread; + const data_size_t* data_indices_ref_this_block = data_indices_ref + block_start; + data_size_t block_num_data = max(0, min(num_data_in_smaller_leaf - block_start, num_data_per_thread * static_cast(blockDim.y))); + const data_size_t num_iteration_total = (block_num_data + blockDim.y - 1) / blockDim.y; + const data_size_t remainder = block_num_data % blockDim.y; + const data_size_t num_iteration_this = remainder == 0 ? num_iteration_total : num_iteration_total - static_cast(threadIdx_y >= remainder); + data_size_t inner_data_index = static_cast(threadIdx_y); + const int column_index = static_cast(threadIdx.x) + partition_column_start; + if (threadIdx.x < static_cast(num_columns_in_partition)) { + int32_t* shared_hist_ptr = shared_hist_packed + (column_hist_offsets[column_index]); + for (data_size_t i = 0; i < num_iteration_this; ++i) { + const data_size_t data_index = data_indices_ref_this_block[inner_data_index]; + const int32_t grad_and_hess = cuda_gradients_and_hessians[data_index]; + const uint32_t bin = static_cast(data_ptr[data_index * num_columns_in_partition + threadIdx.x]); + int32_t* pos_ptr = shared_hist_ptr + bin; + atomicAdd_block(pos_ptr, grad_and_hess); + inner_data_index += blockDim.y; + } + } + __syncthreads(); + if (USE_16BIT_HIST) { + int32_t* feature_histogram_ptr = reinterpret_cast(smaller_leaf_splits->hist_in_leaf) + partition_hist_start; + for (unsigned int i = thread_idx; i < num_items_in_partition; i += num_threads_per_block) { + const int32_t packed_grad_hess = shared_hist_packed[i]; + atomicAdd_system(feature_histogram_ptr + i, packed_grad_hess); + } + } else { + atomic_add_long_t* feature_histogram_ptr = reinterpret_cast(smaller_leaf_splits->hist_in_leaf) + partition_hist_start; + for (unsigned int i = thread_idx; i < num_items_in_partition; i += num_threads_per_block) { + const int32_t packed_grad_hess = shared_hist_packed[i]; + const int64_t packed_grad_hess_int64 = (static_cast(static_cast(packed_grad_hess >> 16)) << 32) | (static_cast(packed_grad_hess & 0x0000ffff)); + atomicAdd_system(feature_histogram_ptr + i, (atomic_add_long_t)(packed_grad_hess_int64)); + } + } +} + +template +__global__ void CUDAConstructDiscretizedHistogramSparseKernel_GlobalMemory( + const CUDALeafSplitsStruct* smaller_leaf_splits, + const int32_t* cuda_gradients_and_hessians, + const BIN_TYPE* data, + const DATA_PTR_TYPE* row_ptr, + const DATA_PTR_TYPE* partition_ptr, + const uint32_t* column_hist_offsets_full, + const data_size_t num_data, + int32_t* global_hist_buffer) { + const int dim_y = static_cast(gridDim.y * blockDim.y); + const data_size_t num_data_in_smaller_leaf = smaller_leaf_splits->num_data_in_leaf; + const data_size_t num_data_per_thread = (num_data_in_smaller_leaf + dim_y - 1) / dim_y; + const data_size_t* data_indices_ref = smaller_leaf_splits->data_indices_in_leaf; + const int num_total_bin = column_hist_offsets_full[gridDim.x]; + const unsigned int num_threads_per_block = blockDim.x * blockDim.y; + const DATA_PTR_TYPE* block_row_ptr = row_ptr + blockIdx.x * (num_data + 1); + const BIN_TYPE* data_ptr = data + partition_ptr[blockIdx.x]; + const uint32_t partition_hist_start = column_hist_offsets_full[blockIdx.x]; + const uint32_t partition_hist_end = column_hist_offsets_full[blockIdx.x + 1]; + const uint32_t num_items_in_partition = (partition_hist_end - partition_hist_start); + const unsigned int thread_idx = threadIdx.x + threadIdx.y * blockDim.x; + int32_t* shared_hist_packed = global_hist_buffer + (blockIdx.y * num_total_bin + partition_hist_start); + for (unsigned int i = thread_idx; i < num_items_in_partition; i += num_threads_per_block) { + shared_hist_packed[i] = 0.0f; + } + __syncthreads(); + const unsigned int threadIdx_y = threadIdx.y; + const unsigned int blockIdx_y = blockIdx.y; + const data_size_t block_start = (blockIdx_y * blockDim.y) * num_data_per_thread; + const data_size_t* data_indices_ref_this_block = data_indices_ref + block_start; + data_size_t block_num_data = max(0, min(num_data_in_smaller_leaf - block_start, num_data_per_thread * static_cast(blockDim.y))); + const data_size_t num_iteration_total = (block_num_data + blockDim.y - 1) / blockDim.y; + const data_size_t remainder = block_num_data % blockDim.y; + const data_size_t num_iteration_this = remainder == 0 ? num_iteration_total : num_iteration_total - static_cast(threadIdx_y >= remainder); + data_size_t inner_data_index = static_cast(threadIdx_y); + for (data_size_t i = 0; i < num_iteration_this; ++i) { + const data_size_t data_index = data_indices_ref_this_block[inner_data_index]; + const DATA_PTR_TYPE row_start = block_row_ptr[data_index]; + const DATA_PTR_TYPE row_end = block_row_ptr[data_index + 1]; + const DATA_PTR_TYPE row_size = row_end - row_start; + if (threadIdx.x < row_size) { + const int32_t grad_and_hess = cuda_gradients_and_hessians[data_index]; + const uint32_t bin = static_cast(data_ptr[row_start + threadIdx.x]); + int32_t* pos_ptr = shared_hist_packed + bin; + atomicAdd_block(pos_ptr, grad_and_hess); + } + inner_data_index += blockDim.y; + } + __syncthreads(); + if (USE_16BIT_HIST) { + int32_t* feature_histogram_ptr = reinterpret_cast(smaller_leaf_splits->hist_in_leaf) + partition_hist_start; + for (unsigned int i = thread_idx; i < num_items_in_partition; i += num_threads_per_block) { + const int32_t packed_grad_hess = shared_hist_packed[i]; + atomicAdd_system(feature_histogram_ptr + i, packed_grad_hess); + } + } else { + atomic_add_long_t* feature_histogram_ptr = reinterpret_cast(smaller_leaf_splits->hist_in_leaf) + partition_hist_start; + for (unsigned int i = thread_idx; i < num_items_in_partition; i += num_threads_per_block) { + const int32_t packed_grad_hess = shared_hist_packed[i]; + const int64_t packed_grad_hess_int64 = (static_cast(static_cast(packed_grad_hess >> 16)) << 32) | (static_cast(packed_grad_hess & 0x0000ffff)); + atomicAdd_system(feature_histogram_ptr + i, (atomic_add_long_t)(packed_grad_hess_int64)); + } + } +} + void CUDAHistogramConstructor::LaunchConstructHistogramKernel( const CUDALeafSplitsStruct* cuda_smaller_leaf_splits, - const data_size_t num_data_in_smaller_leaf) { + const data_size_t num_data_in_smaller_leaf, + const uint8_t num_bits_in_histogram_bins) { if (cuda_row_data_->shared_hist_size() == DP_SHARED_HIST_SIZE && gpu_use_dp_) { - LaunchConstructHistogramKernelInner(cuda_smaller_leaf_splits, num_data_in_smaller_leaf); + LaunchConstructHistogramKernelInner(cuda_smaller_leaf_splits, num_data_in_smaller_leaf, num_bits_in_histogram_bins); } else if (cuda_row_data_->shared_hist_size() == SP_SHARED_HIST_SIZE && !gpu_use_dp_) { - LaunchConstructHistogramKernelInner(cuda_smaller_leaf_splits, num_data_in_smaller_leaf); + LaunchConstructHistogramKernelInner(cuda_smaller_leaf_splits, num_data_in_smaller_leaf, num_bits_in_histogram_bins); } else { Log::Fatal("Unknown shared histogram size %d", cuda_row_data_->shared_hist_size()); } @@ -261,13 +526,14 @@ void CUDAHistogramConstructor::LaunchConstructHistogramKernel( template void CUDAHistogramConstructor::LaunchConstructHistogramKernelInner( const CUDALeafSplitsStruct* cuda_smaller_leaf_splits, - const data_size_t num_data_in_smaller_leaf) { + const data_size_t num_data_in_smaller_leaf, + const uint8_t num_bits_in_histogram_bins) { if (cuda_row_data_->bit_type() == 8) { - LaunchConstructHistogramKernelInner0(cuda_smaller_leaf_splits, num_data_in_smaller_leaf); + LaunchConstructHistogramKernelInner0(cuda_smaller_leaf_splits, num_data_in_smaller_leaf, num_bits_in_histogram_bins); } else if (cuda_row_data_->bit_type() == 16) { - LaunchConstructHistogramKernelInner0(cuda_smaller_leaf_splits, num_data_in_smaller_leaf); + LaunchConstructHistogramKernelInner0(cuda_smaller_leaf_splits, num_data_in_smaller_leaf, num_bits_in_histogram_bins); } else if (cuda_row_data_->bit_type() == 32) { - LaunchConstructHistogramKernelInner0(cuda_smaller_leaf_splits, num_data_in_smaller_leaf); + LaunchConstructHistogramKernelInner0(cuda_smaller_leaf_splits, num_data_in_smaller_leaf, num_bits_in_histogram_bins); } else { Log::Fatal("Unknown bit_type = %d", cuda_row_data_->bit_type()); } @@ -276,16 +542,17 @@ void CUDAHistogramConstructor::LaunchConstructHistogramKernelInner( template void CUDAHistogramConstructor::LaunchConstructHistogramKernelInner0( const CUDALeafSplitsStruct* cuda_smaller_leaf_splits, - const data_size_t num_data_in_smaller_leaf) { + const data_size_t num_data_in_smaller_leaf, + const uint8_t num_bits_in_histogram_bins) { if (cuda_row_data_->row_ptr_bit_type() == 16) { - LaunchConstructHistogramKernelInner1(cuda_smaller_leaf_splits, num_data_in_smaller_leaf); + LaunchConstructHistogramKernelInner1(cuda_smaller_leaf_splits, num_data_in_smaller_leaf, num_bits_in_histogram_bins); } else if (cuda_row_data_->row_ptr_bit_type() == 32) { - LaunchConstructHistogramKernelInner1(cuda_smaller_leaf_splits, num_data_in_smaller_leaf); + LaunchConstructHistogramKernelInner1(cuda_smaller_leaf_splits, num_data_in_smaller_leaf, num_bits_in_histogram_bins); } else if (cuda_row_data_->row_ptr_bit_type() == 64) { - LaunchConstructHistogramKernelInner1(cuda_smaller_leaf_splits, num_data_in_smaller_leaf); + LaunchConstructHistogramKernelInner1(cuda_smaller_leaf_splits, num_data_in_smaller_leaf, num_bits_in_histogram_bins); } else { if (!cuda_row_data_->is_sparse()) { - LaunchConstructHistogramKernelInner1(cuda_smaller_leaf_splits, num_data_in_smaller_leaf); + LaunchConstructHistogramKernelInner1(cuda_smaller_leaf_splits, num_data_in_smaller_leaf, num_bits_in_histogram_bins); } else { Log::Fatal("Unknown row_ptr_bit_type = %d", cuda_row_data_->row_ptr_bit_type()); } @@ -295,18 +562,20 @@ void CUDAHistogramConstructor::LaunchConstructHistogramKernelInner0( template void CUDAHistogramConstructor::LaunchConstructHistogramKernelInner1( const CUDALeafSplitsStruct* cuda_smaller_leaf_splits, - const data_size_t num_data_in_smaller_leaf) { + const data_size_t num_data_in_smaller_leaf, + const uint8_t num_bits_in_histogram_bins) { if (cuda_row_data_->NumLargeBinPartition() == 0) { - LaunchConstructHistogramKernelInner2(cuda_smaller_leaf_splits, num_data_in_smaller_leaf); + LaunchConstructHistogramKernelInner2(cuda_smaller_leaf_splits, num_data_in_smaller_leaf, num_bits_in_histogram_bins); } else { - LaunchConstructHistogramKernelInner2(cuda_smaller_leaf_splits, num_data_in_smaller_leaf); + LaunchConstructHistogramKernelInner2(cuda_smaller_leaf_splits, num_data_in_smaller_leaf, num_bits_in_histogram_bins); } } template void CUDAHistogramConstructor::LaunchConstructHistogramKernelInner2( const CUDALeafSplitsStruct* cuda_smaller_leaf_splits, - const data_size_t num_data_in_smaller_leaf) { + const data_size_t num_data_in_smaller_leaf, + const uint8_t num_bits_in_histogram_bins) { int grid_dim_x = 0; int grid_dim_y = 0; int block_dim_x = 0; @@ -314,47 +583,139 @@ void CUDAHistogramConstructor::LaunchConstructHistogramKernelInner2( CalcConstructHistogramKernelDim(&grid_dim_x, &grid_dim_y, &block_dim_x, &block_dim_y, num_data_in_smaller_leaf); dim3 grid_dim(grid_dim_x, grid_dim_y); dim3 block_dim(block_dim_x, block_dim_y); - if (!USE_GLOBAL_MEM_BUFFER) { - if (cuda_row_data_->is_sparse()) { - CUDAConstructHistogramSparseKernel<<>>( - cuda_smaller_leaf_splits, - cuda_gradients_, cuda_hessians_, - cuda_row_data_->GetBin(), - cuda_row_data_->GetRowPtr(), - cuda_row_data_->GetPartitionPtr(), - cuda_row_data_->cuda_partition_hist_offsets(), - num_data_); + if (use_quantized_grad_) { + if (USE_GLOBAL_MEM_BUFFER) { + if (cuda_row_data_->is_sparse()) { + if (num_bits_in_histogram_bins <= 16) { + CUDAConstructDiscretizedHistogramSparseKernel_GlobalMemory<<>>( + cuda_smaller_leaf_splits, + reinterpret_cast(cuda_gradients_), + cuda_row_data_->GetBin(), + cuda_row_data_->GetRowPtr(), + cuda_row_data_->GetPartitionPtr(), + cuda_row_data_->cuda_partition_hist_offsets(), + num_data_, + reinterpret_cast(cuda_hist_buffer_.RawData())); + } else { + CUDAConstructDiscretizedHistogramSparseKernel_GlobalMemory<<>>( + cuda_smaller_leaf_splits, + reinterpret_cast(cuda_gradients_), + cuda_row_data_->GetBin(), + cuda_row_data_->GetRowPtr(), + cuda_row_data_->GetPartitionPtr(), + cuda_row_data_->cuda_partition_hist_offsets(), + num_data_, + reinterpret_cast(cuda_hist_buffer_.RawData())); + } + } else { + if (num_bits_in_histogram_bins <= 16) { + CUDAConstructDiscretizedHistogramDenseKernel_GlobalMemory<<>>( + cuda_smaller_leaf_splits, + reinterpret_cast(cuda_gradients_), + cuda_row_data_->GetBin(), + cuda_row_data_->cuda_column_hist_offsets(), + cuda_row_data_->cuda_partition_hist_offsets(), + cuda_row_data_->cuda_feature_partition_column_index_offsets(), + num_data_, + reinterpret_cast(cuda_hist_buffer_.RawData())); + } else { + CUDAConstructDiscretizedHistogramDenseKernel_GlobalMemory<<>>( + cuda_smaller_leaf_splits, + reinterpret_cast(cuda_gradients_), + cuda_row_data_->GetBin(), + cuda_row_data_->cuda_column_hist_offsets(), + cuda_row_data_->cuda_partition_hist_offsets(), + cuda_row_data_->cuda_feature_partition_column_index_offsets(), + num_data_, + reinterpret_cast(cuda_hist_buffer_.RawData())); + } + } } else { - CUDAConstructHistogramDenseKernel<<>>( - cuda_smaller_leaf_splits, - cuda_gradients_, cuda_hessians_, - cuda_row_data_->GetBin(), - cuda_row_data_->cuda_column_hist_offsets(), - cuda_row_data_->cuda_partition_hist_offsets(), - cuda_row_data_->cuda_feature_partition_column_index_offsets(), - num_data_); + if (cuda_row_data_->is_sparse()) { + if (num_bits_in_histogram_bins <= 16) { + CUDAConstructDiscretizedHistogramSparseKernel<<>>( + cuda_smaller_leaf_splits, + reinterpret_cast(cuda_gradients_), + cuda_row_data_->GetBin(), + cuda_row_data_->GetRowPtr(), + cuda_row_data_->GetPartitionPtr(), + cuda_row_data_->cuda_partition_hist_offsets(), + num_data_); + } else { + CUDAConstructDiscretizedHistogramSparseKernel<<>>( + cuda_smaller_leaf_splits, + reinterpret_cast(cuda_gradients_), + cuda_row_data_->GetBin(), + cuda_row_data_->GetRowPtr(), + cuda_row_data_->GetPartitionPtr(), + cuda_row_data_->cuda_partition_hist_offsets(), + num_data_); + } + } else { + if (num_bits_in_histogram_bins <= 16) { + CUDAConstructDiscretizedHistogramDenseKernel<<>>( + cuda_smaller_leaf_splits, + reinterpret_cast(cuda_gradients_), + cuda_row_data_->GetBin(), + cuda_row_data_->cuda_column_hist_offsets(), + cuda_row_data_->cuda_partition_hist_offsets(), + cuda_row_data_->cuda_feature_partition_column_index_offsets(), + num_data_); + } else { + CUDAConstructDiscretizedHistogramDenseKernel<<>>( + cuda_smaller_leaf_splits, + reinterpret_cast(cuda_gradients_), + cuda_row_data_->GetBin(), + cuda_row_data_->cuda_column_hist_offsets(), + cuda_row_data_->cuda_partition_hist_offsets(), + cuda_row_data_->cuda_feature_partition_column_index_offsets(), + num_data_); + } + } } } else { - if (cuda_row_data_->is_sparse()) { - CUDAConstructHistogramSparseKernel_GlobalMemory<<>>( - cuda_smaller_leaf_splits, - cuda_gradients_, cuda_hessians_, - cuda_row_data_->GetBin(), - cuda_row_data_->GetRowPtr(), - cuda_row_data_->GetPartitionPtr(), - cuda_row_data_->cuda_partition_hist_offsets(), - num_data_, - cuda_hist_buffer_); + if (!USE_GLOBAL_MEM_BUFFER) { + if (cuda_row_data_->is_sparse()) { + CUDAConstructHistogramSparseKernel<<>>( + cuda_smaller_leaf_splits, + cuda_gradients_, cuda_hessians_, + cuda_row_data_->GetBin(), + cuda_row_data_->GetRowPtr(), + cuda_row_data_->GetPartitionPtr(), + cuda_row_data_->cuda_partition_hist_offsets(), + num_data_); + } else { + CUDAConstructHistogramDenseKernel<<>>( + cuda_smaller_leaf_splits, + cuda_gradients_, cuda_hessians_, + cuda_row_data_->GetBin(), + cuda_row_data_->cuda_column_hist_offsets(), + cuda_row_data_->cuda_partition_hist_offsets(), + cuda_row_data_->cuda_feature_partition_column_index_offsets(), + num_data_); + } } else { - CUDAConstructHistogramDenseKernel_GlobalMemory<<>>( - cuda_smaller_leaf_splits, - cuda_gradients_, cuda_hessians_, - cuda_row_data_->GetBin(), - cuda_row_data_->cuda_column_hist_offsets(), - cuda_row_data_->cuda_partition_hist_offsets(), - cuda_row_data_->cuda_feature_partition_column_index_offsets(), - num_data_, - cuda_hist_buffer_); + if (cuda_row_data_->is_sparse()) { + CUDAConstructHistogramSparseKernel_GlobalMemory<<>>( + cuda_smaller_leaf_splits, + cuda_gradients_, cuda_hessians_, + cuda_row_data_->GetBin(), + cuda_row_data_->GetRowPtr(), + cuda_row_data_->GetPartitionPtr(), + cuda_row_data_->cuda_partition_hist_offsets(), + num_data_, + reinterpret_cast(cuda_hist_buffer_.RawData())); + } else { + CUDAConstructHistogramDenseKernel_GlobalMemory<<>>( + cuda_smaller_leaf_splits, + cuda_gradients_, cuda_hessians_, + cuda_row_data_->GetBin(), + cuda_row_data_->cuda_column_hist_offsets(), + cuda_row_data_->cuda_partition_hist_offsets(), + cuda_row_data_->cuda_feature_partition_column_index_offsets(), + num_data_, + reinterpret_cast(cuda_hist_buffer_.RawData())); + } } } } @@ -403,28 +764,195 @@ __global__ void FixHistogramKernel( } } +template +__global__ void SubtractHistogramDiscretizedKernel( + const int num_total_bin, + const CUDALeafSplitsStruct* cuda_smaller_leaf_splits, + const CUDALeafSplitsStruct* cuda_larger_leaf_splits, + hist_t* num_bit_change_buffer) { + const unsigned int global_thread_index = threadIdx.x + blockIdx.x * blockDim.x; + const int cuda_larger_leaf_index_ref = cuda_larger_leaf_splits->leaf_index; + if (cuda_larger_leaf_index_ref >= 0) { + if (PARENT_USE_16BIT_HIST) { + const int32_t* smaller_leaf_hist = reinterpret_cast(cuda_smaller_leaf_splits->hist_in_leaf); + int32_t* larger_leaf_hist = reinterpret_cast(cuda_larger_leaf_splits->hist_in_leaf); + if (global_thread_index < num_total_bin) { + larger_leaf_hist[global_thread_index] -= smaller_leaf_hist[global_thread_index]; + } + } else if (LARGER_USE_16BIT_HIST) { + int32_t* buffer = reinterpret_cast(num_bit_change_buffer); + const int32_t* smaller_leaf_hist = reinterpret_cast(cuda_smaller_leaf_splits->hist_in_leaf); + int64_t* larger_leaf_hist = reinterpret_cast(cuda_larger_leaf_splits->hist_in_leaf); + if (global_thread_index < num_total_bin) { + const int64_t parent_hist_item = larger_leaf_hist[global_thread_index]; + const int32_t smaller_hist_item = smaller_leaf_hist[global_thread_index]; + const int64_t smaller_hist_item_int64 = (static_cast(static_cast(smaller_hist_item >> 16)) << 32) | + static_cast(smaller_hist_item & 0x0000ffff); + const int64_t larger_hist_item = parent_hist_item - smaller_hist_item_int64; + buffer[global_thread_index] = static_cast(static_cast(larger_hist_item >> 32) << 16) | + static_cast(larger_hist_item & 0x000000000000ffff); + } + } else if (SMALLER_USE_16BIT_HIST) { + const int32_t* smaller_leaf_hist = reinterpret_cast(cuda_smaller_leaf_splits->hist_in_leaf); + int64_t* larger_leaf_hist = reinterpret_cast(cuda_larger_leaf_splits->hist_in_leaf); + if (global_thread_index < num_total_bin) { + const int64_t parent_hist_item = larger_leaf_hist[global_thread_index]; + const int32_t smaller_hist_item = smaller_leaf_hist[global_thread_index]; + const int64_t smaller_hist_item_int64 = (static_cast(static_cast(smaller_hist_item >> 16)) << 32) | + static_cast(smaller_hist_item & 0x0000ffff); + const int64_t larger_hist_item = parent_hist_item - smaller_hist_item_int64; + larger_leaf_hist[global_thread_index] = larger_hist_item; + } + } else { + const int64_t* smaller_leaf_hist = reinterpret_cast(cuda_smaller_leaf_splits->hist_in_leaf); + int64_t* larger_leaf_hist = reinterpret_cast(cuda_larger_leaf_splits->hist_in_leaf); + if (global_thread_index < num_total_bin) { + larger_leaf_hist[global_thread_index] -= smaller_leaf_hist[global_thread_index]; + } + } + } +} + +__global__ void CopyChangedNumBitHistogram( + const int num_total_bin, + const CUDALeafSplitsStruct* cuda_larger_leaf_splits, + hist_t* num_bit_change_buffer) { + int32_t* hist_dst = reinterpret_cast(cuda_larger_leaf_splits->hist_in_leaf); + const int32_t* hist_src = reinterpret_cast(num_bit_change_buffer); + const unsigned int global_thread_index = threadIdx.x + blockIdx.x * blockDim.x; + if (global_thread_index < static_cast(num_total_bin)) { + hist_dst[global_thread_index] = hist_src[global_thread_index]; + } +} + +template +__global__ void FixHistogramDiscretizedKernel( + const uint32_t* cuda_feature_num_bins, + const uint32_t* cuda_feature_hist_offsets, + const uint32_t* cuda_feature_most_freq_bins, + const int* cuda_need_fix_histogram_features, + const uint32_t* cuda_need_fix_histogram_features_num_bin_aligned, + const CUDALeafSplitsStruct* cuda_smaller_leaf_splits) { + __shared__ int64_t shared_mem_buffer[32]; + const unsigned int blockIdx_x = blockIdx.x; + const int feature_index = cuda_need_fix_histogram_features[blockIdx_x]; + const uint32_t num_bin_aligned = cuda_need_fix_histogram_features_num_bin_aligned[blockIdx_x]; + const uint32_t feature_hist_offset = cuda_feature_hist_offsets[feature_index]; + const uint32_t most_freq_bin = cuda_feature_most_freq_bins[feature_index]; + if (USE_16BIT_HIST) { + const int64_t leaf_sum_gradients_hessians_int64 = cuda_smaller_leaf_splits->sum_of_gradients_hessians; + const int32_t leaf_sum_gradients_hessians = + (static_cast(leaf_sum_gradients_hessians_int64 >> 32) << 16) | static_cast(leaf_sum_gradients_hessians_int64 & 0x000000000000ffff); + int32_t* feature_hist = reinterpret_cast(cuda_smaller_leaf_splits->hist_in_leaf) + feature_hist_offset; + const unsigned int threadIdx_x = threadIdx.x; + const uint32_t num_bin = cuda_feature_num_bins[feature_index]; + const int32_t bin_gradient_hessian = (threadIdx_x < num_bin && threadIdx_x != most_freq_bin) ? feature_hist[threadIdx_x] : 0; + const int32_t sum_gradient_hessian = ShuffleReduceSum( + bin_gradient_hessian, + reinterpret_cast(shared_mem_buffer), + num_bin_aligned); + if (threadIdx_x == 0) { + feature_hist[most_freq_bin] = leaf_sum_gradients_hessians - sum_gradient_hessian; + } + } else { + const int64_t leaf_sum_gradients_hessians = cuda_smaller_leaf_splits->sum_of_gradients_hessians; + int64_t* feature_hist = reinterpret_cast(cuda_smaller_leaf_splits->hist_in_leaf) + feature_hist_offset; + const unsigned int threadIdx_x = threadIdx.x; + const uint32_t num_bin = cuda_feature_num_bins[feature_index]; + const int64_t bin_gradient_hessian = (threadIdx_x < num_bin && threadIdx_x != most_freq_bin) ? feature_hist[threadIdx_x] : 0; + const int64_t sum_gradient_hessian = ShuffleReduceSum(bin_gradient_hessian, shared_mem_buffer, num_bin_aligned); + if (threadIdx_x == 0) { + feature_hist[most_freq_bin] = leaf_sum_gradients_hessians - sum_gradient_hessian; + } + } +} + void CUDAHistogramConstructor::LaunchSubtractHistogramKernel( const CUDALeafSplitsStruct* cuda_smaller_leaf_splits, - const CUDALeafSplitsStruct* cuda_larger_leaf_splits) { - const int num_subtract_threads = 2 * num_total_bin_; - const int num_subtract_blocks = (num_subtract_threads + SUBTRACT_BLOCK_SIZE - 1) / SUBTRACT_BLOCK_SIZE; - global_timer.Start("CUDAHistogramConstructor::FixHistogramKernel"); - if (need_fix_histogram_features_.size() > 0) { - FixHistogramKernel<<>>( - cuda_feature_num_bins_, - cuda_feature_hist_offsets_, - cuda_feature_most_freq_bins_, - cuda_need_fix_histogram_features_, - cuda_need_fix_histogram_features_num_bin_aligned_, - cuda_smaller_leaf_splits); - } - global_timer.Stop("CUDAHistogramConstructor::FixHistogramKernel"); - global_timer.Start("CUDAHistogramConstructor::SubtractHistogramKernel"); - SubtractHistogramKernel<<>>( - num_total_bin_, - cuda_smaller_leaf_splits, - cuda_larger_leaf_splits); - global_timer.Stop("CUDAHistogramConstructor::SubtractHistogramKernel"); + const CUDALeafSplitsStruct* cuda_larger_leaf_splits, + const bool use_discretized_grad, + const uint8_t parent_num_bits_in_histogram_bins, + const uint8_t smaller_num_bits_in_histogram_bins, + const uint8_t larger_num_bits_in_histogram_bins) { + if (!use_discretized_grad) { + const int num_subtract_threads = 2 * num_total_bin_; + const int num_subtract_blocks = (num_subtract_threads + SUBTRACT_BLOCK_SIZE - 1) / SUBTRACT_BLOCK_SIZE; + global_timer.Start("CUDAHistogramConstructor::FixHistogramKernel"); + if (need_fix_histogram_features_.size() > 0) { + FixHistogramKernel<<>>( + cuda_feature_num_bins_.RawData(), + cuda_feature_hist_offsets_.RawData(), + cuda_feature_most_freq_bins_.RawData(), + cuda_need_fix_histogram_features_.RawData(), + cuda_need_fix_histogram_features_num_bin_aligned_.RawData(), + cuda_smaller_leaf_splits); + } + global_timer.Stop("CUDAHistogramConstructor::FixHistogramKernel"); + global_timer.Start("CUDAHistogramConstructor::SubtractHistogramKernel"); + SubtractHistogramKernel<<>>( + num_total_bin_, + cuda_smaller_leaf_splits, + cuda_larger_leaf_splits); + global_timer.Stop("CUDAHistogramConstructor::SubtractHistogramKernel"); + } else { + const int num_subtract_threads = num_total_bin_; + const int num_subtract_blocks = (num_subtract_threads + SUBTRACT_BLOCK_SIZE - 1) / SUBTRACT_BLOCK_SIZE; + global_timer.Start("CUDAHistogramConstructor::FixHistogramDiscretizedKernel"); + if (need_fix_histogram_features_.size() > 0) { + if (smaller_num_bits_in_histogram_bins <= 16) { + FixHistogramDiscretizedKernel<<>>( + cuda_feature_num_bins_.RawData(), + cuda_feature_hist_offsets_.RawData(), + cuda_feature_most_freq_bins_.RawData(), + cuda_need_fix_histogram_features_.RawData(), + cuda_need_fix_histogram_features_num_bin_aligned_.RawData(), + cuda_smaller_leaf_splits); + } else { + FixHistogramDiscretizedKernel<<>>( + cuda_feature_num_bins_.RawData(), + cuda_feature_hist_offsets_.RawData(), + cuda_feature_most_freq_bins_.RawData(), + cuda_need_fix_histogram_features_.RawData(), + cuda_need_fix_histogram_features_num_bin_aligned_.RawData(), + cuda_smaller_leaf_splits); + } + } + global_timer.Stop("CUDAHistogramConstructor::FixHistogramDiscretizedKernel"); + global_timer.Start("CUDAHistogramConstructor::SubtractHistogramDiscretizedKernel"); + if (parent_num_bits_in_histogram_bins <= 16) { + CHECK_LE(smaller_num_bits_in_histogram_bins, 16); + CHECK_LE(larger_num_bits_in_histogram_bins, 16); + SubtractHistogramDiscretizedKernel<<>>( + num_total_bin_, + cuda_smaller_leaf_splits, + cuda_larger_leaf_splits, + hist_buffer_for_num_bit_change_.RawData()); + } else if (larger_num_bits_in_histogram_bins <= 16) { + CHECK_LE(smaller_num_bits_in_histogram_bins, 16); + SubtractHistogramDiscretizedKernel<<>>( + num_total_bin_, + cuda_smaller_leaf_splits, + cuda_larger_leaf_splits, + hist_buffer_for_num_bit_change_.RawData()); + CopyChangedNumBitHistogram<<>>( + num_total_bin_, + cuda_larger_leaf_splits, + hist_buffer_for_num_bit_change_.RawData()); + } else if (smaller_num_bits_in_histogram_bins <= 16) { + SubtractHistogramDiscretizedKernel<<>>( + num_total_bin_, + cuda_smaller_leaf_splits, + cuda_larger_leaf_splits, + hist_buffer_for_num_bit_change_.RawData()); + } else { + SubtractHistogramDiscretizedKernel<<>>( + num_total_bin_, + cuda_smaller_leaf_splits, + cuda_larger_leaf_splits, + hist_buffer_for_num_bit_change_.RawData()); + } + global_timer.Stop("CUDAHistogramConstructor::SubtractHistogramDiscretizedKernel"); + } } } // namespace LightGBM diff --git a/src/treelearner/cuda/cuda_histogram_constructor.hpp b/src/treelearner/cuda/cuda_histogram_constructor.hpp index 7e600e7c01b4..ddc78cb17d90 100644 --- a/src/treelearner/cuda/cuda_histogram_constructor.hpp +++ b/src/treelearner/cuda/cuda_histogram_constructor.hpp @@ -9,6 +9,7 @@ #ifdef USE_CUDA #include +#include #include #include @@ -37,7 +38,9 @@ class CUDAHistogramConstructor { const int min_data_in_leaf, const double min_sum_hessian_in_leaf, const int gpu_device_id, - const bool gpu_use_dp); + const bool gpu_use_dp, + const bool use_discretized_grad, + const int grad_discretized_bins); ~CUDAHistogramConstructor(); @@ -49,7 +52,16 @@ class CUDAHistogramConstructor { const data_size_t num_data_in_smaller_leaf, const data_size_t num_data_in_larger_leaf, const double sum_hessians_in_smaller_leaf, - const double sum_hessians_in_larger_leaf); + const double sum_hessians_in_larger_leaf, + const uint8_t num_bits_in_histogram_bins); + + void SubtractHistogramForLeaf( + const CUDALeafSplitsStruct* cuda_smaller_leaf_splits, + const CUDALeafSplitsStruct* cuda_larger_leaf_splits, + const bool use_discretized_grad, + const uint8_t parent_num_bits_in_histogram_bins, + const uint8_t smaller_num_bits_in_histogram_bins, + const uint8_t larger_num_bits_in_histogram_bins); void ResetTrainingData(const Dataset* train_data, TrainingShareStates* share_states); @@ -57,9 +69,9 @@ class CUDAHistogramConstructor { void BeforeTrain(const score_t* gradients, const score_t* hessians); - const hist_t* cuda_hist() const { return cuda_hist_; } + const hist_t* cuda_hist() const { return cuda_hist_.RawData(); } - hist_t* cuda_hist_pointer() { return cuda_hist_; } + hist_t* cuda_hist_pointer() { return cuda_hist_.RawData(); } private: void InitFeatureMetaInfo(const Dataset* train_data, const std::vector& feature_hist_offsets); @@ -74,30 +86,39 @@ class CUDAHistogramConstructor { template void LaunchConstructHistogramKernelInner( const CUDALeafSplitsStruct* cuda_smaller_leaf_splits, - const data_size_t num_data_in_smaller_leaf); + const data_size_t num_data_in_smaller_leaf, + const uint8_t num_bits_in_histogram_bins); template void LaunchConstructHistogramKernelInner0( const CUDALeafSplitsStruct* cuda_smaller_leaf_splits, - const data_size_t num_data_in_smaller_leaf); + const data_size_t num_data_in_smaller_leaf, + const uint8_t num_bits_in_histogram_bins); template void LaunchConstructHistogramKernelInner1( const CUDALeafSplitsStruct* cuda_smaller_leaf_splits, - const data_size_t num_data_in_smaller_leaf); + const data_size_t num_data_in_smaller_leaf, + const uint8_t num_bits_in_histogram_bins); template void LaunchConstructHistogramKernelInner2( const CUDALeafSplitsStruct* cuda_smaller_leaf_splits, - const data_size_t num_data_in_smaller_leaf); + const data_size_t num_data_in_smaller_leaf, + const uint8_t num_bits_in_histogram_bins); void LaunchConstructHistogramKernel( const CUDALeafSplitsStruct* cuda_smaller_leaf_splits, - const data_size_t num_data_in_smaller_leaf); + const data_size_t num_data_in_smaller_leaf, + const uint8_t num_bits_in_histogram_bins); void LaunchSubtractHistogramKernel( const CUDALeafSplitsStruct* cuda_smaller_leaf_splits, - const CUDALeafSplitsStruct* cuda_larger_leaf_splits); + const CUDALeafSplitsStruct* cuda_larger_leaf_splits, + const bool use_discretized_grad, + const uint8_t parent_num_bits_in_histogram_bins, + const uint8_t smaller_num_bits_in_histogram_bins, + const uint8_t larger_num_bits_in_histogram_bins); // Host memory @@ -136,19 +157,21 @@ class CUDAHistogramConstructor { /*! \brief CUDA row wise data */ std::unique_ptr cuda_row_data_; /*! \brief number of bins per feature */ - uint32_t* cuda_feature_num_bins_; + CUDAVector cuda_feature_num_bins_; /*! \brief offsets in histogram of all features */ - uint32_t* cuda_feature_hist_offsets_; + CUDAVector cuda_feature_hist_offsets_; /*! \brief most frequent bins in each feature */ - uint32_t* cuda_feature_most_freq_bins_; + CUDAVector cuda_feature_most_freq_bins_; /*! \brief CUDA histograms */ - hist_t* cuda_hist_; + CUDAVector cuda_hist_; /*! \brief CUDA histograms buffer for each block */ - float* cuda_hist_buffer_; + CUDAVector cuda_hist_buffer_; /*! \brief indices of feature whose histograms need to be fixed */ - int* cuda_need_fix_histogram_features_; + CUDAVector cuda_need_fix_histogram_features_; /*! \brief aligned number of bins of the features whose histograms need to be fixed */ - uint32_t* cuda_need_fix_histogram_features_num_bin_aligned_; + CUDAVector cuda_need_fix_histogram_features_num_bin_aligned_; + /*! \brief histogram buffer used in histogram subtraction with different number of bits for histogram bins */ + CUDAVector hist_buffer_for_num_bit_change_; // CUDA memory, held by other object @@ -161,6 +184,10 @@ class CUDAHistogramConstructor { const int gpu_device_id_; /*! \brief use double precision histogram per block */ const bool gpu_use_dp_; + /*! \brief whether to use quantized gradients */ + const bool use_quantized_grad_; + /*! \brief the number of bins to quantized gradients */ + const int num_grad_quant_bins_; }; } // namespace LightGBM diff --git a/src/treelearner/cuda/cuda_leaf_splits.cpp b/src/treelearner/cuda/cuda_leaf_splits.cpp index 6aa020d9ea0d..57b5b777c142 100644 --- a/src/treelearner/cuda/cuda_leaf_splits.cpp +++ b/src/treelearner/cuda/cuda_leaf_splits.cpp @@ -11,27 +11,22 @@ namespace LightGBM { CUDALeafSplits::CUDALeafSplits(const data_size_t num_data): -num_data_(num_data) { - cuda_struct_ = nullptr; - cuda_sum_of_gradients_buffer_ = nullptr; - cuda_sum_of_hessians_buffer_ = nullptr; -} +num_data_(num_data) {} -CUDALeafSplits::~CUDALeafSplits() { - DeallocateCUDAMemory(&cuda_struct_, __FILE__, __LINE__); - DeallocateCUDAMemory(&cuda_sum_of_gradients_buffer_, __FILE__, __LINE__); - DeallocateCUDAMemory(&cuda_sum_of_hessians_buffer_, __FILE__, __LINE__); -} +CUDALeafSplits::~CUDALeafSplits() {} -void CUDALeafSplits::Init() { +void CUDALeafSplits::Init(const bool use_quantized_grad) { num_blocks_init_from_gradients_ = (num_data_ + NUM_THRADS_PER_BLOCK_LEAF_SPLITS - 1) / NUM_THRADS_PER_BLOCK_LEAF_SPLITS; // allocate more memory for sum reduction in CUDA // only the first element records the final sum - AllocateCUDAMemory(&cuda_sum_of_gradients_buffer_, num_blocks_init_from_gradients_, __FILE__, __LINE__); - AllocateCUDAMemory(&cuda_sum_of_hessians_buffer_, num_blocks_init_from_gradients_, __FILE__, __LINE__); + cuda_sum_of_gradients_buffer_.Resize(static_cast(num_blocks_init_from_gradients_)); + cuda_sum_of_hessians_buffer_.Resize(static_cast(num_blocks_init_from_gradients_)); + if (use_quantized_grad) { + cuda_sum_of_gradients_hessians_buffer_.Resize(static_cast(num_blocks_init_from_gradients_)); + } - AllocateCUDAMemory(&cuda_struct_, 1, __FILE__, __LINE__); + cuda_struct_.Resize(1); } void CUDALeafSplits::InitValues() { @@ -46,24 +41,33 @@ void CUDALeafSplits::InitValues( const data_size_t num_used_indices, hist_t* cuda_hist_in_leaf, double* root_sum_hessians) { cuda_gradients_ = cuda_gradients; cuda_hessians_ = cuda_hessians; - SetCUDAMemory(cuda_sum_of_gradients_buffer_, 0, num_blocks_init_from_gradients_, __FILE__, __LINE__); - SetCUDAMemory(cuda_sum_of_hessians_buffer_, 0, num_blocks_init_from_gradients_, __FILE__, __LINE__); + cuda_sum_of_gradients_buffer_.SetValue(0); + cuda_sum_of_hessians_buffer_.SetValue(0); LaunchInitValuesKernal(lambda_l1, lambda_l2, cuda_bagging_data_indices, cuda_data_indices_in_leaf, num_used_indices, cuda_hist_in_leaf); - CopyFromCUDADeviceToHost(root_sum_hessians, cuda_sum_of_hessians_buffer_, 1, __FILE__, __LINE__); + CopyFromCUDADeviceToHost(root_sum_hessians, cuda_sum_of_hessians_buffer_.RawData(), 1, __FILE__, __LINE__); + SynchronizeCUDADevice(__FILE__, __LINE__); +} + +void CUDALeafSplits::InitValues( + const double lambda_l1, const double lambda_l2, + const int16_t* cuda_gradients_and_hessians, + const data_size_t* cuda_bagging_data_indices, + const data_size_t* cuda_data_indices_in_leaf, const data_size_t num_used_indices, + hist_t* cuda_hist_in_leaf, double* root_sum_hessians, + const score_t* grad_scale, const score_t* hess_scale) { + cuda_gradients_ = reinterpret_cast(cuda_gradients_and_hessians); + cuda_hessians_ = nullptr; + LaunchInitValuesKernal(lambda_l1, lambda_l2, cuda_bagging_data_indices, cuda_data_indices_in_leaf, num_used_indices, cuda_hist_in_leaf, grad_scale, hess_scale); + CopyFromCUDADeviceToHost(root_sum_hessians, cuda_sum_of_hessians_buffer_.RawData(), 1, __FILE__, __LINE__); SynchronizeCUDADevice(__FILE__, __LINE__); } void CUDALeafSplits::Resize(const data_size_t num_data) { - if (num_data > num_data_) { - DeallocateCUDAMemory(&cuda_sum_of_gradients_buffer_, __FILE__, __LINE__); - DeallocateCUDAMemory(&cuda_sum_of_hessians_buffer_, __FILE__, __LINE__); - num_blocks_init_from_gradients_ = (num_data + NUM_THRADS_PER_BLOCK_LEAF_SPLITS - 1) / NUM_THRADS_PER_BLOCK_LEAF_SPLITS; - AllocateCUDAMemory(&cuda_sum_of_gradients_buffer_, num_blocks_init_from_gradients_, __FILE__, __LINE__); - AllocateCUDAMemory(&cuda_sum_of_hessians_buffer_, num_blocks_init_from_gradients_, __FILE__, __LINE__); - } else { - num_blocks_init_from_gradients_ = (num_data + NUM_THRADS_PER_BLOCK_LEAF_SPLITS - 1) / NUM_THRADS_PER_BLOCK_LEAF_SPLITS; - } num_data_ = num_data; + num_blocks_init_from_gradients_ = (num_data + NUM_THRADS_PER_BLOCK_LEAF_SPLITS - 1) / NUM_THRADS_PER_BLOCK_LEAF_SPLITS; + cuda_sum_of_gradients_buffer_.Resize(static_cast(num_blocks_init_from_gradients_)); + cuda_sum_of_hessians_buffer_.Resize(static_cast(num_blocks_init_from_gradients_)); + cuda_sum_of_gradients_hessians_buffer_.Resize(static_cast(num_blocks_init_from_gradients_)); } } // namespace LightGBM diff --git a/src/treelearner/cuda/cuda_leaf_splits.cu b/src/treelearner/cuda/cuda_leaf_splits.cu index 29e42f67ead9..ae505ecd55dd 100644 --- a/src/treelearner/cuda/cuda_leaf_splits.cu +++ b/src/treelearner/cuda/cuda_leaf_splits.cu @@ -81,6 +81,90 @@ __global__ void CUDAInitValuesKernel2( } } +template +__global__ void CUDAInitValuesKernel3(const int16_t* cuda_gradients_and_hessians, + const data_size_t num_data, const data_size_t* cuda_bagging_data_indices, + double* cuda_sum_of_gradients, double* cuda_sum_of_hessians, int64_t* cuda_sum_of_hessians_hessians, + const score_t* grad_scale_pointer, const score_t* hess_scale_pointer) { + const score_t grad_scale = *grad_scale_pointer; + const score_t hess_scale = *hess_scale_pointer; + __shared__ int64_t shared_mem_buffer[32]; + const data_size_t data_index = static_cast(threadIdx.x + blockIdx.x * blockDim.x); + int64_t int_gradient = 0; + int64_t int_hessian = 0; + if (data_index < num_data) { + int_gradient = USE_INDICES ? cuda_gradients_and_hessians[2 * cuda_bagging_data_indices[data_index] + 1] : + cuda_gradients_and_hessians[2 * data_index + 1]; + int_hessian = USE_INDICES ? cuda_gradients_and_hessians[2 * cuda_bagging_data_indices[data_index]] : + cuda_gradients_and_hessians[2 * data_index]; + } + const int64_t block_sum_gradient = ShuffleReduceSum(int_gradient, shared_mem_buffer, blockDim.x); + __syncthreads(); + const int64_t block_sum_hessian = ShuffleReduceSum(int_hessian, shared_mem_buffer, blockDim.x); + if (threadIdx.x == 0) { + cuda_sum_of_gradients[blockIdx.x] = block_sum_gradient * grad_scale; + cuda_sum_of_hessians[blockIdx.x] = block_sum_hessian * hess_scale; + cuda_sum_of_hessians_hessians[blockIdx.x] = ((block_sum_gradient << 32) | block_sum_hessian); + } +} + +__global__ void CUDAInitValuesKernel4( + const double lambda_l1, + const double lambda_l2, + const int num_blocks_to_reduce, + double* cuda_sum_of_gradients, + double* cuda_sum_of_hessians, + int64_t* cuda_sum_of_gradients_hessians, + const data_size_t num_data, + const data_size_t* cuda_data_indices_in_leaf, + hist_t* cuda_hist_in_leaf, + CUDALeafSplitsStruct* cuda_struct) { + __shared__ double shared_mem_buffer[32]; + double thread_sum_of_gradients = 0.0f; + double thread_sum_of_hessians = 0.0f; + int64_t thread_sum_of_gradients_hessians = 0; + for (int block_index = static_cast(threadIdx.x); block_index < num_blocks_to_reduce; block_index += static_cast(blockDim.x)) { + thread_sum_of_gradients += cuda_sum_of_gradients[block_index]; + thread_sum_of_hessians += cuda_sum_of_hessians[block_index]; + thread_sum_of_gradients_hessians += cuda_sum_of_gradients_hessians[block_index]; + } + const double sum_of_gradients = ShuffleReduceSum(thread_sum_of_gradients, shared_mem_buffer, blockDim.x); + __syncthreads(); + const double sum_of_hessians = ShuffleReduceSum(thread_sum_of_hessians, shared_mem_buffer, blockDim.x); + __syncthreads(); + const double sum_of_gradients_hessians = ShuffleReduceSum( + thread_sum_of_gradients_hessians, + reinterpret_cast(shared_mem_buffer), + blockDim.x); + if (threadIdx.x == 0) { + cuda_sum_of_hessians[0] = sum_of_hessians; + cuda_struct->leaf_index = 0; + cuda_struct->sum_of_gradients = sum_of_gradients; + cuda_struct->sum_of_hessians = sum_of_hessians; + cuda_struct->sum_of_gradients_hessians = sum_of_gradients_hessians; + cuda_struct->num_data_in_leaf = num_data; + const bool use_l1 = lambda_l1 > 0.0f; + if (!use_l1) { + // no smoothing on root node + cuda_struct->gain = CUDALeafSplits::GetLeafGain(sum_of_gradients, sum_of_hessians, lambda_l1, lambda_l2, 0.0f, 0, 0.0f); + } else { + // no smoothing on root node + cuda_struct->gain = CUDALeafSplits::GetLeafGain(sum_of_gradients, sum_of_hessians, lambda_l1, lambda_l2, 0.0f, 0, 0.0f); + } + if (!use_l1) { + // no smoothing on root node + cuda_struct->leaf_value = + CUDALeafSplits::CalculateSplittedLeafOutput(sum_of_gradients, sum_of_hessians, lambda_l1, lambda_l2, 0.0f, 0, 0.0f); + } else { + // no smoothing on root node + cuda_struct->leaf_value = + CUDALeafSplits::CalculateSplittedLeafOutput(sum_of_gradients, sum_of_hessians, lambda_l1, lambda_l2, 0.0f, 0, 0.0f); + } + cuda_struct->data_indices_in_leaf = cuda_data_indices_in_leaf; + cuda_struct->hist_in_leaf = cuda_hist_in_leaf; + } +} + __global__ void InitValuesEmptyKernel(CUDALeafSplitsStruct* cuda_struct) { cuda_struct->leaf_index = -1; cuda_struct->sum_of_gradients = 0.0f; @@ -93,7 +177,7 @@ __global__ void InitValuesEmptyKernel(CUDALeafSplitsStruct* cuda_struct) { } void CUDALeafSplits::LaunchInitValuesEmptyKernel() { - InitValuesEmptyKernel<<<1, 1>>>(cuda_struct_); + InitValuesEmptyKernel<<<1, 1>>>(cuda_struct_.RawData()); } void CUDALeafSplits::LaunchInitValuesKernal( @@ -104,23 +188,55 @@ void CUDALeafSplits::LaunchInitValuesKernal( hist_t* cuda_hist_in_leaf) { if (cuda_bagging_data_indices == nullptr) { CUDAInitValuesKernel1<<>>( - cuda_gradients_, cuda_hessians_, num_used_indices, nullptr, cuda_sum_of_gradients_buffer_, - cuda_sum_of_hessians_buffer_); + cuda_gradients_, cuda_hessians_, num_used_indices, nullptr, cuda_sum_of_gradients_buffer_.RawData(), + cuda_sum_of_hessians_buffer_.RawData()); } else { CUDAInitValuesKernel1<<>>( - cuda_gradients_, cuda_hessians_, num_used_indices, cuda_bagging_data_indices, cuda_sum_of_gradients_buffer_, - cuda_sum_of_hessians_buffer_); + cuda_gradients_, cuda_hessians_, num_used_indices, cuda_bagging_data_indices, cuda_sum_of_gradients_buffer_.RawData(), + cuda_sum_of_hessians_buffer_.RawData()); } SynchronizeCUDADevice(__FILE__, __LINE__); CUDAInitValuesKernel2<<<1, NUM_THRADS_PER_BLOCK_LEAF_SPLITS>>>( lambda_l1, lambda_l2, num_blocks_init_from_gradients_, - cuda_sum_of_gradients_buffer_, - cuda_sum_of_hessians_buffer_, + cuda_sum_of_gradients_buffer_.RawData(), + cuda_sum_of_hessians_buffer_.RawData(), + num_used_indices, + cuda_data_indices_in_leaf, + cuda_hist_in_leaf, + cuda_struct_.RawData()); + SynchronizeCUDADevice(__FILE__, __LINE__); +} + +void CUDALeafSplits::LaunchInitValuesKernal( + const double lambda_l1, const double lambda_l2, + const data_size_t* cuda_bagging_data_indices, + const data_size_t* cuda_data_indices_in_leaf, + const data_size_t num_used_indices, + hist_t* cuda_hist_in_leaf, + const score_t* grad_scale, + const score_t* hess_scale) { + if (cuda_bagging_data_indices == nullptr) { + CUDAInitValuesKernel3<<>>( + reinterpret_cast(cuda_gradients_), num_used_indices, nullptr, cuda_sum_of_gradients_buffer_.RawData(), + cuda_sum_of_hessians_buffer_.RawData(), cuda_sum_of_gradients_hessians_buffer_.RawData(), grad_scale, hess_scale); + } else { + CUDAInitValuesKernel3<<>>( + reinterpret_cast(cuda_gradients_), num_used_indices, cuda_bagging_data_indices, cuda_sum_of_gradients_buffer_.RawData(), + cuda_sum_of_hessians_buffer_.RawData(), cuda_sum_of_gradients_hessians_buffer_.RawData(), grad_scale, hess_scale); + } + + SynchronizeCUDADevice(__FILE__, __LINE__); + CUDAInitValuesKernel4<<<1, NUM_THRADS_PER_BLOCK_LEAF_SPLITS>>>( + lambda_l1, lambda_l2, + num_blocks_init_from_gradients_, + cuda_sum_of_gradients_buffer_.RawData(), + cuda_sum_of_hessians_buffer_.RawData(), + cuda_sum_of_gradients_hessians_buffer_.RawData(), num_used_indices, cuda_data_indices_in_leaf, cuda_hist_in_leaf, - cuda_struct_); + cuda_struct_.RawData()); SynchronizeCUDADevice(__FILE__, __LINE__); } diff --git a/src/treelearner/cuda/cuda_leaf_splits.hpp b/src/treelearner/cuda/cuda_leaf_splits.hpp index 769f956b95c3..33a9ea578a1f 100644 --- a/src/treelearner/cuda/cuda_leaf_splits.hpp +++ b/src/treelearner/cuda/cuda_leaf_splits.hpp @@ -8,7 +8,7 @@ #ifdef USE_CUDA -#include +#include #include #include #include @@ -23,6 +23,7 @@ struct CUDALeafSplitsStruct { int leaf_index; double sum_of_gradients; double sum_of_hessians; + int64_t sum_of_gradients_hessians; data_size_t num_data_in_leaf; double gain; double leaf_value; @@ -36,7 +37,7 @@ class CUDALeafSplits { ~CUDALeafSplits(); - void Init(); + void Init(const bool use_quantized_grad); void InitValues( const double lambda_l1, const double lambda_l2, @@ -45,11 +46,19 @@ class CUDALeafSplits { const data_size_t* cuda_data_indices_in_leaf, const data_size_t num_used_indices, hist_t* cuda_hist_in_leaf, double* root_sum_hessians); + void InitValues( + const double lambda_l1, const double lambda_l2, + const int16_t* cuda_gradients_and_hessians, + const data_size_t* cuda_bagging_data_indices, + const data_size_t* cuda_data_indices_in_leaf, const data_size_t num_used_indices, + hist_t* cuda_hist_in_leaf, double* root_sum_hessians, + const score_t* grad_scale, const score_t* hess_scale); + void InitValues(); - const CUDALeafSplitsStruct* GetCUDAStruct() const { return cuda_struct_; } + const CUDALeafSplitsStruct* GetCUDAStruct() const { return cuda_struct_.RawDataReadOnly(); } - CUDALeafSplitsStruct* GetCUDAStructRef() { return cuda_struct_; } + CUDALeafSplitsStruct* GetCUDAStructRef() { return cuda_struct_.RawData(); } void Resize(const data_size_t num_data); @@ -140,14 +149,24 @@ class CUDALeafSplits { const data_size_t num_used_indices, hist_t* cuda_hist_in_leaf); + void LaunchInitValuesKernal( + const double lambda_l1, const double lambda_l2, + const data_size_t* cuda_bagging_data_indices, + const data_size_t* cuda_data_indices_in_leaf, + const data_size_t num_used_indices, + hist_t* cuda_hist_in_leaf, + const score_t* grad_scale, + const score_t* hess_scale); + // Host memory data_size_t num_data_; int num_blocks_init_from_gradients_; // CUDA memory, held by this object - CUDALeafSplitsStruct* cuda_struct_; - double* cuda_sum_of_gradients_buffer_; - double* cuda_sum_of_hessians_buffer_; + CUDAVector cuda_struct_; + CUDAVector cuda_sum_of_gradients_buffer_; + CUDAVector cuda_sum_of_hessians_buffer_; + CUDAVector cuda_sum_of_gradients_hessians_buffer_; // CUDA memory, held by other object const score_t* cuda_gradients_; diff --git a/src/treelearner/cuda/cuda_single_gpu_tree_learner.cpp b/src/treelearner/cuda/cuda_single_gpu_tree_learner.cpp index 1600f3767c0c..8f8ff15f0715 100644 --- a/src/treelearner/cuda/cuda_single_gpu_tree_learner.cpp +++ b/src/treelearner/cuda/cuda_single_gpu_tree_learner.cpp @@ -9,7 +9,7 @@ #include "cuda_single_gpu_tree_learner.hpp" #include -#include +#include #include #include #include @@ -39,13 +39,14 @@ void CUDASingleGPUTreeLearner::Init(const Dataset* train_data, bool is_constant_ SetCUDADevice(gpu_device_id_, __FILE__, __LINE__); cuda_smaller_leaf_splits_.reset(new CUDALeafSplits(num_data_)); - cuda_smaller_leaf_splits_->Init(); + cuda_smaller_leaf_splits_->Init(config_->use_quantized_grad); cuda_larger_leaf_splits_.reset(new CUDALeafSplits(num_data_)); - cuda_larger_leaf_splits_->Init(); + cuda_larger_leaf_splits_->Init(config_->use_quantized_grad); cuda_histogram_constructor_.reset(new CUDAHistogramConstructor(train_data_, config_->num_leaves, num_threads_, share_state_->feature_hist_offsets(), - config_->min_data_in_leaf, config_->min_sum_hessian_in_leaf, gpu_device_id_, config_->gpu_use_dp)); + config_->min_data_in_leaf, config_->min_sum_hessian_in_leaf, gpu_device_id_, config_->gpu_use_dp, + config_->use_quantized_grad, config_->num_grad_quant_bins)); cuda_histogram_constructor_->Init(train_data_, share_state_.get()); const auto& feature_hist_offsets = share_state_->feature_hist_offsets(); @@ -73,11 +74,19 @@ void CUDASingleGPUTreeLearner::Init(const Dataset* train_data, bool is_constant_ } AllocateBitset(); - cuda_leaf_gradient_stat_buffer_ = nullptr; - cuda_leaf_hessian_stat_buffer_ = nullptr; leaf_stat_buffer_size_ = 0; num_cat_threshold_ = 0; + if (config_->use_quantized_grad) { + cuda_leaf_gradient_stat_buffer_.Resize(config_->num_leaves); + cuda_leaf_hessian_stat_buffer_.Resize(config_->num_leaves); + cuda_gradient_discretizer_.reset(new CUDAGradientDiscretizer( + config_->num_grad_quant_bins, config_->num_iterations, config_->seed, is_constant_hessian, config_->stochastic_rounding)); + cuda_gradient_discretizer_->Init(num_data_, config_->num_leaves, train_data_->num_features(), train_data_); + } else { + cuda_gradient_discretizer_.reset(nullptr); + } + #ifdef DEBUG host_gradients_.resize(num_data_, 0.0f); host_hessians_.resize(num_data_, 0.0f); @@ -101,19 +110,37 @@ void CUDASingleGPUTreeLearner::BeforeTrain() { const data_size_t* leaf_splits_init_indices = cuda_data_partition_->use_bagging() ? cuda_data_partition_->cuda_data_indices() : nullptr; cuda_data_partition_->BeforeTrain(); - cuda_smaller_leaf_splits_->InitValues( - config_->lambda_l1, - config_->lambda_l2, - gradients_, - hessians_, - leaf_splits_init_indices, - cuda_data_partition_->cuda_data_indices(), - root_num_data, - cuda_histogram_constructor_->cuda_hist_pointer(), - &leaf_sum_hessians_[0]); + if (config_->use_quantized_grad) { + cuda_gradient_discretizer_->DiscretizeGradients(num_data_, gradients_, hessians_); + cuda_histogram_constructor_->BeforeTrain( + reinterpret_cast(cuda_gradient_discretizer_->discretized_gradients_and_hessians()), nullptr); + cuda_smaller_leaf_splits_->InitValues( + config_->lambda_l1, + config_->lambda_l2, + reinterpret_cast(cuda_gradient_discretizer_->discretized_gradients_and_hessians()), + leaf_splits_init_indices, + cuda_data_partition_->cuda_data_indices(), + root_num_data, + cuda_histogram_constructor_->cuda_hist_pointer(), + &leaf_sum_hessians_[0], + cuda_gradient_discretizer_->grad_scale_ptr(), + cuda_gradient_discretizer_->hess_scale_ptr()); + cuda_gradient_discretizer_->SetNumBitsInHistogramBin(0, -1, root_num_data, 0); + } else { + cuda_histogram_constructor_->BeforeTrain(gradients_, hessians_); + cuda_smaller_leaf_splits_->InitValues( + config_->lambda_l1, + config_->lambda_l2, + gradients_, + hessians_, + leaf_splits_init_indices, + cuda_data_partition_->cuda_data_indices(), + root_num_data, + cuda_histogram_constructor_->cuda_hist_pointer(), + &leaf_sum_hessians_[0]); + } leaf_num_data_[0] = root_num_data; cuda_larger_leaf_splits_->InitValues(); - cuda_histogram_constructor_->BeforeTrain(gradients_, hessians_); col_sampler_.ResetByTree(); cuda_best_split_finder_->BeforeTrain(col_sampler_.is_feature_used_bytree()); leaf_data_start_[0] = 0; @@ -141,24 +168,70 @@ Tree* CUDASingleGPUTreeLearner::Train(const score_t* gradients, const data_size_t num_data_in_larger_leaf = larger_leaf_index_ < 0 ? 0 : leaf_num_data_[larger_leaf_index_]; const double sum_hessians_in_smaller_leaf = leaf_sum_hessians_[smaller_leaf_index_]; const double sum_hessians_in_larger_leaf = larger_leaf_index_ < 0 ? 0 : leaf_sum_hessians_[larger_leaf_index_]; + const uint8_t num_bits_in_histogram_bins = config_->use_quantized_grad ? cuda_gradient_discretizer_->GetHistBitsInLeaf(smaller_leaf_index_) : 0; cuda_histogram_constructor_->ConstructHistogramForLeaf( cuda_smaller_leaf_splits_->GetCUDAStruct(), cuda_larger_leaf_splits_->GetCUDAStruct(), num_data_in_smaller_leaf, num_data_in_larger_leaf, sum_hessians_in_smaller_leaf, - sum_hessians_in_larger_leaf); + sum_hessians_in_larger_leaf, + num_bits_in_histogram_bins); global_timer.Stop("CUDASingleGPUTreeLearner::ConstructHistogramForLeaf"); global_timer.Start("CUDASingleGPUTreeLearner::FindBestSplitsForLeaf"); - SelectFeatureByNode(tree.get()); - - cuda_best_split_finder_->FindBestSplitsForLeaf( + uint8_t parent_num_bits_bin = 0; + uint8_t smaller_num_bits_bin = 0; + uint8_t larger_num_bits_bin = 0; + if (config_->use_quantized_grad) { + if (larger_leaf_index_ != -1) { + const int parent_leaf_index = std::min(smaller_leaf_index_, larger_leaf_index_); + parent_num_bits_bin = cuda_gradient_discretizer_->GetHistBitsInNode(parent_leaf_index); + smaller_num_bits_bin = cuda_gradient_discretizer_->GetHistBitsInLeaf(smaller_leaf_index_); + larger_num_bits_bin = cuda_gradient_discretizer_->GetHistBitsInLeaf(larger_leaf_index_); + } else { + parent_num_bits_bin = cuda_gradient_discretizer_->GetHistBitsInLeaf(0); + smaller_num_bits_bin = cuda_gradient_discretizer_->GetHistBitsInLeaf(0); + larger_num_bits_bin = cuda_gradient_discretizer_->GetHistBitsInLeaf(0); + } + } else { + parent_num_bits_bin = 0; + smaller_num_bits_bin = 0; + larger_num_bits_bin = 0; + } + cuda_histogram_constructor_->SubtractHistogramForLeaf( cuda_smaller_leaf_splits_->GetCUDAStruct(), cuda_larger_leaf_splits_->GetCUDAStruct(), - smaller_leaf_index_, larger_leaf_index_, - num_data_in_smaller_leaf, num_data_in_larger_leaf, - sum_hessians_in_smaller_leaf, sum_hessians_in_larger_leaf); + config_->use_quantized_grad, + parent_num_bits_bin, + smaller_num_bits_bin, + larger_num_bits_bin); + + SelectFeatureByNode(tree.get()); + + if (config_->use_quantized_grad) { + const uint8_t smaller_leaf_num_bits_bin = cuda_gradient_discretizer_->GetHistBitsInLeaf(smaller_leaf_index_); + const uint8_t larger_leaf_num_bits_bin = larger_leaf_index_ < 0 ? 32 : cuda_gradient_discretizer_->GetHistBitsInLeaf(larger_leaf_index_); + cuda_best_split_finder_->FindBestSplitsForLeaf( + cuda_smaller_leaf_splits_->GetCUDAStruct(), + cuda_larger_leaf_splits_->GetCUDAStruct(), + smaller_leaf_index_, larger_leaf_index_, + num_data_in_smaller_leaf, num_data_in_larger_leaf, + sum_hessians_in_smaller_leaf, sum_hessians_in_larger_leaf, + cuda_gradient_discretizer_->grad_scale_ptr(), + cuda_gradient_discretizer_->hess_scale_ptr(), + smaller_leaf_num_bits_bin, + larger_leaf_num_bits_bin); + } else { + cuda_best_split_finder_->FindBestSplitsForLeaf( + cuda_smaller_leaf_splits_->GetCUDAStruct(), + cuda_larger_leaf_splits_->GetCUDAStruct(), + smaller_leaf_index_, larger_leaf_index_, + num_data_in_smaller_leaf, num_data_in_larger_leaf, + sum_hessians_in_smaller_leaf, sum_hessians_in_larger_leaf, + nullptr, nullptr, 0, 0); + } + global_timer.Stop("CUDASingleGPUTreeLearner::FindBestSplitsForLeaf"); global_timer.Start("CUDASingleGPUTreeLearner::FindBestFromAllSplits"); const CUDASplitInfo* best_split_info = nullptr; @@ -247,9 +320,19 @@ Tree* CUDASingleGPUTreeLearner::Train(const score_t* gradients, #endif // DEBUG smaller_leaf_index_ = (leaf_num_data_[best_leaf_index_] < leaf_num_data_[right_leaf_index] ? best_leaf_index_ : right_leaf_index); larger_leaf_index_ = (smaller_leaf_index_ == best_leaf_index_ ? right_leaf_index : best_leaf_index_); + + if (config_->use_quantized_grad) { + cuda_gradient_discretizer_->SetNumBitsInHistogramBin( + best_leaf_index_, right_leaf_index, leaf_num_data_[best_leaf_index_], leaf_num_data_[right_leaf_index]); + } global_timer.Stop("CUDASingleGPUTreeLearner::Split"); } SynchronizeCUDADevice(__FILE__, __LINE__); + if (config_->use_quantized_grad && config_->quant_train_renew_leaf) { + global_timer.Start("CUDASingleGPUTreeLearner::RenewDiscretizedTreeLeaves"); + RenewDiscretizedTreeLeaves(tree.get()); + global_timer.Stop("CUDASingleGPUTreeLearner::RenewDiscretizedTreeLeaves"); + } tree->ToHost(); return tree.release(); } @@ -322,7 +405,7 @@ void CUDASingleGPUTreeLearner::RenewTreeOutput(Tree* tree, const ObjectiveFuncti } std::vector n_nozeroworker_perleaf(cuda_tree->num_leaves(), 1); int num_machines = Network::num_machines(); - #pragma omp parallel for schedule(static) + #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) for (int i = 0; i < cuda_tree->num_leaves(); ++i) { const double output = static_cast(cuda_tree->LeafOutput(i)); data_size_t cnt_leaf_data = leaf_num_data_[i]; @@ -357,8 +440,8 @@ void CUDASingleGPUTreeLearner::RenewTreeOutput(Tree* tree, const ObjectiveFuncti Tree* CUDASingleGPUTreeLearner::FitByExistingTree(const Tree* old_tree, const score_t* gradients, const score_t* hessians) const { std::unique_ptr cuda_tree(new CUDATree(old_tree)); - SetCUDAMemory(cuda_leaf_gradient_stat_buffer_, 0, static_cast(old_tree->num_leaves()), __FILE__, __LINE__); - SetCUDAMemory(cuda_leaf_hessian_stat_buffer_, 0, static_cast(old_tree->num_leaves()), __FILE__, __LINE__); + cuda_leaf_gradient_stat_buffer_.SetValue(0); + cuda_leaf_hessian_stat_buffer_.SetValue(0); ReduceLeafStat(cuda_tree.get(), gradients, hessians, cuda_data_partition_->cuda_data_indices()); cuda_tree->SyncLeafOutputFromCUDAToHost(); return cuda_tree.release(); @@ -373,13 +456,9 @@ Tree* CUDASingleGPUTreeLearner::FitByExistingTree(const Tree* old_tree, const st const int num_block = (refit_num_data_ + CUDA_SINGLE_GPU_TREE_LEARNER_BLOCK_SIZE - 1) / CUDA_SINGLE_GPU_TREE_LEARNER_BLOCK_SIZE; buffer_size *= static_cast(num_block + 1); } - if (buffer_size != leaf_stat_buffer_size_) { - if (leaf_stat_buffer_size_ != 0) { - DeallocateCUDAMemory(&cuda_leaf_gradient_stat_buffer_, __FILE__, __LINE__); - DeallocateCUDAMemory(&cuda_leaf_hessian_stat_buffer_, __FILE__, __LINE__); - } - AllocateCUDAMemory(&cuda_leaf_gradient_stat_buffer_, static_cast(buffer_size), __FILE__, __LINE__); - AllocateCUDAMemory(&cuda_leaf_hessian_stat_buffer_, static_cast(buffer_size), __FILE__, __LINE__); + if (static_cast(buffer_size) > cuda_leaf_gradient_stat_buffer_.Size()) { + cuda_leaf_gradient_stat_buffer_.Resize(buffer_size); + cuda_leaf_hessian_stat_buffer_.Resize(buffer_size); } return FitByExistingTree(old_tree, gradients, hessians); } @@ -513,6 +592,15 @@ void CUDASingleGPUTreeLearner::CheckSplitValid( } #endif // DEBUG +void CUDASingleGPUTreeLearner::RenewDiscretizedTreeLeaves(CUDATree* cuda_tree) { + cuda_data_partition_->ReduceLeafGradStat( + gradients_, hessians_, cuda_tree, + cuda_leaf_gradient_stat_buffer_.RawData(), + cuda_leaf_hessian_stat_buffer_.RawData()); + LaunchCalcLeafValuesGivenGradStat(cuda_tree, cuda_data_partition_->cuda_data_indices()); + SynchronizeCUDADevice(__FILE__, __LINE__); +} + } // namespace LightGBM #endif // USE_CUDA diff --git a/src/treelearner/cuda/cuda_single_gpu_tree_learner.cu b/src/treelearner/cuda/cuda_single_gpu_tree_learner.cu index 8a558ddc43d1..670f1f36d643 100644 --- a/src/treelearner/cuda/cuda_single_gpu_tree_learner.cu +++ b/src/treelearner/cuda/cuda_single_gpu_tree_learner.cu @@ -129,18 +129,18 @@ void CUDASingleGPUTreeLearner::LaunchReduceLeafStatKernel( if (num_leaves <= 2048) { ReduceLeafStatKernel_SharedMemory<<>>( gradients, hessians, num_leaves, num_data, cuda_data_partition_->cuda_data_index_to_leaf_index(), - cuda_leaf_gradient_stat_buffer_, cuda_leaf_hessian_stat_buffer_); + cuda_leaf_gradient_stat_buffer_.RawData(), cuda_leaf_hessian_stat_buffer_.RawData()); } else { ReduceLeafStatKernel_GlobalMemory<<>>( gradients, hessians, num_leaves, num_data, cuda_data_partition_->cuda_data_index_to_leaf_index(), - cuda_leaf_gradient_stat_buffer_, cuda_leaf_hessian_stat_buffer_); + cuda_leaf_gradient_stat_buffer_.RawData(), cuda_leaf_hessian_stat_buffer_.RawData()); } const bool use_l1 = config_->lambda_l1 > 0.0f; const bool use_smoothing = config_->path_smooth > 0.0f; num_block = (num_leaves + CUDA_SINGLE_GPU_TREE_LEARNER_BLOCK_SIZE - 1) / CUDA_SINGLE_GPU_TREE_LEARNER_BLOCK_SIZE; #define CalcRefitLeafOutputKernel_ARGS \ - num_leaves, cuda_leaf_gradient_stat_buffer_, cuda_leaf_hessian_stat_buffer_, num_data_in_leaf, \ + num_leaves, cuda_leaf_gradient_stat_buffer_.RawData(), cuda_leaf_hessian_stat_buffer_.RawData(), num_data_in_leaf, \ leaf_parent, left_child, right_child, \ config_->lambda_l1, config_->lambda_l2, config_->path_smooth, \ shrinkage_rate, config_->refit_decay_rate, cuda_leaf_value @@ -162,6 +162,7 @@ void CUDASingleGPUTreeLearner::LaunchReduceLeafStatKernel( <<>>(CalcRefitLeafOutputKernel_ARGS); } } + #undef CalcRefitLeafOutputKernel_ARGS } template @@ -256,6 +257,37 @@ void CUDASingleGPUTreeLearner::LaunchConstructBitsetForCategoricalSplitKernel( CUDAConstructBitset(best_split_info, num_cat_threshold_, cuda_bitset_, cuda_bitset_len_); } +void CUDASingleGPUTreeLearner::LaunchCalcLeafValuesGivenGradStat( + CUDATree* cuda_tree, const data_size_t* num_data_in_leaf) { + #define CalcRefitLeafOutputKernel_ARGS \ + cuda_tree->num_leaves(), cuda_leaf_gradient_stat_buffer_.RawData(), cuda_leaf_hessian_stat_buffer_.RawData(), num_data_in_leaf, \ + cuda_tree->cuda_leaf_parent(), cuda_tree->cuda_left_child(), cuda_tree->cuda_right_child(), \ + config_->lambda_l1, config_->lambda_l2, config_->path_smooth, \ + 1.0f, config_->refit_decay_rate, cuda_tree->cuda_leaf_value_ref() + const bool use_l1 = config_->lambda_l1 > 0.0f; + const bool use_smoothing = config_->path_smooth > 0.0f; + const int num_block = (cuda_tree->num_leaves() + CUDA_SINGLE_GPU_TREE_LEARNER_BLOCK_SIZE - 1) / CUDA_SINGLE_GPU_TREE_LEARNER_BLOCK_SIZE; + if (!use_l1) { + if (!use_smoothing) { + CalcRefitLeafOutputKernel + <<>>(CalcRefitLeafOutputKernel_ARGS); + } else { + CalcRefitLeafOutputKernel + <<>>(CalcRefitLeafOutputKernel_ARGS); + } + } else { + if (!use_smoothing) { + CalcRefitLeafOutputKernel + <<>>(CalcRefitLeafOutputKernel_ARGS); + } else { + CalcRefitLeafOutputKernel + <<>>(CalcRefitLeafOutputKernel_ARGS); + } + } + + #undef CalcRefitLeafOutputKernel_ARGS +} + } // namespace LightGBM #endif // USE_CUDA diff --git a/src/treelearner/cuda/cuda_single_gpu_tree_learner.hpp b/src/treelearner/cuda/cuda_single_gpu_tree_learner.hpp index 576d01ffe5cc..a1ea79efa1a1 100644 --- a/src/treelearner/cuda/cuda_single_gpu_tree_learner.hpp +++ b/src/treelearner/cuda/cuda_single_gpu_tree_learner.hpp @@ -16,6 +16,7 @@ #include "cuda_data_partition.hpp" #include "cuda_best_split_finder.hpp" +#include "cuda_gradient_discretizer.hpp" #include "../serial_tree_learner.h" namespace LightGBM { @@ -74,6 +75,10 @@ class CUDASingleGPUTreeLearner: public SerialTreeLearner { const double sum_left_gradients, const double sum_right_gradients); #endif // DEBUG + void RenewDiscretizedTreeLeaves(CUDATree* cuda_tree); + + void LaunchCalcLeafValuesGivenGradStat(CUDATree* cuda_tree, const data_size_t* num_data_in_leaf); + // GPU device ID int gpu_device_id_; // number of threads on CPU @@ -90,6 +95,8 @@ class CUDASingleGPUTreeLearner: public SerialTreeLearner { std::unique_ptr cuda_histogram_constructor_; // for best split information finding, given the histograms std::unique_ptr cuda_best_split_finder_; + // gradient discretizer for quantized training + std::unique_ptr cuda_gradient_discretizer_; std::vector leaf_best_split_feature_; std::vector leaf_best_split_threshold_; @@ -108,8 +115,8 @@ class CUDASingleGPUTreeLearner: public SerialTreeLearner { std::vector categorical_bin_to_value_; std::vector categorical_bin_offsets_; - mutable double* cuda_leaf_gradient_stat_buffer_; - mutable double* cuda_leaf_hessian_stat_buffer_; + mutable CUDAVector cuda_leaf_gradient_stat_buffer_; + mutable CUDAVector cuda_leaf_hessian_stat_buffer_; mutable data_size_t leaf_stat_buffer_size_; mutable data_size_t refit_num_data_; uint32_t* cuda_bitset_; @@ -148,7 +155,7 @@ class CUDASingleGPUTreeLearner: public SerialTreeLearner { #pragma warning(disable : 4702) explicit CUDASingleGPUTreeLearner(const Config* tree_config, const bool /*boosting_on_cuda*/) : SerialTreeLearner(tree_config) { Log::Fatal("CUDA Tree Learner was not enabled in this build.\n" - "Please recompile with CMake option -DUSE_CUDAP=1"); + "Please recompile with CMake option -DUSE_CUDA=1"); } }; diff --git a/src/treelearner/data_parallel_tree_learner.cpp b/src/treelearner/data_parallel_tree_learner.cpp index 2509db5e722a..64c342e5b01d 100644 --- a/src/treelearner/data_parallel_tree_learner.cpp +++ b/src/treelearner/data_parallel_tree_learner.cpp @@ -228,7 +228,7 @@ void DataParallelTreeLearner::FindBestSplits(const Tree* tree) { if (local_data_on_smaller_leaf <= 0) { // clear histogram buffer before synchronizing // otherwise histogram contents from the previous iteration will be sent - #pragma omp parallel for schedule(static) + #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) for (int feature_index = 0; feature_index < this->num_features_; ++feature_index) { if (this->col_sampler_.is_feature_used_bytree()[feature_index] == false) continue; @@ -249,7 +249,7 @@ void DataParallelTreeLearner::FindBestSplits(const Tree* tree) { // construct local histograms global_timer.Start("DataParallelTreeLearner::ReduceHistogram"); global_timer.Start("DataParallelTreeLearner::ReduceHistogram::Copy"); - #pragma omp parallel for schedule(static) + #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) for (int feature_index = 0; feature_index < this->num_features_; ++feature_index) { if (this->col_sampler_.is_feature_used_bytree()[feature_index] == false) continue; @@ -318,7 +318,7 @@ void DataParallelTreeLearner::FindBestSplitsFromHistograms(const if (parent_num_bits > 16 && larger_leaf_num_bits <= 16) { CHECK_LE(smaller_leaf_num_bits, 16); OMP_INIT_EX(); - #pragma omp parallel for schedule(static) + #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) for (int feature_index = 0; feature_index < this->num_features_; ++feature_index) { OMP_LOOP_EX_BEGIN(); if (!is_feature_aggregated_[feature_index]) continue; @@ -330,7 +330,7 @@ void DataParallelTreeLearner::FindBestSplitsFromHistograms(const } OMP_INIT_EX(); - #pragma omp parallel for schedule(static) + #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) for (int feature_index = 0; feature_index < this->num_features_; ++feature_index) { OMP_LOOP_EX_BEGIN(); if (!is_feature_aggregated_[feature_index]) continue; diff --git a/src/treelearner/data_partition.hpp b/src/treelearner/data_partition.hpp index 7a6ac031e62d..25c39c3ebde2 100644 --- a/src/treelearner/data_partition.hpp +++ b/src/treelearner/data_partition.hpp @@ -52,7 +52,7 @@ class DataPartition { if (used_data_indices_ == nullptr) { // if using all data leaf_count_[0] = num_data_; -#pragma omp parallel for schedule(static, 512) if (num_data_ >= 1024) +#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static, 512) if (num_data_ >= 1024) for (data_size_t i = 0; i < num_data_; ++i) { indices_[i] = i; } diff --git a/src/treelearner/feature_histogram.hpp b/src/treelearner/feature_histogram.hpp index d917ed7917ec..bd5bda1e8879 100644 --- a/src/treelearner/feature_histogram.hpp +++ b/src/treelearner/feature_histogram.hpp @@ -1692,7 +1692,7 @@ class HistogramPool { auto& ref_feature_meta = *feature_meta; const int num_feature = train_data->num_features(); ref_feature_meta.resize(num_feature); -#pragma omp parallel for schedule(static, 512) if (num_feature >= 1024) +#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static, 512) if (num_feature >= 1024) for (int i = 0; i < num_feature; ++i) { if (USE_DATA) { ref_feature_meta[i].num_bin = train_data->FeatureNumBin(i); @@ -1749,7 +1749,7 @@ class HistogramPool { if (config->use_quantized_grad) { OMP_INIT_EX(); - #pragma omp parallel for schedule(static) + #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) for (int i = old_cache_size; i < cache_size; ++i) { OMP_LOOP_EX_BEGIN(); pool_[i].reset(new FeatureHistogram[train_data->num_features()]); @@ -1763,7 +1763,7 @@ class HistogramPool { OMP_THROW_EX(); } else { OMP_INIT_EX(); - #pragma omp parallel for schedule(static) + #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) for (int i = old_cache_size; i < cache_size; ++i) { OMP_LOOP_EX_BEGIN(); pool_[i].reset(new FeatureHistogram[train_data->num_features()]); @@ -1787,7 +1787,7 @@ class HistogramPool { old_config->extra_trees != config->extra_trees || old_config->max_delta_step != config->max_delta_step || old_config->path_smooth != config->path_smooth) { -#pragma omp parallel for schedule(static) +#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) for (int i = 0; i < cache_size_; ++i) { for (int j = 0; j < train_data->num_features(); ++j) { pool_[i][j].ResetFunc(); diff --git a/src/treelearner/gpu_tree_learner.cpp b/src/treelearner/gpu_tree_learner.cpp index 294be28b6f86..0817614483b2 100644 --- a/src/treelearner/gpu_tree_learner.cpp +++ b/src/treelearner/gpu_tree_learner.cpp @@ -191,7 +191,7 @@ void GPUTreeLearner::WaitAndGetHistograms(hist_t* histograms) { HistType* hist_outputs = reinterpret_cast(host_histogram_outputs_); // when the output is ready, the computation is done histograms_wait_obj_.wait(); - #pragma omp parallel for schedule(static) + #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) for (int i = 0; i < num_dense_feature_groups_; ++i) { if (!feature_masks_[i]) { continue; @@ -344,7 +344,7 @@ void GPUTreeLearner::AllocateGPUMemory() { // for data transfer time auto start_time = std::chrono::steady_clock::now(); // Now generate new data structure feature4, and copy data to the device - int nthreads = std::min(omp_get_max_threads(), static_cast(dense_feature_group_map_.size()) / dword_features_); + int nthreads = std::min(OMP_NUM_THREADS(), static_cast(dense_feature_group_map_.size()) / dword_features_); nthreads = std::max(nthreads, 1); std::vector host4_vecs(nthreads); std::vector host4_bufs(nthreads); @@ -359,7 +359,7 @@ void GPUTreeLearner::AllocateGPUMemory() { 0, num_data_ * sizeof(Feature4))); } // building Feature4 bundles; each thread handles dword_features_ features - #pragma omp parallel for schedule(static) + #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) for (int i = 0; i < static_cast(dense_feature_group_map_.size() / dword_features_); ++i) { int tid = omp_get_thread_num(); Feature4* host4 = host4_ptrs[tid]; @@ -451,7 +451,7 @@ void GPUTreeLearner::AllocateGPUMemory() { BinIterator* bin_iter = train_data_->FeatureGroupIterator(dense_dword_ind[i]); if (dynamic_cast*>(bin_iter) != 0) { DenseBinIterator iter = *static_cast*>(bin_iter); - #pragma omp parallel for schedule(static) + #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) for (int j = 0; j < num_data_; ++j) { host4[j].s[i >> 1] |= (uint8_t)((iter.RawGet(j) * device_bin_mults_[copied_feature4 * dword_features_ + i] + ((j+i) & (device_bin_mults_[copied_feature4 * dword_features_ + i] - 1))) @@ -464,14 +464,14 @@ void GPUTreeLearner::AllocateGPUMemory() { BinIterator* bin_iter = train_data_->FeatureGroupIterator(dense_dword_ind[i]); if (dynamic_cast*>(bin_iter) != 0) { DenseBinIterator iter = *static_cast*>(bin_iter); - #pragma omp parallel for schedule(static) + #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) for (int j = 0; j < num_data_; ++j) { host4[j].s[i] = (uint8_t)(iter.RawGet(j) * device_bin_mults_[copied_feature4 * dword_features_ + i] + ((j+i) & (device_bin_mults_[copied_feature4 * dword_features_ + i] - 1))); } } else if (dynamic_cast*>(bin_iter) != 0) { DenseBinIterator iter = *static_cast*>(bin_iter); - #pragma omp parallel for schedule(static) + #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) for (int j = 0; j < num_data_; ++j) { host4[j].s[i] = (uint8_t)(iter.RawGet(j) * device_bin_mults_[copied_feature4 * dword_features_ + i] + ((j+i) & (device_bin_mults_[copied_feature4 * dword_features_ + i] - 1))); @@ -485,7 +485,7 @@ void GPUTreeLearner::AllocateGPUMemory() { } // fill the leftover features if (dword_features_ == 8) { - #pragma omp parallel for schedule(static) + #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) for (int j = 0; j < num_data_; ++j) { for (int i = k; i < dword_features_; ++i) { // fill this empty feature with some "random" value @@ -493,7 +493,7 @@ void GPUTreeLearner::AllocateGPUMemory() { } } } else if (dword_features_ == 4) { - #pragma omp parallel for schedule(static) + #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) for (int j = 0; j < num_data_; ++j) { for (int i = k; i < dword_features_; ++i) { // fill this empty feature with some "random" value @@ -572,7 +572,7 @@ void GPUTreeLearner::BuildGPUKernels() { // currently we don't use constant memory int use_constants = 0; OMP_INIT_EX(); - #pragma omp parallel for schedule(guided) + #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(guided) for (int i = 0; i <= kMaxLogWorkgroupsPerFeature; ++i) { OMP_LOOP_EX_BEGIN(); boost::compute::program program; @@ -811,7 +811,7 @@ void GPUTreeLearner::BeforeTrain() { // transfer the indices to GPU indices_future_ = boost::compute::copy_async(indices, indices + cnt, device_data_indices_->begin(), queue_); if (!share_state_->is_constant_hessian) { - #pragma omp parallel for schedule(static) + #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) for (data_size_t i = 0; i < cnt; ++i) { ordered_hessians_[i] = hessians_[indices[i]]; } @@ -827,7 +827,7 @@ void GPUTreeLearner::BeforeTrain() { histogram_fulldata_kernels_[i].set_arg(6, const_hessian); } } - #pragma omp parallel for schedule(static) + #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) for (data_size_t i = 0; i < cnt; ++i) { ordered_gradients_[i] = gradients_[indices[i]]; } @@ -865,7 +865,7 @@ bool GPUTreeLearner::BeforeFindBestSplit(const Tree* tree, int left_leaf, int ri indices_future_ = boost::compute::copy_async(indices + begin, indices + end, device_data_indices_->begin(), queue_); if (!share_state_->is_constant_hessian) { - #pragma omp parallel for schedule(static) + #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) for (data_size_t i = begin; i < end; ++i) { ordered_hessians_[i - begin] = hessians_[indices[i]]; } @@ -873,7 +873,7 @@ bool GPUTreeLearner::BeforeFindBestSplit(const Tree* tree, int left_leaf, int ri hessians_future_ = queue_.enqueue_write_buffer_async(device_hessians_, 0, (end - begin) * sizeof(score_t), ptr_pinned_hessians_); } - #pragma omp parallel for schedule(static) + #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) for (data_size_t i = begin; i < end; ++i) { ordered_gradients_[i - begin] = gradients_[indices[i]]; } @@ -907,7 +907,7 @@ bool GPUTreeLearner::ConstructGPUHistogramsAsync( // generate and copy ordered_gradients if gradients is not null if (gradients != nullptr) { if (num_data != num_data_) { - #pragma omp parallel for schedule(static) + #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) for (data_size_t i = 0; i < num_data; ++i) { ordered_gradients[i] = gradients[data_indices[i]]; } @@ -919,7 +919,7 @@ bool GPUTreeLearner::ConstructGPUHistogramsAsync( // generate and copy ordered_hessians if Hessians is not null if (hessians != nullptr && !share_state_->is_constant_hessian) { if (num_data != num_data_) { - #pragma omp parallel for schedule(static) + #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) for (data_size_t i = 0; i < num_data; ++i) { ordered_hessians[i] = hessians[data_indices[i]]; } @@ -930,7 +930,7 @@ bool GPUTreeLearner::ConstructGPUHistogramsAsync( } // converted indices in is_feature_used to feature-group indices std::vector is_feature_group_used(num_feature_groups_, 0); - #pragma omp parallel for schedule(static, 1024) if (num_features_ >= 2048) + #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static, 1024) if (num_features_ >= 2048) for (int i = 0; i < num_features_; ++i) { if (is_feature_used[i]) { is_feature_group_used[train_data_->Feature2Group(i)] = 1; @@ -938,7 +938,7 @@ bool GPUTreeLearner::ConstructGPUHistogramsAsync( } // construct the feature masks for dense feature-groups int used_dense_feature_groups = 0; - #pragma omp parallel for schedule(static, 1024) reduction(+:used_dense_feature_groups) if (num_dense_feature_groups_ >= 2048) + #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static, 1024) reduction(+:used_dense_feature_groups) if (num_dense_feature_groups_ >= 2048) for (int i = 0; i < num_dense_feature_groups_; ++i) { if (is_feature_group_used[dense_feature_group_map_[i]]) { feature_masks_[i] = 1; @@ -973,7 +973,7 @@ bool GPUTreeLearner::ConstructGPUHistogramsAsync( void GPUTreeLearner::ConstructHistograms(const std::vector& is_feature_used, bool use_subtract) { std::vector is_sparse_feature_used(num_features_, 0); std::vector is_dense_feature_used(num_features_, 0); - #pragma omp parallel for schedule(static) + #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) for (int feature_index = 0; feature_index < num_features_; ++feature_index) { if (!col_sampler_.is_feature_used_bytree()[feature_index]) continue; if (!is_feature_used[feature_index]) continue; diff --git a/src/treelearner/gradient_discretizer.cpp b/src/treelearner/gradient_discretizer.cpp index 4c00f73ab12c..b7e2e814b74a 100644 --- a/src/treelearner/gradient_discretizer.cpp +++ b/src/treelearner/gradient_discretizer.cpp @@ -216,7 +216,7 @@ void GradientDiscretizer::RenewIntGradTreeOutput( data_size_t leaf_cnt = 0; const data_size_t* data_indices = data_partition->GetIndexOnLeaf(leaf_id, &leaf_cnt); double sum_gradient = 0.0f, sum_hessian = 0.0f; - #pragma omp parallel for schedule(static) reduction(+:sum_gradient, sum_hessian) + #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) reduction(+:sum_gradient, sum_hessian) for (data_size_t i = 0; i < leaf_cnt; ++i) { const data_size_t index = data_indices[i]; const score_t grad = gradients[index]; @@ -242,7 +242,7 @@ void GradientDiscretizer::RenewIntGradTreeOutput( data_size_t leaf_cnt = 0; const data_size_t* data_indices = data_partition->GetIndexOnLeaf(leaf_id, &leaf_cnt); double sum_gradient = 0.0f, sum_hessian = 0.0f; - #pragma omp parallel for schedule(static) reduction(+:sum_gradient, sum_hessian) + #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) reduction(+:sum_gradient, sum_hessian) for (data_size_t i = 0; i < leaf_cnt; ++i) { const data_size_t index = data_indices[i]; const score_t grad = gradients[index]; diff --git a/src/treelearner/leaf_splits.hpp b/src/treelearner/leaf_splits.hpp index fdf55693a0e9..2db71573e97e 100644 --- a/src/treelearner/leaf_splits.hpp +++ b/src/treelearner/leaf_splits.hpp @@ -95,7 +95,7 @@ class LeafSplits { data_indices_ = nullptr; double tmp_sum_gradients = 0.0f; double tmp_sum_hessians = 0.0f; -#pragma omp parallel for schedule(static, 512) reduction(+:tmp_sum_gradients, tmp_sum_hessians) if (num_data_in_leaf_ >= 1024 && !deterministic_) +#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static, 512) reduction(+:tmp_sum_gradients, tmp_sum_hessians) if (num_data_in_leaf_ >= 1024 && !deterministic_) for (data_size_t i = 0; i < num_data_in_leaf_; ++i) { tmp_sum_gradients += gradients[i]; tmp_sum_hessians += hessians[i]; @@ -120,7 +120,7 @@ class LeafSplits { double tmp_sum_hessians = 0.0f; const int16_t* packed_int_gradients_and_hessians = reinterpret_cast(int_gradients_and_hessians); int64_t tmp_sum_gradients_and_hessians = 0; -#pragma omp parallel for schedule(static, 512) reduction(+:tmp_sum_gradients, tmp_sum_hessians, tmp_sum_gradients_and_hessians) if (num_data_in_leaf_ >= 1024 && !deterministic_) +#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static, 512) reduction(+:tmp_sum_gradients, tmp_sum_hessians, tmp_sum_gradients_and_hessians) if (num_data_in_leaf_ >= 1024 && !deterministic_) for (data_size_t i = 0; i < num_data_in_leaf_; ++i) { tmp_sum_gradients += int_gradients_and_hessians[2 * i + 1] * grad_scale; tmp_sum_hessians += int_gradients_and_hessians[2 * i] * hess_scale; @@ -149,7 +149,7 @@ class LeafSplits { data_indices_ = data_partition->GetIndexOnLeaf(leaf, &num_data_in_leaf_); double tmp_sum_gradients = 0.0f; double tmp_sum_hessians = 0.0f; -#pragma omp parallel for schedule(static, 512) reduction(+:tmp_sum_gradients, tmp_sum_hessians) if (num_data_in_leaf_ >= 1024 && !deterministic_) +#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static, 512) reduction(+:tmp_sum_gradients, tmp_sum_hessians) if (num_data_in_leaf_ >= 1024 && !deterministic_) for (data_size_t i = 0; i < num_data_in_leaf_; ++i) { const data_size_t idx = data_indices_[i]; tmp_sum_gradients += gradients[idx]; @@ -177,7 +177,7 @@ class LeafSplits { double tmp_sum_hessians = 0.0f; const int16_t* packed_int_gradients_and_hessians = reinterpret_cast(int_gradients_and_hessians); int64_t tmp_sum_gradients_and_hessians = 0; -#pragma omp parallel for schedule(static, 512) reduction(+:tmp_sum_gradients, tmp_sum_hessians, tmp_sum_gradients_and_hessians) if (num_data_in_leaf_ >= 1024 && deterministic_) +#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static, 512) reduction(+:tmp_sum_gradients, tmp_sum_hessians, tmp_sum_gradients_and_hessians) if (num_data_in_leaf_ >= 1024 && deterministic_) for (data_size_t i = 0; i < num_data_in_leaf_; ++i) { const data_size_t idx = data_indices_[i]; tmp_sum_gradients += int_gradients_and_hessians[2 * idx + 1] * grad_scale; diff --git a/src/treelearner/linear_tree_learner.cpp b/src/treelearner/linear_tree_learner.cpp index 071a6405709c..c96bce64d644 100644 --- a/src/treelearner/linear_tree_learner.cpp +++ b/src/treelearner/linear_tree_learner.cpp @@ -19,7 +19,7 @@ void LinearTreeLearner::InitLinear(const Dataset* train_data, const int max_leav leaf_map_ = std::vector(train_data->num_data(), -1); contains_nan_ = std::vector(train_data->num_features(), 0); // identify features containing nans -#pragma omp parallel for schedule(static) +#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) for (int feat = 0; feat < train_data->num_features(); ++feat) { auto bin_mapper = train_data_->FeatureBinMapper(feat); if (bin_mapper->bin_type() == BinType::NumericalBin) { @@ -52,7 +52,7 @@ void LinearTreeLearner::InitLinear(const Dataset* train_data, const int max_leav } XTHX_by_thread_.clear(); XTg_by_thread_.clear(); - int max_threads = omp_get_max_threads(); + int max_threads = OMP_NUM_THREADS(); for (int i = 0; i < max_threads; ++i) { XTHX_by_thread_.push_back(XTHX_); XTg_by_thread_.push_back(XTg_); @@ -159,7 +159,7 @@ void LinearTreeLearner::GetLeafMap(Tree* tree) const { std::fill(leaf_map_.begin(), leaf_map_.end(), -1); // map data to leaf number const data_size_t* ind = data_partition_->indices(); -#pragma omp parallel for schedule(dynamic) +#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(dynamic) for (int i = 0; i < tree->num_leaves(); ++i) { data_size_t idx = data_partition_->leaf_begin(i); for (int j = 0; j < data_partition_->leaf_count(i); ++j) { @@ -224,7 +224,7 @@ void LinearTreeLearner::CalculateLinear(Tree* tree, bool is_refit, const score_t } } // clear the coefficient matrices -#pragma omp parallel for schedule(static) +#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) for (int i = 0; i < num_threads; ++i) { for (int leaf_num = 0; leaf_num < num_leaves; ++leaf_num) { size_t num_feat = leaf_features[leaf_num].size(); @@ -232,7 +232,7 @@ void LinearTreeLearner::CalculateLinear(Tree* tree, bool is_refit, const score_t std::fill(XTg_by_thread_[i][leaf_num].begin(), XTg_by_thread_[i][leaf_num].begin() + num_feat + 1, 0.0f); } } -#pragma omp parallel for schedule(static) +#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) for (int leaf_num = 0; leaf_num < num_leaves; ++leaf_num) { size_t num_feat = leaf_features[leaf_num].size(); std::fill(XTHX_[leaf_num].begin(), XTHX_[leaf_num].begin() + (num_feat + 1) * (num_feat + 2) / 2, 0.0f); @@ -245,7 +245,7 @@ void LinearTreeLearner::CalculateLinear(Tree* tree, bool is_refit, const score_t } } OMP_INIT_EX(); -#pragma omp parallel if (num_data_ > 1024) +#pragma omp parallel num_threads(OMP_NUM_THREADS()) if (num_data_ > 1024) { std::vector curr_row(max_num_features + 1); int tid = omp_get_thread_num(); @@ -296,7 +296,7 @@ void LinearTreeLearner::CalculateLinear(Tree* tree, bool is_refit, const score_t auto total_nonzero = std::vector(tree->num_leaves()); // aggregate results from different threads for (int tid = 0; tid < num_threads; ++tid) { -#pragma omp parallel for schedule(static) +#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) for (int leaf_num = 0; leaf_num < num_leaves; ++leaf_num) { size_t num_feat = leaf_features[leaf_num].size(); for (size_t j = 0; j < (num_feat + 1) * (num_feat + 2) / 2; ++j) { @@ -318,7 +318,7 @@ void LinearTreeLearner::CalculateLinear(Tree* tree, bool is_refit, const score_t double shrinkage = tree->shrinkage(); double decay_rate = config_->refit_decay_rate; // copy into eigen matrices and solve -#pragma omp parallel for schedule(static) +#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) for (int leaf_num = 0; leaf_num < num_leaves; ++leaf_num) { if (total_nonzero[leaf_num] < static_cast(leaf_features[leaf_num].size()) + 1) { if (is_refit) { diff --git a/src/treelearner/linear_tree_learner.h b/src/treelearner/linear_tree_learner.h index 7e7b1f6d2a8d..770b18d133e5 100644 --- a/src/treelearner/linear_tree_learner.h +++ b/src/treelearner/linear_tree_learner.h @@ -75,7 +75,7 @@ class LinearTreeLearner: public SerialTreeLearner { leaf_num_features[leaf_num] = static_cast(feat_ptr[leaf_num].size()); } OMP_INIT_EX(); -#pragma omp parallel for schedule(static) if (num_data_ > 1024) +#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) if (num_data_ > 1024) for (int i = 0; i < num_data_; ++i) { OMP_LOOP_EX_BEGIN(); int leaf_num = leaf_map_[i]; diff --git a/src/treelearner/serial_tree_learner.cpp b/src/treelearner/serial_tree_learner.cpp index 37d9a2a50713..d5c5cc59ef3a 100644 --- a/src/treelearner/serial_tree_learner.cpp +++ b/src/treelearner/serial_tree_learner.cpp @@ -242,7 +242,7 @@ Tree* SerialTreeLearner::FitByExistingTree(const Tree* old_tree, const score_t* auto tree = std::unique_ptr(new Tree(*old_tree)); CHECK_GE(data_partition_->num_leaves(), tree->num_leaves()); OMP_INIT_EX(); - #pragma omp parallel for schedule(static) + #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) for (int i = 0; i < tree->num_leaves(); ++i) { OMP_LOOP_EX_BEGIN(); data_size_t cnt_leaf_data = 0; @@ -379,7 +379,7 @@ void SerialTreeLearner::FindBestSplits(const Tree* tree) { void SerialTreeLearner::FindBestSplits(const Tree* tree, const std::set* force_features) { std::vector is_feature_used(num_features_, 0); - #pragma omp parallel for schedule(static, 256) if (num_features_ >= 512) + #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static, 256) if (num_features_ >= 512) for (int feature_index = 0; feature_index < num_features_; ++feature_index) { if (!col_sampler_.is_feature_used_bytree()[feature_index] && (force_features == nullptr || force_features->find(feature_index) == force_features->end())) continue; if (parent_leaf_histogram_array_ != nullptr @@ -922,7 +922,7 @@ void SerialTreeLearner::RenewTreeOutput(Tree* tree, const ObjectiveFunction* obj } std::vector n_nozeroworker_perleaf(tree->num_leaves(), 1); int num_machines = Network::num_machines(); - #pragma omp parallel for schedule(static) + #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) for (int i = 0; i < tree->num_leaves(); ++i) { const double output = static_cast(tree->LeafOutput(i)); data_size_t cnt_leaf_data = 0; diff --git a/src/treelearner/serial_tree_learner.h b/src/treelearner/serial_tree_learner.h index 93e0787a90cf..43ff6a4b1e13 100644 --- a/src/treelearner/serial_tree_learner.h +++ b/src/treelearner/serial_tree_learner.h @@ -103,7 +103,7 @@ class SerialTreeLearner: public TreeLearner { if (tree->num_leaves() <= 1) { return; } -#pragma omp parallel for schedule(static, 1) +#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static, 1) for (int i = 0; i < tree->num_leaves(); ++i) { double output = static_cast(tree->LeafOutput(i)); data_size_t cnt_leaf_data = 0; diff --git a/src/treelearner/voting_parallel_tree_learner.cpp b/src/treelearner/voting_parallel_tree_learner.cpp index 0eee032839cd..b88db5a7ba28 100644 --- a/src/treelearner/voting_parallel_tree_learner.cpp +++ b/src/treelearner/voting_parallel_tree_learner.cpp @@ -243,7 +243,7 @@ template void VotingParallelTreeLearner::FindBestSplits(const Tree* tree) { // use local data to find local best splits std::vector is_feature_used(this->num_features_, 0); -#pragma omp parallel for schedule(static) +#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) for (int feature_index = 0; feature_index < this->num_features_; ++feature_index) { if (!this->col_sampler_.is_feature_used_bytree()[feature_index]) continue; if (this->parent_leaf_histogram_array_ != nullptr @@ -265,7 +265,7 @@ void VotingParallelTreeLearner::FindBestSplits(const Tree* tree) // clear histogram buffer before synchronizing // otherwise histogram contents from the previous iteration will be sent OMP_INIT_EX(); - #pragma omp parallel for schedule(static) + #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) for (int feature_index = 0; feature_index < this->num_features_; ++feature_index) { OMP_LOOP_EX_BEGIN(); if (!is_feature_used[feature_index]) { continue; } @@ -285,7 +285,7 @@ void VotingParallelTreeLearner::FindBestSplits(const Tree* tree) const data_size_t local_data_on_larger_leaf = this->data_partition_->leaf_count(larger_leaf_index); if (local_data_on_larger_leaf <= 0) { OMP_INIT_EX(); - #pragma omp parallel for schedule(static) + #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) for (int feature_index = 0; feature_index < this->num_features_; ++feature_index) { OMP_LOOP_EX_BEGIN(); if (!is_feature_used[feature_index]) { continue; } @@ -307,7 +307,7 @@ void VotingParallelTreeLearner::FindBestSplits(const Tree* tree) double larger_leaf_parent_output = this->GetParentOutput(tree, this->larger_leaf_splits_.get()); OMP_INIT_EX(); // find splits -#pragma omp parallel for schedule(static) +#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) for (int feature_index = 0; feature_index < this->num_features_; ++feature_index) { OMP_LOOP_EX_BEGIN(); if (!is_feature_used[feature_index]) { continue; } diff --git a/src/utils/openmp_wrapper.cpp b/src/utils/openmp_wrapper.cpp new file mode 100644 index 000000000000..fb6e661eb67c --- /dev/null +++ b/src/utils/openmp_wrapper.cpp @@ -0,0 +1,44 @@ +/*! + * Copyright (c) 2023 Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See LICENSE file in the project root for license information. + */ +#include + +int LGBM_MAX_NUM_THREADS = -1; + +int LGBM_DEFAULT_NUM_THREADS = -1; + +#ifdef _OPENMP + +#include + +int OMP_NUM_THREADS() { + int default_num_threads = 1; + + if (LGBM_DEFAULT_NUM_THREADS > 0) { + // if LightGBM-specific default has been set, ignore OpenMP-global config + default_num_threads = LGBM_DEFAULT_NUM_THREADS; + } else { + // otherwise, default to OpenMP-global config + #pragma omp single + { default_num_threads = omp_get_max_threads(); } + } + + // ensure that if LGBM_SetMaxThreads() was ever called, LightGBM doesn't + // use more than that many threads + if (LGBM_MAX_NUM_THREADS > 0 && default_num_threads > LGBM_MAX_NUM_THREADS) { + return LGBM_MAX_NUM_THREADS; + } + + return default_num_threads; +} + +void OMP_SET_NUM_THREADS(int num_threads) { + if (num_threads <= 0) { + LGBM_DEFAULT_NUM_THREADS = -1; + } else { + LGBM_DEFAULT_NUM_THREADS = num_threads; + } +} + +#endif // _OPENMP diff --git a/tests/c_api_test/test_.py b/tests/c_api_test/test_.py index 4bb76e4aba19..6cfec1c445fc 100644 --- a/tests/c_api_test/test_.py +++ b/tests/c_api_test/test_.py @@ -247,3 +247,36 @@ def test_booster(): c_str(''), c_str('preb.txt')) LIB.LGBM_BoosterFree(booster2) + + +def test_max_thread_control(): + # at initialization, should be -1 + num_threads = ctypes.c_int(0) + ret = LIB.LGBM_GetMaxThreads( + ctypes.byref(num_threads) + ) + assert ret == 0 + assert num_threads.value == -1 + + # updating that value through the C API should work + ret = LIB.LGBM_SetMaxThreads( + ctypes.c_int(6) + ) + assert ret == 0 + + ret = LIB.LGBM_GetMaxThreads( + ctypes.byref(num_threads) + ) + assert ret == 0 + assert num_threads.value == 6 + + # resetting to any negative number should set it to -1 + ret = LIB.LGBM_SetMaxThreads( + ctypes.c_int(-123) + ) + assert ret == 0 + ret = LIB.LGBM_GetMaxThreads( + ctypes.byref(num_threads) + ) + assert ret == 0 + assert num_threads.value == -1 diff --git a/tests/cpp_tests/test_arrow.cpp b/tests/cpp_tests/test_arrow.cpp new file mode 100644 index 000000000000..e975b6ba374b --- /dev/null +++ b/tests/cpp_tests/test_arrow.cpp @@ -0,0 +1,212 @@ +/*! + * Copyright (c) 2023 Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See LICENSE file in the project root for license information. + * + * Author: Oliver Borchert + */ + +#include +#include + +#include +#include + +using LightGBM::ArrowChunkedArray; +using LightGBM::ArrowTable; + +class ArrowChunkedArrayTest : public testing::Test { + protected: + void SetUp() override {} + + ArrowArray created_nested_array(const std::vector& arrays) { + ArrowArray arr; + arr.buffers = nullptr; + arr.children = (ArrowArray**)arrays.data(); // NOLINT + arr.dictionary = nullptr; + arr.length = arrays[0]->length; + arr.n_buffers = 0; + arr.n_children = arrays.size(); + arr.null_count = 0; + arr.offset = 0; + arr.private_data = nullptr; + arr.release = nullptr; + return arr; + } + + template + ArrowArray create_primitive_array(const std::vector& values, + int64_t offset = 0, + std::vector null_indices = {}) { + // NOTE: Arrow arrays have 64-bit alignment but we can safely ignore this in tests + // 1) Create validity bitmap + char* validity = nullptr; + if (!null_indices.empty()) { + auto num_bytes = (values.size() + 7) / 8; + validity = static_cast(calloc(num_bytes, sizeof(char))); + memset(validity, 0xff, num_bytes * sizeof(char)); + for (size_t i = 0; i < values.size(); ++i) { + if (std::find(null_indices.begin(), null_indices.end(), i) != null_indices.end()) { + validity[i / 8] &= ~(1 << (i % 8)); + } + } + } + + // 2) Create buffers + const void** buffers = (const void**)malloc(sizeof(void*) * 2); + buffers[0] = validity; + buffers[1] = values.data() + offset; + + // Create arrow array + ArrowArray arr; + arr.buffers = buffers; + arr.children = nullptr; + arr.dictionary = nullptr; + arr.length = values.size() - offset; + arr.null_count = 0; + arr.offset = 0; + arr.private_data = nullptr; + arr.release = [](ArrowArray* arr) { + if (arr->buffers[0] != nullptr) + free((void*)(arr->buffers[0])); // NOLINT + free((void*)(arr->buffers)); // NOLINT + }; + return arr; + } + + ArrowSchema create_nested_schema(const std::vector& arrays) { + ArrowSchema schema; + schema.format = "+s"; + schema.name = nullptr; + schema.metadata = nullptr; + schema.flags = 0; + schema.n_children = arrays.size(); + schema.children = (ArrowSchema**)arrays.data(); // NOLINT + schema.dictionary = nullptr; + schema.private_data = nullptr; + schema.release = nullptr; + return schema; + } + + template + ArrowSchema create_primitive_schema() { + std::logic_error("not implemented"); + } + + template <> + ArrowSchema create_primitive_schema() { + ArrowSchema schema; + schema.format = "f"; + schema.name = nullptr; + schema.metadata = nullptr; + schema.flags = 0; + schema.n_children = 0; + schema.children = nullptr; + schema.dictionary = nullptr; + schema.private_data = nullptr; + schema.release = nullptr; + return schema; + } +}; + +TEST_F(ArrowChunkedArrayTest, GetLength) { + std::vector dat1 = {1, 2}; + auto arr1 = create_primitive_array(dat1); + + ArrowChunkedArray ca1(1, &arr1, nullptr); + ASSERT_EQ(ca1.get_length(), 2); + + std::vector dat2 = {3, 4, 5, 6}; + auto arr2 = create_primitive_array(dat2); + ArrowArray arrs[2] = {arr1, arr2}; + ArrowChunkedArray ca2(2, arrs, nullptr); + ASSERT_EQ(ca2.get_length(), 6); + + arr1.release(&arr1); + arr2.release(&arr2); +} + +TEST_F(ArrowChunkedArrayTest, GetColumns) { + std::vector dat1 = {1, 2, 3}; + auto arr1 = create_primitive_array(dat1); + std::vector dat2 = {4, 5, 6}; + auto arr2 = create_primitive_array(dat2); + std::vector arrs = {&arr1, &arr2}; + auto arr = created_nested_array(arrs); + + auto schema1 = create_primitive_schema(); + auto schema2 = create_primitive_schema(); + std::vector schemas = {&schema1, &schema2}; + auto schema = create_nested_schema(schemas); + + ArrowTable table(1, &arr, &schema); + ASSERT_EQ(table.get_num_rows(), 3); + ASSERT_EQ(table.get_num_columns(), 2); + + auto ca1 = table.get_column(0); + ASSERT_EQ(ca1.get_length(), 3); + ASSERT_EQ(*ca1.begin(), 1); + + auto ca2 = table.get_column(1); + ASSERT_EQ(ca2.get_length(), 3); + ASSERT_EQ(*ca2.begin(), 4); + + arr1.release(&arr1); + arr2.release(&arr2); +} + +TEST_F(ArrowChunkedArrayTest, IteratorArithmetic) { + std::vector dat1 = {1, 2}; + auto arr1 = create_primitive_array(dat1); + std::vector dat2 = {3, 4, 5, 6}; + auto arr2 = create_primitive_array(dat2); + std::vector dat3 = {7}; + auto arr3 = create_primitive_array(dat3); + auto schema = create_primitive_schema(); + + ArrowArray arrs[3] = {arr1, arr2, arr3}; + ArrowChunkedArray ca(3, arrs, &schema); + + // Arithmetic + auto it = ca.begin(); + ASSERT_EQ(*it, 1); + ++it; + ASSERT_EQ(*it, 2); + ++it; + ASSERT_EQ(*it, 3); + it += 2; + ASSERT_EQ(*it, 5); + it += 2; + ASSERT_EQ(*it, 7); + --it; + ASSERT_EQ(*it, 6); + + // Subscripts + ASSERT_EQ(it[0], 1); + ASSERT_EQ(it[1], 2); + ASSERT_EQ(it[2], 3); + ASSERT_EQ(it[6], 7); + + // End + auto end = ca.end(); + ASSERT_EQ(end - it, 2); + ASSERT_EQ(end - ca.begin(), 7); + + arr1.release(&arr1); + arr2.release(&arr2); + arr2.release(&arr3); +} + +TEST_F(ArrowChunkedArrayTest, OffsetAndValidity) { + std::vector dat = {0, 1, 2, 3, 4, 5, 6}; + auto arr = create_primitive_array(dat, 2, {0, 1}); + auto schema = create_primitive_schema(); + ArrowChunkedArray ca(1, &arr, &schema); + + auto it = ca.begin(); + ASSERT_TRUE(std::isnan(*it)); + ASSERT_TRUE(std::isnan(*(++it))); + ASSERT_EQ(it[2], 4); + ASSERT_EQ(it[4], 6); + + arr.release(&arr); +} diff --git a/tests/cpp_tests/test_byte_buffer.cpp b/tests/cpp_tests/test_byte_buffer.cpp index 98df661ddd31..a6c2b660b983 100644 --- a/tests/cpp_tests/test_byte_buffer.cpp +++ b/tests/cpp_tests/test_byte_buffer.cpp @@ -30,7 +30,7 @@ TEST(ByteBuffer, JustWorks) { EXPECT_EQ(cumulativeSize, buffer->GetSize()); int16_t serializedInt16 = 0; char* int16Ptr = reinterpret_cast(&serializedInt16); - for (int i = 0; i < sizeof(int16_t); i++) { + for (unsigned int i = 0; i < sizeof(int16_t); i++) { int16Ptr[i] = buffer->GetAt(cumulativeSize - (sizeof(int16_t) - i)); } EXPECT_EQ(int16Val, serializedInt16); @@ -41,7 +41,7 @@ TEST(ByteBuffer, JustWorks) { EXPECT_EQ(cumulativeSize, buffer->GetSize()); int64_t serializedInt64 = 0; char* int64Ptr = reinterpret_cast(&serializedInt64); - for (int i = 0; i < sizeof(int64_t); i++) { + for (unsigned int i = 0; i < sizeof(int64_t); i++) { int64Ptr[i] = buffer->GetAt(cumulativeSize - (sizeof(int64_t) - i)); } EXPECT_EQ(int64Val, serializedInt64); @@ -52,7 +52,7 @@ TEST(ByteBuffer, JustWorks) { EXPECT_EQ(cumulativeSize, buffer->GetSize()); double serializedDouble = 0; char* doublePtr = reinterpret_cast(&serializedDouble); - for (int i = 0; i < sizeof(double); i++) { + for (unsigned int i = 0; i < sizeof(double); i++) { doublePtr[i] = buffer->GetAt(cumulativeSize - (sizeof(double) - i)); } EXPECT_EQ(doubleVal, serializedDouble); diff --git a/tests/cpp_tests/testutils.cpp b/tests/cpp_tests/testutils.cpp index f0b3e1c1f206..84acfe5b98a3 100644 --- a/tests/cpp_tests/testutils.cpp +++ b/tests/cpp_tests/testutils.cpp @@ -265,7 +265,7 @@ namespace LightGBM { groups_ptr, thread_count, t); - threads.push_back(move(th)); + threads.push_back(std::move(th)); } for (auto& t : threads) t.join(); diff --git a/tests/python_package_test/test_arrow.py b/tests/python_package_test/test_arrow.py new file mode 100644 index 000000000000..593c03d8c7ef --- /dev/null +++ b/tests/python_package_test/test_arrow.py @@ -0,0 +1,387 @@ +# coding: utf-8 +import filecmp +from typing import Any, Dict, Optional + +import numpy as np +import pyarrow as pa +import pytest + +import lightgbm as lgb + +from .utils import np_assert_array_equal + +# ----------------------------------------------------------------------------------------------- # +# UTILITIES # +# ----------------------------------------------------------------------------------------------- # + +_INTEGER_TYPES = [ + pa.int8(), + pa.int16(), + pa.int32(), + pa.int64(), + pa.uint8(), + pa.uint16(), + pa.uint32(), + pa.uint64(), +] +_FLOAT_TYPES = [ + pa.float32(), + pa.float64(), +] + + +def generate_simple_arrow_table(empty_chunks: bool = False) -> pa.Table: + c: list[list[int]] = [[]] if empty_chunks else [] + columns = [ + pa.chunked_array(c + [[1, 2, 3]] + c + [[4, 5]] + c, type=pa.uint8()), + pa.chunked_array(c + [[1, 2, 3]] + c + [[4, 5]] + c, type=pa.int8()), + pa.chunked_array(c + [[1, 2, 3]] + c + [[4, 5]] + c, type=pa.uint16()), + pa.chunked_array(c + [[1, 2, 3]] + c + [[4, 5]] + c, type=pa.int16()), + pa.chunked_array(c + [[1, 2, 3]] + c + [[4, 5]] + c, type=pa.uint32()), + pa.chunked_array(c + [[1, 2, 3]] + c + [[4, 5]] + c, type=pa.int32()), + pa.chunked_array(c + [[1, 2, 3]] + c + [[4, 5]] + c, type=pa.uint64()), + pa.chunked_array(c + [[1, 2, 3]] + c + [[4, 5]] + c, type=pa.int64()), + pa.chunked_array(c + [[1, 2, 3]] + c + [[4, 5]] + c, type=pa.float32()), + pa.chunked_array(c + [[1, 2, 3]] + c + [[4, 5]] + c, type=pa.float64()), + ] + return pa.Table.from_arrays(columns, names=[f"col_{i}" for i in range(len(columns))]) + + +def generate_nullable_arrow_table() -> pa.Table: + columns = [ + pa.chunked_array([[1, None, 3, 4, 5]], type=pa.float32()), + pa.chunked_array([[None, 2, 3, 4, 5]], type=pa.float32()), + pa.chunked_array([[1, 2, 3, 4, None]], type=pa.float32()), + pa.chunked_array([[None, None, None, None, None]], type=pa.float32()), + ] + return pa.Table.from_arrays(columns, names=[f"col_{i}" for i in range(len(columns))]) + + +def generate_dummy_arrow_table() -> pa.Table: + col1 = pa.chunked_array([[1, 2, 3], [4, 5]], type=pa.uint8()) + col2 = pa.chunked_array([[0.5, 0.6], [0.1, 0.8, 1.5]], type=pa.float32()) + return pa.Table.from_arrays([col1, col2], names=["a", "b"]) + + +def generate_random_arrow_table( + num_columns: int, + num_datapoints: int, + seed: int, + generate_nulls: bool = True, + values: Optional[np.ndarray] = None, +) -> pa.Table: + columns = [ + generate_random_arrow_array( + num_datapoints, seed + i, generate_nulls=generate_nulls, values=values + ) + for i in range(num_columns) + ] + names = [f"col_{i}" for i in range(num_columns)] + return pa.Table.from_arrays(columns, names=names) + + +def generate_random_arrow_array( + num_datapoints: int, + seed: int, + generate_nulls: bool = True, + values: Optional[np.ndarray] = None, +) -> pa.ChunkedArray: + generator = np.random.default_rng(seed) + data = ( + generator.standard_normal(num_datapoints) + if values is None + else generator.choice(values, size=num_datapoints, replace=True) + ) + + # Set random nulls + if generate_nulls: + indices = generator.choice(len(data), size=num_datapoints // 10) + data[indices] = None + + # Split data into <=2 random chunks + split_points = np.sort(generator.choice(np.arange(1, num_datapoints), 2, replace=False)) + split_points = np.concatenate([[0], split_points, [num_datapoints]]) + chunks = [data[split_points[i] : split_points[i + 1]] for i in range(len(split_points) - 1)] + chunks = [chunk for chunk in chunks if len(chunk) > 0] + + # Turn chunks into array + return pa.chunked_array([data], type=pa.float32()) + + +def dummy_dataset_params() -> Dict[str, Any]: + return { + "min_data_in_bin": 1, + "min_data_in_leaf": 1, + } + + +# ----------------------------------------------------------------------------------------------- # +# UNIT TESTS # +# ----------------------------------------------------------------------------------------------- # + +# ------------------------------------------- DATASET ------------------------------------------- # + + +@pytest.mark.parametrize( + ("arrow_table_fn", "dataset_params"), + [ # Use lambda functions here to minimize memory consumption + (lambda: generate_simple_arrow_table(), dummy_dataset_params()), + (lambda: generate_simple_arrow_table(empty_chunks=True), dummy_dataset_params()), + (lambda: generate_dummy_arrow_table(), dummy_dataset_params()), + (lambda: generate_nullable_arrow_table(), dummy_dataset_params()), + (lambda: generate_random_arrow_table(3, 1000, 42), {}), + (lambda: generate_random_arrow_table(100, 10000, 43), {}), + ], +) +def test_dataset_construct_fuzzy(tmp_path, arrow_table_fn, dataset_params): + arrow_table = arrow_table_fn() + + arrow_dataset = lgb.Dataset(arrow_table, params=dataset_params) + arrow_dataset.construct() + + pandas_dataset = lgb.Dataset(arrow_table.to_pandas(), params=dataset_params) + pandas_dataset.construct() + + arrow_dataset._dump_text(tmp_path / "arrow.txt") + pandas_dataset._dump_text(tmp_path / "pandas.txt") + assert filecmp.cmp(tmp_path / "arrow.txt", tmp_path / "pandas.txt") + + +# -------------------------------------------- FIELDS ------------------------------------------- # + + +def test_dataset_construct_fields_fuzzy(): + arrow_table = generate_random_arrow_table(3, 1000, 42) + arrow_labels = generate_random_arrow_array(1000, 42, generate_nulls=False) + arrow_weights = generate_random_arrow_array(1000, 42, generate_nulls=False) + arrow_groups = pa.chunked_array([[300, 400, 50], [250]], type=pa.int32()) + + arrow_dataset = lgb.Dataset( + arrow_table, label=arrow_labels, weight=arrow_weights, group=arrow_groups + ) + arrow_dataset.construct() + + pandas_dataset = lgb.Dataset( + arrow_table.to_pandas(), + label=arrow_labels.to_numpy(), + weight=arrow_weights.to_numpy(), + group=arrow_groups.to_numpy(), + ) + pandas_dataset.construct() + + # Check for equality + for field in ("label", "weight", "group"): + np_assert_array_equal( + arrow_dataset.get_field(field), pandas_dataset.get_field(field), strict=True + ) + np_assert_array_equal(arrow_dataset.get_label(), pandas_dataset.get_label(), strict=True) + np_assert_array_equal(arrow_dataset.get_weight(), pandas_dataset.get_weight(), strict=True) + + +# -------------------------------------------- LABELS ------------------------------------------- # + + +@pytest.mark.parametrize( + ["array_type", "label_data"], + [ + (pa.array, [0, 1, 0, 0, 1]), + (pa.chunked_array, [[0], [1, 0, 0, 1]]), + (pa.chunked_array, [[], [0], [1, 0, 0, 1]]), + (pa.chunked_array, [[0], [], [1, 0], [], [], [0, 1], []]), + ], +) +@pytest.mark.parametrize("arrow_type", _INTEGER_TYPES + _FLOAT_TYPES) +def test_dataset_construct_labels(array_type, label_data, arrow_type): + data = generate_dummy_arrow_table() + labels = array_type(label_data, type=arrow_type) + dataset = lgb.Dataset(data, label=labels, params=dummy_dataset_params()) + dataset.construct() + + expected = np.array([0, 1, 0, 0, 1], dtype=np.float32) + np_assert_array_equal(expected, dataset.get_label(), strict=True) + + +# ------------------------------------------- WEIGHTS ------------------------------------------- # + + +def test_dataset_construct_weights_none(): + data = generate_dummy_arrow_table() + weight = pa.array([1, 1, 1, 1, 1]) + dataset = lgb.Dataset(data, weight=weight, params=dummy_dataset_params()) + dataset.construct() + assert dataset.get_weight() is None + assert dataset.get_field("weight") is None + + +@pytest.mark.parametrize( + ["array_type", "weight_data"], + [ + (pa.array, [3, 0.7, 1.5, 0.5, 0.1]), + (pa.chunked_array, [[3], [0.7, 1.5, 0.5, 0.1]]), + (pa.chunked_array, [[], [3], [0.7, 1.5, 0.5, 0.1]]), + (pa.chunked_array, [[3], [0.7], [], [], [1.5, 0.5, 0.1], []]), + ], +) +@pytest.mark.parametrize("arrow_type", _FLOAT_TYPES) +def test_dataset_construct_weights(array_type, weight_data, arrow_type): + data = generate_dummy_arrow_table() + weights = array_type(weight_data, type=arrow_type) + dataset = lgb.Dataset(data, weight=weights, params=dummy_dataset_params()) + dataset.construct() + + expected = np.array([3, 0.7, 1.5, 0.5, 0.1], dtype=np.float32) + np_assert_array_equal(expected, dataset.get_weight(), strict=True) + + +# -------------------------------------------- GROUPS ------------------------------------------- # + + +@pytest.mark.parametrize( + ["array_type", "group_data"], + [ + (pa.array, [2, 3]), + (pa.chunked_array, [[2], [3]]), + (pa.chunked_array, [[], [2, 3]]), + (pa.chunked_array, [[2], [], [3], []]), + ], +) +@pytest.mark.parametrize("arrow_type", _INTEGER_TYPES) +def test_dataset_construct_groups(array_type, group_data, arrow_type): + data = generate_dummy_arrow_table() + groups = array_type(group_data, type=arrow_type) + dataset = lgb.Dataset(data, group=groups, params=dummy_dataset_params()) + dataset.construct() + + expected = np.array([0, 2, 5], dtype=np.int32) + np_assert_array_equal(expected, dataset.get_field("group"), strict=True) + + +# ----------------------------------------- INIT SCORES ----------------------------------------- # + + +@pytest.mark.parametrize( + ["array_type", "init_score_data"], + [ + (pa.array, [0, 1, 2, 3, 3]), + (pa.chunked_array, [[0, 1, 2], [3, 3]]), + (pa.chunked_array, [[], [0, 1, 2], [3, 3]]), + (pa.chunked_array, [[0, 1], [], [], [2], [3, 3], []]), + ], +) +@pytest.mark.parametrize("arrow_type", _INTEGER_TYPES + _FLOAT_TYPES) +def test_dataset_construct_init_scores_array( + array_type: Any, init_score_data: Any, arrow_type: Any +): + data = generate_dummy_arrow_table() + init_scores = array_type(init_score_data, type=arrow_type) + dataset = lgb.Dataset(data, init_score=init_scores, params=dummy_dataset_params()) + dataset.construct() + + expected = np.array([0, 1, 2, 3, 3], dtype=np.float64) + np_assert_array_equal(expected, dataset.get_init_score(), strict=True) + + +def test_dataset_construct_init_scores_table(): + data = generate_dummy_arrow_table() + init_scores = pa.Table.from_arrays( + [ + generate_random_arrow_array(5, seed=1, generate_nulls=False), + generate_random_arrow_array(5, seed=2, generate_nulls=False), + generate_random_arrow_array(5, seed=3, generate_nulls=False), + ], + names=["a", "b", "c"], + ) + dataset = lgb.Dataset(data, init_score=init_scores, params=dummy_dataset_params()) + dataset.construct() + + actual = dataset.get_init_score() + expected = init_scores.to_pandas().to_numpy().astype(np.float64) + np_assert_array_equal(expected, actual, strict=True) + + +# ------------------------------------------ PREDICTION ----------------------------------------- # + + +def assert_equal_predict_arrow_pandas(booster: lgb.Booster, data: pa.Table): + p_arrow = booster.predict(data) + p_pandas = booster.predict(data.to_pandas()) + np_assert_array_equal(p_arrow, p_pandas, strict=True) + + p_raw_arrow = booster.predict(data, raw_score=True) + p_raw_pandas = booster.predict(data.to_pandas(), raw_score=True) + np_assert_array_equal(p_raw_arrow, p_raw_pandas, strict=True) + + p_leaf_arrow = booster.predict(data, pred_leaf=True) + p_leaf_pandas = booster.predict(data.to_pandas(), pred_leaf=True) + np_assert_array_equal(p_leaf_arrow, p_leaf_pandas, strict=True) + + p_pred_contrib_arrow = booster.predict(data, pred_contrib=True) + p_pred_contrib_pandas = booster.predict(data.to_pandas(), pred_contrib=True) + np_assert_array_equal(p_pred_contrib_arrow, p_pred_contrib_pandas, strict=True) + + p_first_iter_arrow = booster.predict(data, start_iteration=0, num_iteration=1, raw_score=True) + p_first_iter_pandas = booster.predict( + data.to_pandas(), start_iteration=0, num_iteration=1, raw_score=True + ) + np_assert_array_equal(p_first_iter_arrow, p_first_iter_pandas, strict=True) + + +def test_predict_regression(): + data = generate_random_arrow_table(10, 10000, 42) + dataset = lgb.Dataset( + data, + label=generate_random_arrow_array(10000, 43, generate_nulls=False), + params=dummy_dataset_params(), + ) + booster = lgb.train( + {"objective": "regression", "num_leaves": 7}, + dataset, + num_boost_round=5, + ) + assert_equal_predict_arrow_pandas(booster, data) + + +def test_predict_binary_classification(): + data = generate_random_arrow_table(10, 10000, 42) + dataset = lgb.Dataset( + data, + label=generate_random_arrow_array(10000, 43, generate_nulls=False, values=np.arange(2)), + params=dummy_dataset_params(), + ) + booster = lgb.train( + {"objective": "binary", "num_leaves": 7}, + dataset, + num_boost_round=5, + ) + assert_equal_predict_arrow_pandas(booster, data) + + +def test_predict_multiclass_classification(): + data = generate_random_arrow_table(10, 10000, 42) + dataset = lgb.Dataset( + data, + label=generate_random_arrow_array(10000, 43, generate_nulls=False, values=np.arange(5)), + params=dummy_dataset_params(), + ) + booster = lgb.train( + {"objective": "multiclass", "num_leaves": 7, "num_class": 5}, + dataset, + num_boost_round=5, + ) + assert_equal_predict_arrow_pandas(booster, data) + + +def test_predict_ranking(): + data = generate_random_arrow_table(10, 10000, 42) + dataset = lgb.Dataset( + data, + label=generate_random_arrow_array(10000, 43, generate_nulls=False, values=np.arange(4)), + group=np.array([1000, 2000, 3000, 4000]), + params=dummy_dataset_params(), + ) + booster = lgb.train( + {"objective": "lambdarank", "num_leaves": 7}, + dataset, + num_boost_round=5, + ) + assert_equal_predict_arrow_pandas(booster, data) diff --git a/tests/python_package_test/test_basic.py b/tests/python_package_test/test_basic.py index 7f8980c271f7..b8ef43e41397 100644 --- a/tests/python_package_test/test_basic.py +++ b/tests/python_package_test/test_basic.py @@ -15,7 +15,7 @@ import lightgbm as lgb from lightgbm.compat import PANDAS_INSTALLED, pd_DataFrame, pd_Series -from .utils import dummy_obj, load_breast_cancer, mse_obj +from .utils import dummy_obj, load_breast_cancer, mse_obj, np_assert_array_equal def test_basic(tmp_path): @@ -499,6 +499,94 @@ def check_asserts(data): check_asserts(lgb_data) +def test_dataset_construction_overwrites_user_provided_metadata_fields(): + + X = np.array([[1.0, 2.0], [3.0, 4.0]]) + + position = np.array([0.0, 1.0], dtype=np.float32) + if getenv('TASK', '') == 'cuda': + position = None + + dtrain = lgb.Dataset( + X, + params={ + "min_data_in_bin": 1, + "min_data_in_leaf": 1, + "verbosity": -1 + }, + group=[1, 1], + init_score=[0.312, 0.708], + label=[1, 2], + position=position, + weight=[0.5, 1.5], + ) + + # unconstructed, get_* methods should return whatever was provided + assert dtrain.group == [1, 1] + assert dtrain.get_group() == [1, 1] + assert dtrain.init_score == [0.312, 0.708] + assert dtrain.get_init_score() == [0.312, 0.708] + assert dtrain.label == [1, 2] + assert dtrain.get_label() == [1, 2] + if getenv('TASK', '') != 'cuda': + np_assert_array_equal( + dtrain.position, + np.array([0.0, 1.0], dtype=np.float32), + strict=True + ) + np_assert_array_equal( + dtrain.get_position(), + np.array([0.0, 1.0], dtype=np.float32), + strict=True + ) + assert dtrain.weight == [0.5, 1.5] + assert dtrain.get_weight() == [0.5, 1.5] + + # before construction, get_field() should raise an exception + for field_name in ["group", "init_score", "label", "position", "weight"]: + with pytest.raises(Exception, match=f"Cannot get {field_name} before construct Dataset"): + dtrain.get_field(field_name) + + # constructed, get_* methods should return numpy arrays, even when the provided + # input was a list of floats or ints + dtrain.construct() + expected_group = np.array([1, 1], dtype=np.int32) + np_assert_array_equal(dtrain.group, expected_group, strict=True) + np_assert_array_equal(dtrain.get_group(), expected_group, strict=True) + # get_field("group") returns a numpy array with boundaries, instead of size + np_assert_array_equal( + dtrain.get_field("group"), + np.array([0, 1, 2], dtype=np.int32), + strict=True + ) + + expected_init_score = np.array([0.312, 0.708],) + np_assert_array_equal(dtrain.init_score, expected_init_score, strict=True) + np_assert_array_equal(dtrain.get_init_score(), expected_init_score, strict=True) + np_assert_array_equal(dtrain.get_field("init_score"), expected_init_score, strict=True) + + expected_label = np.array([1, 2], dtype=np.float32) + np_assert_array_equal(dtrain.label, expected_label, strict=True) + np_assert_array_equal(dtrain.get_label(), expected_label, strict=True) + np_assert_array_equal(dtrain.get_field("label"), expected_label, strict=True) + + if getenv('TASK', '') != 'cuda': + expected_position = np.array([0.0, 1.0], dtype=np.float32) + np_assert_array_equal(dtrain.position, expected_position, strict=True) + np_assert_array_equal(dtrain.get_position(), expected_position, strict=True) + # NOTE: "position" is converted to int32 on the C++ side + np_assert_array_equal( + dtrain.get_field("position"), + np.array([0.0, 1.0], dtype=np.int32), + strict=True + ) + + expected_weight = np.array([0.5, 1.5], dtype=np.float32) + np_assert_array_equal(dtrain.weight, expected_weight, strict=True) + np_assert_array_equal(dtrain.get_weight(), expected_weight, strict=True) + np_assert_array_equal(dtrain.get_field("weight"), expected_weight, strict=True) + + def test_choose_param_value(): original_params = { @@ -734,21 +822,34 @@ def test_no_copy_when_single_float_dtype_dataframe(dtype, feature_name): @pytest.mark.parametrize('feature_name', [['x1'], [42], 'auto']) -def test_categorical_code_conversion_doesnt_modify_original_data(feature_name): +@pytest.mark.parametrize('categories', ['seen', 'unseen']) +def test_categorical_code_conversion_doesnt_modify_original_data(feature_name, categories): pd = pytest.importorskip('pandas') X = np.random.choice(['a', 'b'], 100).reshape(-1, 1) column_name = 'a' if feature_name == 'auto' else feature_name[0] df = pd.DataFrame(X.copy(), columns=[column_name], dtype='category') + if categories == 'seen': + pandas_categorical = [['a', 'b']] + else: + pandas_categorical = [['a']] data = lgb.basic._data_from_pandas( data=df, feature_name=feature_name, categorical_feature="auto", - pandas_categorical=None + pandas_categorical=pandas_categorical, )[0] # check that the original data wasn't modified np.testing.assert_equal(df[column_name], X[:, 0]) # check that the built data has the codes - np.testing.assert_equal(df[column_name].cat.codes, data[:, 0]) + if categories == 'seen': + # if all categories were seen during training we just take the codes + codes = df[column_name].cat.codes + else: + # if we only saw 'a' during training we just replace its code + # and leave the rest as nan + a_code = df[column_name].cat.categories.get_loc('a') + codes = np.where(df[column_name] == 'a', a_code, np.nan) + np.testing.assert_equal(codes, data[:, 0]) @pytest.mark.parametrize('min_data_in_bin', [2, 10]) diff --git a/tests/python_package_test/test_callback.py b/tests/python_package_test/test_callback.py index cb5dc707bf43..f93ca837f8b9 100644 --- a/tests/python_package_test/test_callback.py +++ b/tests/python_package_test/test_callback.py @@ -21,6 +21,17 @@ def test_early_stopping_callback_is_picklable(serializer): assert callback.stopping_rounds == rounds +def test_early_stopping_callback_rejects_invalid_stopping_rounds_with_informative_errors(): + with pytest.raises(ValueError, match="stopping_rounds should be an integer and greater than 0. got: 0"): + lgb.early_stopping(stopping_rounds=0) + + with pytest.raises(ValueError, match="stopping_rounds should be an integer and greater than 0. got: -1"): + lgb.early_stopping(stopping_rounds=-1) + + with pytest.raises(ValueError, match="stopping_rounds should be an integer and greater than 0. got: neverrrr"): + lgb.early_stopping(stopping_rounds="neverrrr") + + @pytest.mark.parametrize('serializer', SERIALIZERS) def test_log_evaluation_callback_is_picklable(serializer): periods = 42 diff --git a/tests/python_package_test/test_engine.py b/tests/python_package_test/test_engine.py index b46526bcfaf6..e355e5ab074a 100644 --- a/tests/python_package_test/test_engine.py +++ b/tests/python_package_test/test_engine.py @@ -755,7 +755,7 @@ def test_ranking_prediction_early_stopping(): # (in our example it is simply the ordering by some feature correlated with relevance, e.g., 34) # and clicks on that document (new_label=1) with some probability 'pclick' depending on its true relevance; # at each position the user may stop the traversal with some probability pstop. For the non-clicked documents, -# new_label=0. Thus the generated new labels are biased towards the baseline ranker. +# new_label=0. Thus the generated new labels are biased towards the baseline ranker. # The positions of the documents in the ranked lists produced by the baseline, are returned. def simulate_position_bias(file_dataset_in, file_query_in, file_dataset_out, baseline_feature): # a mapping of a document's true relevance (defined on a 5-grade scale) into the probability of clicking it @@ -772,7 +772,7 @@ def get_pclick(label): return 0.9 # an instantiation of a cascade model where the user stops with probability 0.2 after observing each document pstop = 0.2 - + f_dataset_in = open(file_dataset_in, 'r') f_dataset_out = open(file_dataset_out, 'w') random.seed(10) @@ -780,19 +780,19 @@ def get_pclick(label): for line in open(file_query_in): docs_num = int (line) lines = [] - index_values = [] + index_values = [] positions = [0] * docs_num for index in range(docs_num): features = f_dataset_in.readline().split() lines.append(features) val = 0.0 for feature_val in features: - feature_val_split = feature_val.split(":") + feature_val_split = feature_val.split(":") if int(feature_val_split[0]) == baseline_feature: val = float(feature_val_split[1]) index_values.append([index, val]) index_values.sort(key=lambda x: -x[1]) - stop = False + stop = False for pos in range(docs_num): index = index_values[pos][0] new_label = 0 @@ -800,7 +800,7 @@ def get_pclick(label): label = int(lines[index][0]) pclick = get_pclick(label) if random.random() < pclick: - new_label = 1 + new_label = 1 stop = random.random() < pstop lines[index][0] = str(new_label) positions[index] = pos @@ -843,7 +843,7 @@ def test_ranking_with_position_information_with_file(tmp_path): lgb_train = lgb.Dataset(str(tmp_path / 'rank.train'), params=params) lgb_valid = [lgb_train.create_valid(str(tmp_path / 'rank.test'))] gbm_unbiased_with_file = lgb.train(params, lgb_train, valid_sets = lgb_valid, num_boost_round=50) - + # the performance of the unbiased LambdaMART should outperform the plain LambdaMART on the dataset with position bias assert gbm_baseline.best_score['valid_0']['ndcg@3'] + 0.03 <= gbm_unbiased_with_file.best_score['valid_0']['ndcg@3'] @@ -853,7 +853,7 @@ def test_ranking_with_position_information_with_file(tmp_path): file.close() lgb_train = lgb.Dataset(str(tmp_path / 'rank.train'), params=params) lgb_valid = [lgb_train.create_valid(str(tmp_path / 'rank.test'))] - with pytest.raises(lgb.basic.LightGBMError, match="Positions size \(3006\) doesn't match data size"): + with pytest.raises(lgb.basic.LightGBMError, match=r"Positions size \(3006\) doesn't match data size"): lgb.train(params, lgb_train, valid_sets = lgb_valid, num_boost_round=50) @@ -1470,7 +1470,7 @@ def test_feature_name_with_non_ascii(): assert feature_names == gbm2.feature_name() -def test_parameters_are_loaded_from_model_file(tmp_path): +def test_parameters_are_loaded_from_model_file(tmp_path, capsys): X = np.hstack([np.random.rand(100, 1), np.random.randint(0, 5, (100, 2))]) y = np.random.rand(100) ds = lgb.Dataset(X, y) @@ -1487,8 +1487,18 @@ def test_parameters_are_loaded_from_model_file(tmp_path): 'num_threads': 1, } model_file = tmp_path / 'model.txt' - lgb.train(params, ds, num_boost_round=1, categorical_feature=[1, 2]).save_model(model_file) + orig_bst = lgb.train(params, ds, num_boost_round=1, categorical_feature=[1, 2]) + orig_bst.save_model(model_file) + with model_file.open('rt') as f: + model_contents = f.readlines() + params_start = model_contents.index('parameters:\n') + model_contents.insert(params_start + 1, '[max_conflict_rate: 0]\n') + with model_file.open('wt') as f: + f.writelines(model_contents) bst = lgb.Booster(model_file=model_file) + expected_msg = "[LightGBM] [Warning] Ignoring unrecognized parameter 'max_conflict_rate' found in model string." + stdout = capsys.readouterr().out + assert expected_msg in stdout set_params = {k: bst.params[k] for k in params.keys()} assert set_params == params assert bst.params['categorical_feature'] == [1, 2] @@ -1498,6 +1508,11 @@ def test_parameters_are_loaded_from_model_file(tmp_path): bst2 = lgb.Booster(params={'num_leaves': 7}, model_file=model_file) assert bst.params == bst2.params + # check inference isn't affected by unknown parameter + orig_preds = orig_bst.predict(X) + preds = bst.predict(X) + np.testing.assert_allclose(preds, orig_preds) + def test_save_load_copy_pickle(): def train_and_predict(init_model=None, return_model=False): @@ -4501,9 +4516,9 @@ def test_train_raises_informative_error_if_any_valid_sets_are_not_dataset_object def test_train_raises_informative_error_for_params_of_wrong_type(): X, y = make_synthetic_regression() - params = {"early_stopping_round": "too-many"} + params = {"num_leaves": "too-many"} dtrain = lgb.Dataset(X, label=y) - with pytest.raises(lgb.basic.LightGBMError, match="Parameter early_stopping_round should be of type int, got \"too-many\""): + with pytest.raises(lgb.basic.LightGBMError, match="Parameter num_leaves should be of type int, got \"too-many\""): lgb.train(params, dtrain) diff --git a/tests/python_package_test/test_sklearn.py b/tests/python_package_test/test_sklearn.py index 9c6036ad510c..f54fe5236d79 100644 --- a/tests/python_package_test/test_sklearn.py +++ b/tests/python_package_test/test_sklearn.py @@ -640,11 +640,12 @@ def test_non_serializable_objects_in_callbacks(tmp_path): assert gbm.booster_.attr_set_inside_callback == 40 -def test_random_state_object(): +@pytest.mark.parametrize("rng_constructor", [np.random.RandomState, np.random.default_rng]) +def test_random_state_object(rng_constructor): X, y = load_iris(return_X_y=True) X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.1, random_state=42) - state1 = np.random.RandomState(123) - state2 = np.random.RandomState(123) + state1 = rng_constructor(123) + state2 = rng_constructor(123) clf1 = lgb.LGBMClassifier(n_estimators=10, subsample=0.5, subsample_freq=1, random_state=state1) clf2 = lgb.LGBMClassifier(n_estimators=10, subsample=0.5, subsample_freq=1, random_state=state2) # Test if random_state is properly stored diff --git a/tests/python_package_test/utils.py b/tests/python_package_test/utils.py index df01e29852e7..7eae62b14369 100644 --- a/tests/python_package_test/utils.py +++ b/tests/python_package_test/utils.py @@ -1,6 +1,7 @@ # coding: utf-8 import pickle from functools import lru_cache +from inspect import getfullargspec import cloudpickle import joblib @@ -193,3 +194,22 @@ def pickle_and_unpickle_object(obj, serializer): serializer=serializer ) return obj_from_disk # noqa: RET504 + + +# doing this here, at import time, to ensure it only runs once_per import +# instead of once per assertion +_numpy_testing_supports_strict_kwarg = ( + "strict" in getfullargspec(np.testing.assert_array_equal).kwonlyargs +) + + +def np_assert_array_equal(*args, **kwargs): + """ + np.testing.assert_array_equal() only got the kwarg ``strict`` in June 2022: + https://github.com/numpy/numpy/pull/21595 + + This function is here for testing on older Python (and therefore ``numpy``) + """ + if not _numpy_testing_supports_strict_kwarg: + kwargs.pop("strict") + np.testing.assert_array_equal(*args, **kwargs) diff --git a/windows/LightGBM.vcxproj b/windows/LightGBM.vcxproj index 96fe017e96b8..b7848b4cab5a 100644 --- a/windows/LightGBM.vcxproj +++ b/windows/LightGBM.vcxproj @@ -343,6 +343,7 @@ + diff --git a/windows/LightGBM.vcxproj.filters b/windows/LightGBM.vcxproj.filters index 27b445893c0f..7010926799b7 100644 --- a/windows/LightGBM.vcxproj.filters +++ b/windows/LightGBM.vcxproj.filters @@ -344,5 +344,8 @@ src\treelearner + + src\utils + \ No newline at end of file From 0db19418e1a1aed437722023aac2deb907436a51 Mon Sep 17 00:00:00 2001 From: Claudio Salvatore Arcidiacono <22871978+ClaudioSalvatoreArcidiacono@users.noreply.github.com> Date: Tue, 16 Jan 2024 10:44:26 +0100 Subject: [PATCH 04/24] Revert "Merge master" This reverts commit 457c7f6b35b954d8726f078000a2cb522990eb89. --- .appveyor.yml | 2 +- .ci/check_python_dists.sh | 2 +- .ci/lint-cpp.sh | 24 - .ci/lint_r_code.R | 17 +- .ci/setup.sh | 8 - .ci/test-python-oldest.sh | 4 +- .ci/test.sh | 19 +- .ci/test_r_package.sh | 6 +- .ci/test_r_package_valgrind.sh | 6 +- .ci/test_r_package_windows.ps1 | 23 +- .ci/test_windows.ps1 | 2 - .github/CODEOWNERS | 2 +- .github/workflows/cuda.yml | 12 +- .github/workflows/lock.yml | 9 +- .github/workflows/r_package.yml | 10 +- .github/workflows/static_analysis.yml | 2 +- .gitignore | 2 +- .vsts-ci.yml | 8 +- CMakeLists.txt | 91 +-- R-package/DESCRIPTION | 6 +- R-package/NAMESPACE | 4 +- R-package/R/callback.R | 18 +- R-package/R/lgb.Booster.R | 47 +- R-package/R/lgb.DataProcessor.R | 2 +- R-package/R/lgb.Dataset.R | 76 +- R-package/R/lgb.Predictor.R | 10 +- R-package/R/lgb.cv.R | 40 +- R-package/R/lgb.drop_serialized.R | 2 +- R-package/R/lgb.importance.R | 4 +- R-package/R/lgb.interprete.R | 10 +- R-package/R/lgb.make_serializable.R | 2 +- R-package/R/lgb.model.dt.tree.R | 9 +- R-package/R/lgb.plot.importance.R | 2 - R-package/R/lgb.plot.interpretation.R | 8 +- R-package/R/lgb.restore_handle.R | 6 +- R-package/R/lgb.train.R | 36 +- R-package/R/lightgbm.R | 10 +- R-package/R/multithreading.R | 51 -- R-package/R/readRDS.lgb.Booster.R | 48 ++ R-package/R/saveRDS.lgb.Booster.R | 78 ++ R-package/R/utils.R | 20 +- R-package/README.md | 9 +- R-package/configure | 18 +- R-package/cran-comments.md | 11 - R-package/demo/basic_walkthrough.R | 1 + R-package/demo/boost_from_prediction.R | 1 + R-package/demo/early_stopping.R | 1 + R-package/man/dim.Rd | 2 - R-package/man/dimnames.lgb.Dataset.Rd | 2 - R-package/man/getLGBMThreads.Rd | 26 - R-package/man/get_field.Rd | 2 - R-package/man/lgb.Dataset.Rd | 2 - R-package/man/lgb.Dataset.construct.Rd | 2 - R-package/man/lgb.Dataset.create.valid.Rd | 2 - R-package/man/lgb.Dataset.save.Rd | 2 - R-package/man/lgb.Dataset.set.categorical.Rd | 2 - R-package/man/lgb.Dataset.set.reference.Rd | 2 - R-package/man/lgb.configure_fast_predict.Rd | 2 - R-package/man/lgb.cv.Rd | 2 - R-package/man/lgb.dump.Rd | 2 - R-package/man/lgb.get.eval.result.Rd | 2 - R-package/man/lgb.importance.Rd | 2 - R-package/man/lgb.interprete.Rd | 2 - R-package/man/lgb.load.Rd | 2 - R-package/man/lgb.model.dt.tree.Rd | 2 - R-package/man/lgb.plot.importance.Rd | 2 - R-package/man/lgb.plot.interpretation.Rd | 2 - R-package/man/lgb.restore_handle.Rd | 4 - R-package/man/lgb.save.Rd | 2 - R-package/man/lgb.train.Rd | 2 - R-package/man/predict.lgb.Booster.Rd | 2 - R-package/man/readRDS.lgb.Booster.Rd | 51 ++ R-package/man/saveRDS.lgb.Booster.Rd | 73 ++ R-package/man/setLGBMThreads.Rd | 32 - R-package/man/set_field.Rd | 2 - R-package/man/slice.Rd | 2 - R-package/pkgdown/_pkgdown.yml | 2 + R-package/src/Makevars.in | 1 - R-package/src/Makevars.win.in | 1 - R-package/src/lightgbm_R.cpp | 82 +-- R-package/src/lightgbm_R.h | 19 - R-package/tests/testthat.R | 2 +- R-package/tests/testthat/helper.R | 5 - R-package/tests/testthat/test_Predictor.R | 10 +- R-package/tests/testthat/test_basic.R | 45 +- R-package/tests/testthat/test_dataset.R | 20 +- .../tests/testthat/test_learning_to_rank.R | 11 +- R-package/tests/testthat/test_lgb.Booster.R | 115 ++- .../tests/testthat/test_multithreading.R | 16 - R-package/tests/testthat/test_utils.R | 66 +- R-package/vignettes/basic_walkthrough.Rmd | 16 +- README.md | 3 +- VERSION.txt | 2 +- build-cran-package.sh | 3 +- docs/.linkcheckerrc | 2 +- docs/Experiments.rst | 26 +- docs/FAQ.rst | 49 +- docs/Features.rst | 2 +- docs/GPU-Performance.rst | 8 +- docs/GPU-Targets.rst | 2 +- docs/GPU-Tutorial.rst | 2 +- docs/GPU-Windows.rst | 4 +- docs/Installation-Guide.rst | 42 +- docs/Parallel-Learning-Guide.rst | 10 +- docs/Parameters.rst | 22 +- docs/Quick-Start.rst | 4 +- docs/env.yml | 2 +- docs/gcc-Tips.rst | 2 + include/LightGBM/arrow.h | 259 ------- include/LightGBM/arrow.tpp | 190 ----- include/LightGBM/bin.h | 6 +- include/LightGBM/c_api.h | 85 --- include/LightGBM/config.h | 16 +- include/LightGBM/cuda/cuda_algorithms.hpp | 2 +- include/LightGBM/cuda/cuda_column_data.hpp | 2 +- include/LightGBM/cuda/cuda_metadata.hpp | 2 +- include/LightGBM/cuda/cuda_metric.hpp | 2 +- .../LightGBM/cuda/cuda_objective_function.hpp | 2 +- include/LightGBM/cuda/cuda_row_data.hpp | 2 +- include/LightGBM/cuda/cuda_split_info.hpp | 2 - .../cuda/{cuda_utils.hu => cuda_utils.h} | 18 +- include/LightGBM/dataset.h | 50 +- include/LightGBM/feature_group.h | 2 +- include/LightGBM/sample_strategy.h | 2 +- include/LightGBM/tree.h | 6 +- include/LightGBM/utils/common.h | 4 +- include/LightGBM/utils/openmp_wrapper.h | 47 +- include/LightGBM/utils/threading.h | 2 +- python-package/lightgbm/basic.py | 454 +++--------- python-package/lightgbm/callback.py | 64 +- python-package/lightgbm/compat.py | 63 -- python-package/lightgbm/dask.py | 6 +- python-package/lightgbm/libpath.py | 3 +- python-package/lightgbm/sklearn.py | 81 +- python-package/pyproject.toml | 8 +- src/application/application.cpp | 2 +- src/application/predictor.hpp | 2 +- src/boosting/cuda/cuda_score_updater.hpp | 2 +- src/boosting/gbdt.cpp | 10 +- src/boosting/gbdt.h | 13 +- src/boosting/gbdt_model_text.cpp | 4 +- src/boosting/rf.hpp | 2 +- src/boosting/score_updater.hpp | 6 +- src/c_api.cpp | 204 +----- src/cuda/cuda_utils.cpp | 2 +- src/io/config.cpp | 4 + src/io/dataset.cpp | 33 +- src/io/dataset_loader.cpp | 14 +- src/io/metadata.cpp | 142 ++-- src/io/multi_val_dense_bin.hpp | 2 +- src/io/multi_val_sparse_bin.hpp | 4 +- src/io/train_share_states.cpp | 8 +- src/io/tree.cpp | 4 +- src/metric/binary_metric.hpp | 8 +- src/metric/cuda/cuda_binary_metric.hpp | 2 +- src/metric/cuda/cuda_pointwise_metric.hpp | 2 +- src/metric/cuda/cuda_regression_metric.hpp | 2 +- src/metric/map_metric.hpp | 4 +- src/metric/multiclass_metric.hpp | 8 +- src/metric/rank_metric.hpp | 6 +- src/metric/regression_metric.hpp | 8 +- src/metric/xentropy_metric.hpp | 24 +- src/objective/binary_objective.hpp | 10 +- src/objective/multiclass_objective.hpp | 4 +- src/objective/rank_objective.hpp | 15 +- src/objective/regression_objective.hpp | 46 +- src/objective/xentropy_objective.hpp | 16 +- src/treelearner/col_sampler.hpp | 6 +- .../cuda/cuda_best_split_finder.cpp | 19 +- .../cuda/cuda_best_split_finder.cu | 434 ----------- .../cuda/cuda_best_split_finder.hpp | 31 +- src/treelearner/cuda/cuda_data_partition.cpp | 6 - src/treelearner/cuda/cuda_data_partition.cu | 47 -- src/treelearner/cuda/cuda_data_partition.hpp | 8 - .../cuda/cuda_gradient_discretizer.cu | 171 ----- .../cuda/cuda_gradient_discretizer.hpp | 118 --- .../cuda/cuda_histogram_constructor.cpp | 110 +-- .../cuda/cuda_histogram_constructor.cu | 692 +++--------------- .../cuda/cuda_histogram_constructor.hpp | 61 +- src/treelearner/cuda/cuda_leaf_splits.cpp | 56 +- src/treelearner/cuda/cuda_leaf_splits.cu | 132 +--- src/treelearner/cuda/cuda_leaf_splits.hpp | 33 +- .../cuda/cuda_single_gpu_tree_learner.cpp | 156 +--- .../cuda/cuda_single_gpu_tree_learner.cu | 38 +- .../cuda/cuda_single_gpu_tree_learner.hpp | 13 +- .../data_parallel_tree_learner.cpp | 8 +- src/treelearner/data_partition.hpp | 2 +- src/treelearner/feature_histogram.hpp | 8 +- src/treelearner/gpu_tree_learner.cpp | 36 +- src/treelearner/gradient_discretizer.cpp | 4 +- src/treelearner/leaf_splits.hpp | 8 +- src/treelearner/linear_tree_learner.cpp | 16 +- src/treelearner/linear_tree_learner.h | 2 +- src/treelearner/serial_tree_learner.cpp | 6 +- src/treelearner/serial_tree_learner.h | 2 +- .../voting_parallel_tree_learner.cpp | 8 +- src/utils/openmp_wrapper.cpp | 44 -- tests/c_api_test/test_.py | 33 - tests/cpp_tests/test_arrow.cpp | 212 ------ tests/cpp_tests/test_byte_buffer.cpp | 6 +- tests/cpp_tests/testutils.cpp | 2 +- tests/python_package_test/test_arrow.py | 387 ---------- tests/python_package_test/test_basic.py | 109 +-- tests/python_package_test/test_callback.py | 11 - tests/python_package_test/test_engine.py | 39 +- tests/python_package_test/test_sklearn.py | 7 +- tests/python_package_test/utils.py | 20 - windows/LightGBM.vcxproj | 1 - windows/LightGBM.vcxproj.filters | 3 - 209 files changed, 1498 insertions(+), 5209 deletions(-) delete mode 100644 R-package/R/multithreading.R create mode 100644 R-package/R/readRDS.lgb.Booster.R create mode 100644 R-package/R/saveRDS.lgb.Booster.R delete mode 100644 R-package/man/getLGBMThreads.Rd create mode 100644 R-package/man/readRDS.lgb.Booster.Rd create mode 100644 R-package/man/saveRDS.lgb.Booster.Rd delete mode 100644 R-package/man/setLGBMThreads.Rd delete mode 100644 R-package/tests/testthat/test_multithreading.R delete mode 100644 include/LightGBM/arrow.h delete mode 100644 include/LightGBM/arrow.tpp rename include/LightGBM/cuda/{cuda_utils.hu => cuda_utils.h} (91%) delete mode 100644 src/treelearner/cuda/cuda_gradient_discretizer.cu delete mode 100644 src/treelearner/cuda/cuda_gradient_discretizer.hpp delete mode 100644 src/utils/openmp_wrapper.cpp delete mode 100644 tests/cpp_tests/test_arrow.cpp delete mode 100644 tests/python_package_test/test_arrow.py diff --git a/.appveyor.yml b/.appveyor.yml index 58a536d17a98..4cff03d571a1 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -1,4 +1,4 @@ -version: 4.2.0.99.{build} +version: 4.1.0.99.{build} image: Visual Studio 2015 platform: x64 diff --git a/.ci/check_python_dists.sh b/.ci/check_python_dists.sh index 1dd19679daae..cb0bbae79fa9 100644 --- a/.ci/check_python_dists.sh +++ b/.ci/check_python_dists.sh @@ -25,7 +25,7 @@ if [ $PY_MINOR_VER -gt 7 ]; then pydistcheck \ --inspect \ --ignore 'compiled-objects-have-debug-symbols,distro-too-large-compressed' \ - --max-allowed-size-uncompressed '100M' \ + --max-allowed-size-uncompressed '60M' \ --max-allowed-files 800 \ ${DIST_DIR}/* || exit -1 elif { test $(uname -m) = "aarch64"; }; then diff --git a/.ci/lint-cpp.sh b/.ci/lint-cpp.sh index 2d91f8e85f00..ef9fff683731 100755 --- a/.ci/lint-cpp.sh +++ b/.ci/lint-cpp.sh @@ -18,27 +18,3 @@ cmakelint \ ${cmake_files} \ || exit -1 echo "done running cmakelint" - -echo "checking that all OpenMP pragmas specify num_threads()" -get_omp_pragmas_without_num_threads() { - grep \ - -n \ - -R \ - --include='*.c' \ - --include='*.cc' \ - --include='*.cpp' \ - --include='*.h' \ - --include='*.hpp' \ - 'pragma omp parallel' \ - | grep -v ' num_threads' -} -PROBLEMATIC_LINES=$( - get_omp_pragmas_without_num_threads -) -if test "${PROBLEMATIC_LINES}" != ""; then - get_omp_pragmas_without_num_threads - echo "Found '#pragma omp parallel' not using explicit num_threads() configuration. Fix those." - echo "For details, see https://www.openmp.org/spec-html/5.0/openmpse14.html#x54-800002.6" - exit -1 -fi -echo "done checking OpenMP pragmas" diff --git a/.ci/lint_r_code.R b/.ci/lint_r_code.R index d5b1217a5b04..12116104ef6d 100755 --- a/.ci/lint_r_code.R +++ b/.ci/lint_r_code.R @@ -1,5 +1,5 @@ -loadNamespace("lintr") +library(lintr) args <- commandArgs( trailingOnly = TRUE @@ -33,34 +33,30 @@ LINTERS_TO_USE <- list( , "any_duplicated" = lintr::any_duplicated_linter() , "any_is_na" = lintr::any_is_na_linter() , "assignment" = lintr::assignment_linter() - , "backport" = lintr::backport_linter() , "boolean_arithmetic" = lintr::boolean_arithmetic_linter() , "braces" = lintr::brace_linter() , "class_equals" = lintr::class_equals_linter() , "commas" = lintr::commas_linter() - , "conjunct_test" = lintr::conjunct_test_linter() , "duplicate_argument" = lintr::duplicate_argument_linter() , "empty_assignment" = lintr::empty_assignment_linter() , "equals_na" = lintr::equals_na_linter() - , "fixed_regex" = lintr::fixed_regex_linter() , "for_loop_index" = lintr::for_loop_index_linter() , "function_left" = lintr::function_left_parentheses_linter() - , "function_return" = lintr::function_return_linter() - , "implicit_assignment" = lintr::implicit_assignment_linter() , "implicit_integers" = lintr::implicit_integer_linter() , "infix_spaces" = lintr::infix_spaces_linter() , "inner_combine" = lintr::inner_combine_linter() , "is_numeric" = lintr::is_numeric_linter() + , "fixed_regex" = lintr::fixed_regex_linter() + , "function_return" = lintr::function_return_linter() , "lengths" = lintr::lengths_linter() - , "line_length" = lintr::line_length_linter(length = 120L) , "literal_coercion" = lintr::literal_coercion_linter() + , "long_lines" = lintr::line_length_linter(length = 120L) , "matrix" = lintr::matrix_apply_linter() , "missing_argument" = lintr::missing_argument_linter() , "non_portable_path" = lintr::nonportable_path_linter() , "numeric_leading_zero" = lintr::numeric_leading_zero_linter() , "outer_negation" = lintr::outer_negation_linter() , "package_hooks" = lintr::package_hooks_linter() - , "paren_body" = lintr::paren_body_linter() , "paste" = lintr::paste_linter() , "quotes" = lintr::quotes_linter() , "redundant_equals" = lintr::redundant_equals_linter() @@ -78,7 +74,8 @@ LINTERS_TO_USE <- list( , "true_false" = lintr::T_and_F_symbol_linter() , "undesirable_function" = lintr::undesirable_function_linter( fun = c( - "cbind" = paste0( + "cat" = "CRAN forbids the use of cat() in packages except in special cases. Use message() or warning()." + , "cbind" = paste0( "cbind is an unsafe way to build up a data frame. merge() or direct " , "column assignment is preferred." ) @@ -103,7 +100,6 @@ LINTERS_TO_USE <- list( "%>%" = pipe_text , "%.%" = pipe_text , "%..%" = pipe_text - , "|>" = pipe_text , "?" = interactive_text , "??" = interactive_text ) @@ -111,7 +107,6 @@ LINTERS_TO_USE <- list( , "unnecessary_concatenation" = lintr::unnecessary_concatenation_linter() , "unnecessary_lambda" = lintr::unnecessary_lambda_linter() , "unreachable_code" = lintr::unreachable_code_linter() - , "unused_import" = lintr::unused_import_linter() , "vector_logic" = lintr::vector_logic_linter() , "whitespace" = lintr::whitespace_linter() ) diff --git a/.ci/setup.sh b/.ci/setup.sh index bc17fee03308..f7da21286d7d 100755 --- a/.ci/setup.sh +++ b/.ci/setup.sh @@ -54,14 +54,6 @@ else # Linux sudo apt-get install --no-install-recommends -y \ clang \ libomp-dev - elif [[ $COMPILER == "clang-17" ]]; then - sudo apt-get install wget - wget -qO- https://apt.llvm.org/llvm-snapshot.gpg.key | sudo tee /etc/apt/trusted.gpg.d/apt.llvm.org.asc - sudo apt-add-repository deb http://apt.llvm.org/jammy/ llvm-toolchain-jammy-17 main - sudo apt-add-repository deb-src http://apt.llvm.org/jammy/ llvm-toolchain-jammy-17 main - sudo apt-get update - sudo apt-get install -y clang-17 - sudo apt-get install --no-install-recommends -y libomp-17-dev fi export LANG="en_US.UTF-8" diff --git a/.ci/test-python-oldest.sh b/.ci/test-python-oldest.sh index 40dfd393f1fe..3a0ea08dddda 100644 --- a/.ci/test-python-oldest.sh +++ b/.ci/test-python-oldest.sh @@ -7,11 +7,9 @@ # echo "installing lightgbm's dependencies" pip install \ - 'cffi==1.15.1' \ 'dataclasses' \ - 'numpy==1.16.6' \ + 'numpy==1.12.0' \ 'pandas==0.24.0' \ - 'pyarrow==6.0.1' \ 'scikit-learn==0.18.2' \ 'scipy==0.19.0' \ || exit -1 diff --git a/.ci/test.sh b/.ci/test.sh index 472fd7d8c6e5..af7cae2e3858 100755 --- a/.ci/test.sh +++ b/.ci/test.sh @@ -6,9 +6,6 @@ if [[ $OS_NAME == "macos" ]] && [[ $COMPILER == "gcc" ]]; then elif [[ $OS_NAME == "linux" ]] && [[ $COMPILER == "clang" ]]; then export CXX=clang++ export CC=clang -elif [[ $OS_NAME == "linux" ]] && [[ $COMPILER == "clang-17" ]]; then - export CXX=clang++-17 - export CC=clang-17 fi if [[ $IN_UBUNTU_BASE_CONTAINER == "true" ]]; then @@ -40,7 +37,7 @@ fi CONDA_PYTHON_REQUIREMENT="python=$PYTHON_VERSION[build=*cpython]" if [[ $TASK == "if-else" ]]; then - mamba create -q -y -n $CONDA_ENV ${CONDA_PYTHON_REQUIREMENT} numpy + conda create -q -y -n $CONDA_ENV ${CONDA_PYTHON_REQUIREMENT} numpy source activate $CONDA_ENV mkdir $BUILD_DIRECTORY/build && cd $BUILD_DIRECTORY/build && cmake .. && make lightgbm -j4 || exit -1 cd $BUILD_DIRECTORY/tests/cpp_tests && ../../lightgbm config=train.conf convert_model_language=cpp convert_model=../../src/boosting/gbdt_prediction.cpp && ../../lightgbm config=predict.conf output_result=origin.pred || exit -1 @@ -70,7 +67,7 @@ fi if [[ $TASK == "lint" ]]; then cd ${BUILD_DIRECTORY} - mamba create -q -y -n $CONDA_ENV \ + conda create -q -y -n $CONDA_ENV \ ${CONDA_PYTHON_REQUIREMENT} \ cmakelint \ cpplint \ @@ -90,10 +87,10 @@ fi if [[ $TASK == "check-docs" ]] || [[ $TASK == "check-links" ]]; then cd $BUILD_DIRECTORY/docs - mamba env create \ + conda env create \ -n $CONDA_ENV \ --file ./env.yml || exit -1 - mamba install \ + conda install \ -q \ -y \ -n $CONDA_ENV \ @@ -131,15 +128,13 @@ if [[ $PYTHON_VERSION == "3.7" ]]; then fi # including python=version[build=*cpython] to ensure that conda doesn't fall back to pypy -mamba create -q -y -n $CONDA_ENV \ +conda create -q -y -n $CONDA_ENV \ ${CONSTRAINED_DEPENDENCIES} \ - cffi \ cloudpickle \ joblib \ matplotlib \ numpy \ psutil \ - pyarrow \ pytest \ ${CONDA_PYTHON_REQUIREMENT} \ python-graphviz \ @@ -309,7 +304,7 @@ matplotlib.use\(\"Agg\"\)\ ' plot_example.py # prevent interactive window mode sed -i'.bak' 's/graph.render(view=True)/graph.render(view=False)/' plot_example.py # requirements for examples - mamba install -q -y -n $CONDA_ENV \ + conda install -q -y -n $CONDA_ENV \ h5py \ ipywidgets \ notebook @@ -320,13 +315,11 @@ matplotlib.use\(\"Agg\"\)\ # importing the library should succeed even if all optional dependencies are not present conda uninstall --force --yes \ - cffi \ dask \ distributed \ joblib \ matplotlib \ psutil \ - pyarrow \ python-graphviz \ scikit-learn || exit -1 python -c "import lightgbm" || exit -1 diff --git a/.ci/test_r_package.sh b/.ci/test_r_package.sh index 6d421f16be56..e4d70261aa36 100755 --- a/.ci/test_r_package.sh +++ b/.ci/test_r_package.sh @@ -127,13 +127,13 @@ if [[ "${R_MAJOR_VERSION}" == "3" ]]; then Rscript --vanilla -e "install.packages('https://cran.r-project.org/src/contrib/Archive/lattice/lattice_0.20-41.tar.gz', repos = NULL, lib = '${R_LIB_PATH}')" fi -# Manually install Depends and Imports libraries + 'knitr', 'markdown', 'RhpcBLASctl', 'testthat' +# Manually install Depends and Imports libraries + 'knitr', 'RhpcBLASctl', 'rmarkdown', 'testthat' # to avoid a CI-time dependency on devtools (for devtools::install_deps()) # NOTE: testthat is not required when running rchk if [[ "${TASK}" == "r-rchk" ]]; then - packages="c('data.table', 'jsonlite', 'knitr', 'markdown', 'Matrix', 'R6', 'RhpcBLASctl')" + packages="c('data.table', 'jsonlite', 'knitr', 'Matrix', 'R6', 'RhpcBLASctl', 'rmarkdown')" else - packages="c('data.table', 'jsonlite', 'knitr', 'markdown', 'Matrix', 'R6', 'RhpcBLASctl', 'testthat')" + packages="c('data.table', 'jsonlite', 'knitr', 'Matrix', 'R6', 'RhpcBLASctl', 'rmarkdown', 'testthat')" fi compile_from_source="both" if [[ $OS_NAME == "macos" ]]; then diff --git a/.ci/test_r_package_valgrind.sh b/.ci/test_r_package_valgrind.sh index bd8f1f71f9f1..9eddec2deed1 100755 --- a/.ci/test_r_package_valgrind.sh +++ b/.ci/test_r_package_valgrind.sh @@ -1,11 +1,9 @@ #!/bin/bash -RDscriptvalgrind -e "install.packages(c('R6', 'data.table', 'jsonlite', 'Matrix', 'RhpcBLASctl', 'testthat'), repos = 'https://cran.rstudio.com')" || exit -1 +RDscriptvalgrind -e "install.packages(c('R6', 'data.table', 'jsonlite', 'knitr', 'Matrix', 'RhpcBLASctl', 'rmarkdown', 'testthat'), repos = 'https://cran.rstudio.com', Ncpus = parallel::detectCores())" || exit -1 sh build-cran-package.sh \ --r-executable=RDvalgrind \ - --no-build-vignettes \ || exit -1 - RDvalgrind CMD INSTALL --preclean --install-tests lightgbm_*.tar.gz || exit -1 cd R-package/tests @@ -70,7 +68,7 @@ bytes_possibly_lost=$( | tr -d "," ) echo "valgrind found ${bytes_possibly_lost} bytes possibly lost" -if [[ ${bytes_possibly_lost} -gt 1056 ]]; then +if [[ ${bytes_possibly_lost} -gt 352 ]]; then exit -1 fi diff --git a/.ci/test_r_package_windows.ps1 b/.ci/test_r_package_windows.ps1 index c4ae84f49dae..e4d20de50b90 100644 --- a/.ci/test_r_package_windows.ps1 +++ b/.ci/test_r_package_windows.ps1 @@ -55,14 +55,12 @@ Remove-From-Path ".*\\R\\.*" Remove-From-Path ".*R Client.*" Remove-From-Path ".*rtools40.*" Remove-From-Path ".*rtools42.*" -Remove-From-Path ".*rtools43.*" Remove-From-Path ".*shells.*" Remove-From-Path ".*Strawberry.*" Remove-From-Path ".*tools.*" Remove-Item C:\rtools40 -Force -Recurse -ErrorAction Ignore Remove-Item C:\rtools42 -Force -Recurse -ErrorAction Ignore -Remove-Item C:\rtools43 -Force -Recurse -ErrorAction Ignore # Get details needed for installing R components # @@ -78,11 +76,11 @@ if ($env:R_MAJOR_VERSION -eq "3") { $env:RTOOLS_EXE_FILE = "rtools35-x86_64.exe" $env:R_WINDOWS_VERSION = "3.6.3" } elseif ($env:R_MAJOR_VERSION -eq "4") { - $RTOOLS_INSTALL_PATH = "C:\rtools43" + $RTOOLS_INSTALL_PATH = "C:\rtools42" $env:RTOOLS_BIN = "$RTOOLS_INSTALL_PATH\usr\bin" $env:RTOOLS_MINGW_BIN = "$RTOOLS_INSTALL_PATH\x86_64-w64-mingw32.static.posix\bin" - $env:RTOOLS_EXE_FILE = "rtools43-5550-5548.exe" - $env:R_WINDOWS_VERSION = "4.3.1" + $env:RTOOLS_EXE_FILE = "rtools42-5253-5107.exe" + $env:R_WINDOWS_VERSION = "4.2.2" } else { Write-Output "[ERROR] Unrecognized R version: $env:R_VERSION" Check-Output $false @@ -124,7 +122,7 @@ Start-Process -FilePath Rtools.exe -NoNewWindow -Wait -ArgumentList "/VERYSILENT Write-Output "Done installing Rtools" Write-Output "Installing dependencies" -$packages = "c('data.table', 'jsonlite', 'knitr', 'markdown', 'Matrix', 'processx', 'R6', 'RhpcBLASctl', 'testthat'), dependencies = c('Imports', 'Depends', 'LinkingTo')" +$packages = "c('data.table', 'jsonlite', 'knitr', 'Matrix', 'processx', 'R6', 'RhpcBLASctl', 'rmarkdown', 'testthat'), dependencies = c('Imports', 'Depends', 'LinkingTo')" Run-R-Code-Redirect-Stderr "options(install.packages.check.source = 'no'); install.packages($packages, repos = '$env:CRAN_MIRROR', type = 'binary', lib = '$env:R_LIB_PATH', Ncpus = parallel::detectCores())" ; Check-Output $? Write-Output "Building R package" @@ -205,19 +203,6 @@ if ($env:COMPILER -ne "MSVC") { } } -# Checking that the correct R version was used -if ($env:TOOLCHAIN -ne "MSVC") { - $checks = Select-String -Path "${LOG_FILE_NAME}" -Pattern "using R version $env:R_WINDOWS_VERSION" - $checks_cnt = $checks.Matches.length -} else { - $checks = Select-String -Path "${INSTALL_LOG_FILE_NAME}" -Pattern "R version passed into FindLibR.* $env:R_WINDOWS_VERSION" - $checks_cnt = $checks.Matches.length -} -if ($checks_cnt -eq 0) { - Write-Output "Wrong R version was found (expected '$env:R_WINDOWS_VERSION'). Check the build logs." - Check-Output $False -} - # Checking that we actually got the expected compiler. The R package has some logic # to fail back to MinGW if MSVC fails, but for CI builds we need to check that the correct # compiler was used. diff --git a/.ci/test_windows.ps1 b/.ci/test_windows.ps1 index 6b02aed6ce8b..413af821e065 100644 --- a/.ci/test_windows.ps1 +++ b/.ci/test_windows.ps1 @@ -52,14 +52,12 @@ conda install brotlipy conda update -q -y conda conda create -q -y -n $env:CONDA_ENV ` - cffi ` cloudpickle ` joblib ` matplotlib ` numpy ` pandas ` psutil ` - pyarrow ` pytest ` "python=$env:PYTHON_VERSION[build=*cpython]" ` python-graphviz ` diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index b6885cad0503..02b5cfbdae23 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -7,4 +7,4 @@ # offer a reasonable automatic best-guess # catch-all rule (this only gets matched if no rules below match) -* @guolinke @jameslamb @shiyu1994 @jmoralez @borchero +* @guolinke @jameslamb @shiyu1994 @jmoralez diff --git a/.github/workflows/cuda.yml b/.github/workflows/cuda.yml index 3120fb1fae9f..9ab8440a6625 100644 --- a/.github/workflows/cuda.yml +++ b/.github/workflows/cuda.yml @@ -31,17 +31,17 @@ jobs: - method: wheel compiler: gcc python_version: "3.11" - cuda_version: "11.8.0" + cuda_version: "11.7.1" task: cuda - method: source compiler: gcc python_version: "3.9" - cuda_version: "12.2.0" + cuda_version: "10.0" task: cuda - method: pip compiler: clang python_version: "3.10" - cuda_version: "11.8.0" + cuda_version: "11.7.1" task: cuda steps: - name: Setup or update software on host machine @@ -98,10 +98,8 @@ jobs: cuda_version="${{ matrix.cuda_version }}" cuda_major=${cuda_version%%.*} docker_img="nvcr.io/nvidia/cuda:${cuda_version}-devel" - if [[ ${cuda_major} -eq 11 ]]; then - docker_img="${docker_img}-ubuntu18.04" - elif [[ ${cuda_major} -ge 12 ]]; then - docker_img="${docker_img}-ubuntu20.04" + if [[ ${cuda_major} -gt 10 ]]; then + docker_img="${docker_img}-ubuntu$(lsb_release -rs)" fi docker run --env-file docker.env -v "$GITHUB_WORKSPACE":"$ROOT_DOCKER_FOLDER" --rm --gpus all "$docker_img" /bin/bash $ROOT_DOCKER_FOLDER/docker-script.sh all-cuda-jobs-successful: diff --git a/.github/workflows/lock.yml b/.github/workflows/lock.yml index 4efe658b7f45..72d8b7c2f585 100644 --- a/.github/workflows/lock.yml +++ b/.github/workflows/lock.yml @@ -18,15 +18,15 @@ jobs: action: runs-on: ubuntu-latest steps: - - uses: dessant/lock-threads@v5 + - uses: dessant/lock-threads@v4 with: github-token: ${{ github.token }} # after how many days of inactivity should a closed issue/PR be locked? - issue-inactive-days: '365' - pr-inactive-days: '365' + issue-inactive-days: '90' + pr-inactive-days: '90' # do not close feature request issues... # we close those but track them in https://github.com/microsoft/LightGBM/issues/2302 - exclude-any-issue-labels: 'feature request' + exclude-any-issue-labels: '"feature request"' # what labels should be removed prior to locking? remove-issue-labels: 'awaiting response,awaiting review,blocking,in progress' remove-pr-labels: 'awaiting response,awaiting review,blocking,in progress' @@ -42,4 +42,3 @@ jobs: # what shoulld the locking status be? issue-lock-reason: 'resolved' pr-lock-reason: 'resolved' - process-only: 'issues, prs' diff --git a/.github/workflows/r_package.yml b/.github/workflows/r_package.yml index 4fa7f2ff8683..838528617143 100644 --- a/.github/workflows/r_package.yml +++ b/.github/workflows/r_package.yml @@ -86,7 +86,7 @@ jobs: task: r-package compiler: MINGW toolchain: MSYS - r_version: 4.3 + r_version: 4.2 build_type: cmake container: null # Visual Studio 2019 @@ -102,7 +102,7 @@ jobs: task: r-package compiler: MSVC toolchain: MSVC - r_version: 4.3 + r_version: 4.2 build_type: cmake container: null ############### @@ -119,7 +119,7 @@ jobs: task: r-package compiler: MINGW toolchain: MSYS - r_version: 4.3 + r_version: 4.2 build_type: cran container: null - os: ubuntu-latest @@ -246,7 +246,7 @@ jobs: - name: Install packages shell: bash run: | - RDscript${{ matrix.r_customization }} -e "install.packages(c('R6', 'data.table', 'jsonlite', 'knitr', 'markdown', 'Matrix', 'RhpcBLASctl', 'testthat'), repos = 'https://cran.rstudio.com', Ncpus = parallel::detectCores())" + RDscript${{ matrix.r_customization }} -e "install.packages(c('R6', 'data.table', 'jsonlite', 'knitr', 'Matrix', 'RhpcBLASctl', 'rmarkdown', 'testthat'), repos = 'https://cran.rstudio.com', Ncpus = parallel::detectCores())" sh build-cran-package.sh --r-executable=RD${{ matrix.r_customization }} RD${{ matrix.r_customization }} CMD INSTALL lightgbm_*.tar.gz || exit -1 - name: Run tests with sanitizers @@ -320,7 +320,7 @@ jobs: shell: bash run: | export PATH=/opt/R-devel/bin/:${PATH} - Rscript -e "install.packages(c('R6', 'data.table', 'jsonlite', 'knitr', 'markdown', 'Matrix', 'RhpcBLASctl', 'testthat'), repos = 'https://cran.rstudio.com', Ncpus = parallel::detectCores())" + Rscript -e "install.packages(c('R6', 'data.table', 'jsonlite', 'knitr', 'Matrix', 'RhpcBLASctl', 'rmarkdown', 'testthat'), repos = 'https://cran.rstudio.com', Ncpus = parallel::detectCores())" sh build-cran-package.sh R CMD check --as-cran --run-donttest lightgbm_*.tar.gz || exit -1 if grep -q -E "NOTE|WARNING|ERROR" lightgbm.Rcheck/00check.log; then diff --git a/.github/workflows/static_analysis.yml b/.github/workflows/static_analysis.yml index 33d50b751197..bf369e79c0c5 100644 --- a/.github/workflows/static_analysis.yml +++ b/.github/workflows/static_analysis.yml @@ -66,7 +66,7 @@ jobs: - name: Install packages shell: bash run: | - Rscript -e "install.packages(c('R6', 'data.table', 'jsonlite', 'knitr', 'markdown', 'Matrix', 'RhpcBLASctl', 'roxygen2', 'testthat'), repos = 'https://cran.rstudio.com', Ncpus = parallel::detectCores())" + Rscript -e "install.packages(c('R6', 'data.table', 'jsonlite', 'knitr', 'Matrix', 'RhpcBLASctl', 'rmarkdown', 'roxygen2', 'testthat'), repos = 'https://cran.rstudio.com', Ncpus = parallel::detectCores())" sh build-cran-package.sh || exit -1 R CMD INSTALL --with-keep.source lightgbm_*.tar.gz || exit -1 - name: Test documentation diff --git a/.gitignore b/.gitignore index 9403475cc190..bcf6f48b4cea 100644 --- a/.gitignore +++ b/.gitignore @@ -266,7 +266,7 @@ _Pvt_Extensions *.out *.app /windows/LightGBM.VC.db -/lightgbm +lightgbm /testlightgbm # Created by https://www.gitignore.io/api/python diff --git a/.vsts-ci.yml b/.vsts-ci.yml index f3f3fd8dd1fe..8c4e3e6a4949 100644 --- a/.vsts-ci.yml +++ b/.vsts-ci.yml @@ -34,7 +34,7 @@ jobs: SETUP_CONDA: 'false' OS_NAME: 'linux' PRODUCES_ARTIFACTS: 'true' - pool: sh-mariner + pool: sh-ubuntu container: linux-artifact-builder strategy: matrix: @@ -82,12 +82,12 @@ jobs: - job: Linux_latest ########################################### variables: - COMPILER: clang-17 + COMPILER: clang DEBIAN_FRONTEND: 'noninteractive' IN_UBUNTU_BASE_CONTAINER: 'true' OS_NAME: 'linux' SETUP_CONDA: 'true' - pool: sh-mariner + pool: sh-ubuntu container: ubuntu-latest strategy: matrix: @@ -309,7 +309,7 @@ jobs: R_LIB_PATH=~/Rlib export R_LIBS=${R_LIB_PATH} mkdir -p ${R_LIB_PATH} - RDscript -e "install.packages(c('R6', 'data.table', 'jsonlite', 'knitr', 'markdown', 'Matrix', 'RhpcBLASctl'), lib = '${R_LIB_PATH}', dependencies = c('Depends', 'Imports', 'LinkingTo'), repos = 'https://cran.rstudio.com', Ncpus = parallel::detectCores())" || exit -1 + RDscript -e "install.packages(c('R6', 'data.table', 'jsonlite', 'knitr', 'Matrix', 'RhpcBLASctl', 'rmarkdown'), lib = '${R_LIB_PATH}', dependencies = c('Depends', 'Imports', 'LinkingTo'), repos = 'https://cran.rstudio.com', Ncpus = parallel::detectCores())" || exit -1 sh build-cran-package.sh --r-executable=RD || exit -1 mv lightgbm_${LGB_VER}.tar.gz $(Build.ArtifactStagingDirectory)/lightgbm-${LGB_VER}-r-cran.tar.gz displayName: 'Build CRAN R-package' diff --git a/CMakeLists.txt b/CMakeLists.txt index 1ff289b9d045..6705ef130052 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -27,16 +27,19 @@ if(APPLE) option(APPLE_OUTPUT_DYLIB "Output dylib shared library" OFF) endif() -cmake_minimum_required(VERSION 3.18) - -project(lightgbm LANGUAGES C CXX) - -if(BUILD_CPP_TEST) - set(CMAKE_CXX_STANDARD 14) +if(__INTEGRATE_OPENCL) + cmake_minimum_required(VERSION 3.11) +elseif(USE_SWIG) + cmake_minimum_required(VERSION 3.8) +elseif(USE_GPU OR APPLE) + cmake_minimum_required(VERSION 3.2) +elseif(USE_CUDA) + cmake_minimum_required(VERSION 3.16) else() - set(CMAKE_CXX_STANDARD 11) + cmake_minimum_required(VERSION 3.0) endif() -set(CMAKE_CXX_STANDARD_REQUIRED ON) + +project(lightgbm LANGUAGES C CXX) list(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake/modules") @@ -75,6 +78,7 @@ elseif(CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang") if(CMAKE_CXX_COMPILER_VERSION VERSION_LESS "8.1.0") message(FATAL_ERROR "Insufficient AppleClang version") endif() + cmake_minimum_required(VERSION 3.16) elseif(MSVC) if(MSVC_VERSION LESS 1900) message( @@ -82,6 +86,7 @@ elseif(MSVC) "The compiler ${CMAKE_CXX_COMPILER} doesn't support required C++11 features. Please use a newer MSVC." ) endif() + cmake_minimum_required(VERSION 3.8) endif() if(USE_SWIG) @@ -198,26 +203,20 @@ if(__INTEGRATE_OPENCL) endif() if(USE_CUDA) - find_package(CUDA 11.0 REQUIRED) + find_package(CUDA 10.0 REQUIRED) include_directories(${CUDA_INCLUDE_DIRS}) set(CMAKE_CUDA_FLAGS "${CMAKE_CUDA_FLAGS} -Xcompiler=${OpenMP_CXX_FLAGS} -Xcompiler=-fPIC -Xcompiler=-Wall") - # reference for mapping of CUDA toolkit component versions to supported architectures ("compute capabilities"): - # https://en.wikipedia.org/wiki/CUDA#GPUs_supported - set(CUDA_ARCHS "6.0" "6.1" "6.2" "7.0" "7.5") + set(CUDA_ARCHS "6.0" "6.1" "6.2" "7.0") + if(CUDA_VERSION VERSION_GREATER_EQUAL "10.0") + list(APPEND CUDA_ARCHS "7.5") + endif() if(CUDA_VERSION VERSION_GREATER_EQUAL "11.0") list(APPEND CUDA_ARCHS "8.0") endif() if(CUDA_VERSION VERSION_GREATER_EQUAL "11.1") list(APPEND CUDA_ARCHS "8.6") endif() - if(CUDA_VERSION VERSION_GREATER_EQUAL "11.5") - list(APPEND CUDA_ARCHS "8.7") - endif() - if(CUDA_VERSION VERSION_GREATER_EQUAL "11.8") - list(APPEND CUDA_ARCHS "8.9") - list(APPEND CUDA_ARCHS "9.0") - endif() list(POP_BACK CUDA_ARCHS CUDA_LAST_SUPPORTED_ARCH) list(APPEND CUDA_ARCHS "${CUDA_LAST_SUPPORTED_ARCH}+PTX") cuda_select_nvcc_arch_flags(CUDA_ARCH_FLAGS ${CUDA_ARCHS}) @@ -325,7 +324,7 @@ endif() if(UNIX OR MINGW OR CYGWIN) set( CMAKE_CXX_FLAGS - "${CMAKE_CXX_FLAGS} -pthread -Wextra -Wall -Wno-ignored-attributes -Wno-unknown-pragmas -Wno-return-type" + "${CMAKE_CXX_FLAGS} -std=c++11 -pthread -Wextra -Wall -Wno-ignored-attributes -Wno-unknown-pragmas -Wno-return-type" ) if(MINGW) # ignore this warning: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=95353 @@ -379,13 +378,6 @@ if(MSVC) CMAKE_CXX_FLAGS_RELWITHDEBINFO ) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /W4 /MP") - if(__BUILD_FOR_R) - # MSVC does not like this commit: - # https://github.com/wch/r-source/commit/fb52ac1a610571fcb8ac92d886b9fefcffaa7d48 - # - # and raises "error C3646: 'private_data_c': unknown override specifier" - add_definitions(-DR_LEGACY_RCOMPLEX) - endif() if(USE_DEBUG) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /Od") else() @@ -428,11 +420,7 @@ file( src/objective/*.cpp src/network/*.cpp src/treelearner/*.cpp - src/utils/*.cpp -) -file( - GLOB - LGBM_CUDA_SOURCES +if(USE_CUDA) src/treelearner/*.cu src/boosting/cuda/*.cpp src/boosting/cuda/*.cu @@ -446,11 +434,8 @@ file( src/io/cuda/*.cpp src/cuda/*.cpp src/cuda/*.cu -) - -if(USE_CUDA) - list(APPEND SOURCES ${LGBM_CUDA_SOURCES}) endif() +) add_library(lightgbm_objs OBJECT ${SOURCES}) @@ -516,18 +501,7 @@ if(USE_SWIG) TARGET _lightgbm_swig POST_BUILD COMMAND "${Java_JAVAC_EXECUTABLE}" -d . java/*.java - COMMAND - "${CMAKE_COMMAND}" - -E - copy_if_different - "${PROJECT_SOURCE_DIR}/Release/lib_lightgbm.dll" - com/microsoft/ml/lightgbm/windows/x86_64 - COMMAND - "${CMAKE_COMMAND}" - -E - copy_if_different - "${PROJECT_SOURCE_DIR}/Release/lib_lightgbm_swig.dll" - com/microsoft/ml/lightgbm/windows/x86_64 + COMMAND cp "${PROJECT_SOURCE_DIR}/Release/*.dll" com/microsoft/ml/lightgbm/windows/x86_64 COMMAND "${Java_JAR_EXECUTABLE}" -cf lightgbmlib.jar com ) endif() @@ -536,16 +510,9 @@ if(USE_SWIG) TARGET _lightgbm_swig POST_BUILD COMMAND "${Java_JAVAC_EXECUTABLE}" -d . java/*.java + COMMAND cp "${PROJECT_SOURCE_DIR}/*.dylib" com/microsoft/ml/lightgbm/osx/x86_64 COMMAND - "${CMAKE_COMMAND}" - -E - copy_if_different - "${PROJECT_SOURCE_DIR}/lib_lightgbm.dylib" - com/microsoft/ml/lightgbm/osx/x86_64 - COMMAND - "${CMAKE_COMMAND}" - -E - copy_if_different + cp "${PROJECT_SOURCE_DIR}/lib_lightgbm_swig.jnilib" com/microsoft/ml/lightgbm/osx/x86_64/lib_lightgbm_swig.dylib COMMAND "${Java_JAR_EXECUTABLE}" -cf lightgbmlib.jar com @@ -555,12 +522,7 @@ if(USE_SWIG) TARGET _lightgbm_swig POST_BUILD COMMAND "${Java_JAVAC_EXECUTABLE}" -d . java/*.java - COMMAND - "${CMAKE_COMMAND}" - -E - copy_if_different - "${PROJECT_SOURCE_DIR}/lib_lightgbm.so" - com/microsoft/ml/lightgbm/linux/x86_64 + COMMAND cp "${PROJECT_SOURCE_DIR}/*.so" com/microsoft/ml/lightgbm/linux/x86_64 COMMAND "${Java_JAR_EXECUTABLE}" -cf lightgbmlib.jar com ) endif() @@ -655,7 +617,7 @@ if(BUILD_CPP_TEST) FetchContent_Declare( googletest GIT_REPOSITORY https://github.com/google/googletest.git - GIT_TAG v1.14.0 + GIT_TAG release-1.11.0 ) FetchContent_MakeAvailable(googletest) add_library(GTest::GTest ALIAS gtest) @@ -666,7 +628,6 @@ if(BUILD_CPP_TEST) file(GLOB CPP_TEST_SOURCES tests/cpp_tests/*.cpp) if(MSVC) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /permissive-") set( CompilerFlags CMAKE_CXX_FLAGS diff --git a/R-package/DESCRIPTION b/R-package/DESCRIPTION index 6fbeeee859be..1193c0d463b9 100755 --- a/R-package/DESCRIPTION +++ b/R-package/DESCRIPTION @@ -4,10 +4,10 @@ Title: Light Gradient Boosting Machine Version: ~~VERSION~~ Date: ~~DATE~~ Authors@R: c( - person("Yu", "Shi", email = "yushi2@microsoft.com", role = c("aut")), + person("Yu", "Shi", email = "yushi2@microsoft.com", role = c("aut", "cre")), person("Guolin", "Ke", email = "guolin.ke@outlook.com", role = c("aut")), person("Damien", "Soukhavong", email = "damien.soukhavong@skema.edu", role = c("aut")), - person("James", "Lamb", email="jaylamb20@gmail.com", role = c("aut", "cre")), + person("James", "Lamb", email="jaylamb20@gmail.com", role = c("aut")), person("Qi", "Meng", role = c("aut")), person("Thomas", "Finley", role = c("aut")), person("Taifeng", "Wang", role = c("aut")), @@ -46,9 +46,9 @@ Biarch: true VignetteBuilder: knitr Suggests: knitr, - markdown, processx, RhpcBLASctl, + rmarkdown, testthat Depends: R (>= 3.5) diff --git a/R-package/NAMESPACE b/R-package/NAMESPACE index 718f0e55a0d7..e07af84d8824 100644 --- a/R-package/NAMESPACE +++ b/R-package/NAMESPACE @@ -9,7 +9,6 @@ S3method(print,lgb.Booster) S3method(set_field,lgb.Dataset) S3method(slice,lgb.Dataset) S3method(summary,lgb.Booster) -export(getLGBMthreads) export(get_field) export(lgb.Dataset) export(lgb.Dataset.construct) @@ -34,7 +33,8 @@ export(lgb.restore_handle) export(lgb.save) export(lgb.train) export(lightgbm) -export(setLGBMthreads) +export(readRDS.lgb.Booster) +export(saveRDS.lgb.Booster) export(set_field) export(slice) import(methods) diff --git a/R-package/R/callback.R b/R-package/R/callback.R index c436409ddafb..e428dfb79eea 100644 --- a/R-package/R/callback.R +++ b/R-package/R/callback.R @@ -90,7 +90,7 @@ cb_print_evaluation <- function(period) { # Check if message is existing if (nchar(msg) > 0L) { - cat(.merge_eval_string(env = env), "\n") + print(.merge_eval_string(env = env)) } } @@ -208,9 +208,9 @@ cb_early_stop <- function(stopping_rounds, first_metric_only, verbose) { msg <- paste0( "Will train until there is no improvement in " , stopping_rounds - , " rounds.\n" + , " rounds." ) - cat(msg) + print(msg) } # Internally treat everything as a maximization task @@ -284,7 +284,7 @@ cb_early_stop <- function(stopping_rounds, first_metric_only, verbose) { } if (isTRUE(verbose)) { - cat(paste0("Early stopping, best iteration is: ", best_msg[[i]], "\n")) + print(paste0("Early stopping, best iteration is: ", best_msg[[i]])) } # Store best iteration and stop @@ -302,7 +302,7 @@ cb_early_stop <- function(stopping_rounds, first_metric_only, verbose) { } if (isTRUE(verbose)) { - cat(paste0("Did not meet early stopping, best iteration is: ", best_msg[[i]], "\n")) + print(paste0("Did not meet early stopping, best iteration is: ", best_msg[[i]])) } # Store best iteration and stop @@ -323,17 +323,17 @@ cb_early_stop <- function(stopping_rounds, first_metric_only, verbose) { } # Extract callback names from the list of callbacks -.callback_names <- function(cb_list) { +callback.names <- function(cb_list) { return(unlist(lapply(cb_list, attr, "name"))) } -.add_cb <- function(cb_list, cb) { +add.cb <- function(cb_list, cb) { # Combine two elements cb_list <- c(cb_list, cb) # Set names of elements - names(cb_list) <- .callback_names(cb_list = cb_list) + names(cb_list) <- callback.names(cb_list = cb_list) if ("cb_early_stop" %in% names(cb_list)) { @@ -349,7 +349,7 @@ cb_early_stop <- function(stopping_rounds, first_metric_only, verbose) { } -.categorize_callbacks <- function(cb_list) { +categorize.callbacks <- function(cb_list) { # Check for pre-iteration or post-iteration return( diff --git a/R-package/R/lgb.Booster.R b/R-package/R/lgb.Booster.R index 4437c6fa552e..755b171724f9 100644 --- a/R-package/R/lgb.Booster.R +++ b/R-package/R/lgb.Booster.R @@ -31,12 +31,12 @@ Booster <- R6::R6Class( if (!is.null(train_set)) { - if (!.is_Dataset(train_set)) { + if (!lgb.is.Dataset(train_set)) { stop("lgb.Booster: Can only use lgb.Dataset as training data") } train_set_handle <- train_set$.__enclos_env__$private$get_handle() params <- utils::modifyList(params, train_set$get_params()) - params_str <- .params2str(params = params) + params_str <- lgb.params2str(params = params) # Store booster handle handle <- .Call( LGBM_BoosterCreate_R @@ -130,7 +130,7 @@ Booster <- R6::R6Class( # Add validation data add_valid = function(data, name) { - if (!.is_Dataset(data)) { + if (!lgb.is.Dataset(data)) { stop("lgb.Booster.add_valid: Can only use lgb.Dataset as validation data") } @@ -167,7 +167,7 @@ Booster <- R6::R6Class( params <- utils::modifyList(self$params, params) } - params_str <- .params2str(params = params) + params_str <- lgb.params2str(params = params) self$restore_handle() @@ -193,7 +193,7 @@ Booster <- R6::R6Class( if (!is.null(train_set)) { - if (!.is_Dataset(train_set)) { + if (!lgb.is.Dataset(train_set)) { stop("lgb.Booster.update: Only can use lgb.Dataset as training data") } @@ -340,7 +340,7 @@ Booster <- R6::R6Class( # Evaluate data on metrics eval = function(data, name, feval = NULL) { - if (!.is_Dataset(data)) { + if (!lgb.is.Dataset(data)) { stop("lgb.Booster.eval: Can only use lgb.Dataset to eval") } @@ -508,17 +508,17 @@ Booster <- R6::R6Class( # NOTE: doing this here instead of in Predictor$predict() to keep # Predictor$predict() as fast as possible if (length(params) > 0L) { - params <- .check_wrapper_param( + params <- lgb.check.wrapper_param( main_param_name = "predict_raw_score" , params = params , alternative_kwarg_value = rawscore ) - params <- .check_wrapper_param( + params <- lgb.check.wrapper_param( main_param_name = "predict_leaf_index" , params = params , alternative_kwarg_value = predleaf ) - params <- .check_wrapper_param( + params <- lgb.check.wrapper_param( main_param_name = "predict_contrib" , params = params , alternative_kwarg_value = predcontrib @@ -586,7 +586,7 @@ Booster <- R6::R6Class( , predcontrib , start_iteration , num_iteration - , .params2str(params = params) + , lgb.params2str(params = params) ) private$fast_predict_config <- list( @@ -622,7 +622,7 @@ Booster <- R6::R6Class( }, check_null_handle = function() { - return(.is_null_handle(private$handle)) + return(lgb.is.null.handle(private$handle)) }, restore_handle = function() { @@ -917,8 +917,6 @@ NULL #' the factor levels not being present in the output. #' @examples #' \donttest{ -#' \dontshow{setLGBMthreads(2L)} -#' \dontshow{data.table::setDTthreads(1L)} #' data(agaricus.train, package = "lightgbm") #' train <- agaricus.train #' dtrain <- lgb.Dataset(train$data, label = train$label) @@ -961,7 +959,7 @@ predict.lgb.Booster <- function(object, params = list(), ...) { - if (!.is_Booster(x = object)) { + if (!lgb.is.Booster(x = object)) { stop("predict.lgb.Booster: object should be an ", sQuote("lgb.Booster")) } @@ -1084,8 +1082,6 @@ predict.lgb.Booster <- function(object, #' \link{predict.lgb.Booster}. #' @examples #' \donttest{ -#' \dontshow{setLGBMthreads(2L)} -#' \dontshow{data.table::setDTthreads(1L)} #' library(lightgbm) #' data(mtcars) #' X <- as.matrix(mtcars[, -1L]) @@ -1118,7 +1114,7 @@ lgb.configure_fast_predict <- function(model, num_iteration = NULL, type = "response", params = list()) { - if (!.is_Booster(x = model)) { + if (!lgb.is.Booster(x = model)) { stop("lgb.configure_fast_predict: model should be an ", sQuote("lgb.Booster")) } if (type == "class") { @@ -1164,7 +1160,7 @@ lgb.configure_fast_predict <- function(model, print.lgb.Booster <- function(x, ...) { # nolint start handle <- x$.__enclos_env__$private$handle - handle_is_null <- .is_null_handle(handle) + handle_is_null <- lgb.is.null.handle(handle) if (!handle_is_null) { ntrees <- x$current_iter() @@ -1228,8 +1224,6 @@ summary.lgb.Booster <- function(object, ...) { #' #' @examples #' \donttest{ -#' \dontshow{setLGBMthreads(2L)} -#' \dontshow{data.table::setDTthreads(1L)} #' data(agaricus.train, package = "lightgbm") #' train <- agaricus.train #' dtrain <- lgb.Dataset(train$data, label = train$label) @@ -1295,8 +1289,6 @@ lgb.load <- function(filename = NULL, model_str = NULL) { #' #' @examples #' \donttest{ -#' \dontshow{setLGBMthreads(2L)} -#' \dontshow{data.table::setDTthreads(1L)} #' library(lightgbm) #' data(agaricus.train, package = "lightgbm") #' train <- agaricus.train @@ -1324,7 +1316,7 @@ lgb.load <- function(filename = NULL, model_str = NULL) { #' @export lgb.save <- function(booster, filename, num_iteration = NULL) { - if (!.is_Booster(x = booster)) { + if (!lgb.is.Booster(x = booster)) { stop("lgb.save: booster should be an ", sQuote("lgb.Booster")) } @@ -1354,8 +1346,6 @@ lgb.save <- function(booster, filename, num_iteration = NULL) { #' @examples #' \donttest{ #' library(lightgbm) -#' \dontshow{setLGBMthreads(2L)} -#' \dontshow{data.table::setDTthreads(1L)} #' data(agaricus.train, package = "lightgbm") #' train <- agaricus.train #' dtrain <- lgb.Dataset(train$data, label = train$label) @@ -1382,7 +1372,7 @@ lgb.save <- function(booster, filename, num_iteration = NULL) { #' @export lgb.dump <- function(booster, num_iteration = NULL) { - if (!.is_Booster(x = booster)) { + if (!lgb.is.Booster(x = booster)) { stop("lgb.dump: booster should be an ", sQuote("lgb.Booster")) } @@ -1406,8 +1396,6 @@ lgb.dump <- function(booster, num_iteration = NULL) { #' #' @examples #' \donttest{ -#' \dontshow{setLGBMthreads(2L)} -#' \dontshow{data.table::setDTthreads(1L)} #' # train a regression model #' data(agaricus.train, package = "lightgbm") #' train <- agaricus.train @@ -1442,7 +1430,7 @@ lgb.dump <- function(booster, num_iteration = NULL) { #' @export lgb.get.eval.result <- function(booster, data_name, eval_name, iters = NULL, is_err = FALSE) { - if (!.is_Booster(x = booster)) { + if (!lgb.is.Booster(x = booster)) { stop("lgb.get.eval.result: Can only use ", sQuote("lgb.Booster"), " to get eval result") } @@ -1474,6 +1462,7 @@ lgb.get.eval.result <- function(booster, data_name, eval_name, iters = NULL, is_ , toString(eval_names) , "]" )) + stop("lgb.get.eval.result: wrong eval name") } result <- booster$record_evals[[data_name]][[eval_name]][[.EVAL_KEY()]] diff --git a/R-package/R/lgb.DataProcessor.R b/R-package/R/lgb.DataProcessor.R index c35ce4f93bd3..fc7061945b62 100644 --- a/R-package/R/lgb.DataProcessor.R +++ b/R-package/R/lgb.DataProcessor.R @@ -39,7 +39,7 @@ DataProcessor <- R6::R6Class( ) } data_num_class <- length(self$factor_levels) - params <- .check_wrapper_param( + params <- lgb.check.wrapper_param( main_param_name = "num_class" , params = params , alternative_kwarg_value = data_num_class diff --git a/R-package/R/lgb.Dataset.R b/R-package/R/lgb.Dataset.R index ff9b0b4fa38a..e2892ea4bae0 100644 --- a/R-package/R/lgb.Dataset.R +++ b/R-package/R/lgb.Dataset.R @@ -55,10 +55,10 @@ Dataset <- R6::R6Class( init_score = NULL) { # validate inputs early to avoid unnecessary computation - if (!(is.null(reference) || .is_Dataset(reference))) { + if (!(is.null(reference) || lgb.is.Dataset(reference))) { stop("lgb.Dataset: If provided, reference must be a ", sQuote("lgb.Dataset")) } - if (!(is.null(predictor) || .is_Predictor(predictor))) { + if (!(is.null(predictor) || lgb.is.Predictor(predictor))) { stop("lgb.Dataset: If provided, predictor must be a ", sQuote("lgb.Predictor")) } @@ -135,7 +135,7 @@ Dataset <- R6::R6Class( construct = function() { # Check for handle null - if (!.is_null_handle(x = private$handle)) { + if (!lgb.is.null.handle(x = private$handle)) { return(invisible(self)) } @@ -191,7 +191,7 @@ Dataset <- R6::R6Class( } # Generate parameter str - params_str <- .params2str(params = private$params) + params_str <- lgb.params2str(params = private$params) # Get handle of reference dataset ref_handle <- NULL @@ -277,7 +277,7 @@ Dataset <- R6::R6Class( ) } - if (.is_null_handle(x = handle)) { + if (lgb.is.null.handle(x = handle)) { stop("lgb.Dataset.construct: cannot create Dataset handle") } # Setup class and private type @@ -345,7 +345,7 @@ Dataset <- R6::R6Class( dim = function() { # Check for handle - if (!.is_null_handle(x = private$handle)) { + if (!lgb.is.null.handle(x = private$handle)) { num_row <- 0L num_col <- 0L @@ -385,7 +385,7 @@ Dataset <- R6::R6Class( # Get number of bins for feature get_feature_num_bin = function(feature) { - if (.is_null_handle(x = private$handle)) { + if (lgb.is.null.handle(x = private$handle)) { stop("Cannot get number of bins in feature before constructing Dataset.") } if (is.character(feature)) { @@ -409,7 +409,7 @@ Dataset <- R6::R6Class( get_colnames = function() { # Check for handle - if (!.is_null_handle(x = private$handle)) { + if (!lgb.is.null.handle(x = private$handle)) { private$colnames <- .Call( LGBM_DatasetGetFeatureNames_R , private$handle @@ -449,7 +449,7 @@ Dataset <- R6::R6Class( # Write column names private$colnames <- colnames - if (!.is_null_handle(x = private$handle)) { + if (!lgb.is.null.handle(x = private$handle)) { # Merge names with tab separation merged_name <- paste0(as.list(private$colnames), collapse = "\t") @@ -478,7 +478,7 @@ Dataset <- R6::R6Class( # Check for info name and handle if (is.null(private$info[[field_name]])) { - if (.is_null_handle(x = private$handle)) { + if (lgb.is.null.handle(x = private$handle)) { stop("Cannot perform Dataset$get_field() before constructing Dataset.") } @@ -536,7 +536,7 @@ Dataset <- R6::R6Class( # Store information privately private$info[[field_name]] <- data - if (!.is_null_handle(x = private$handle) && !is.null(data)) { + if (!lgb.is.null.handle(x = private$handle) && !is.null(data)) { if (length(data) > 0L) { @@ -583,14 +583,14 @@ Dataset <- R6::R6Class( return(invisible(self)) } new_params <- utils::modifyList(private$params, params) - if (.is_null_handle(x = private$handle)) { + if (lgb.is.null.handle(x = private$handle)) { private$params <- new_params } else { tryCatch({ .Call( LGBM_DatasetUpdateParamChecking_R - , .params2str(params = private$params) - , .params2str(params = new_params) + , lgb.params2str(params = private$params) + , lgb.params2str(params = new_params) ) private$params <- new_params }, error = function(e) { @@ -663,7 +663,7 @@ Dataset <- R6::R6Class( please set ", sQuote("free_raw_data = FALSE"), " when you construct lgb.Dataset") } - if (!.is_Dataset(reference)) { + if (!lgb.is.Dataset(reference)) { stop("set_reference: Can only use lgb.Dataset as a reference") } @@ -711,7 +711,7 @@ Dataset <- R6::R6Class( get_handle = function() { # Get handle and construct if needed - if (.is_null_handle(x = private$handle)) { + if (lgb.is.null.handle(x = private$handle)) { self$construct() } return(private$handle) @@ -734,7 +734,7 @@ Dataset <- R6::R6Class( if (!is.null(predictor)) { # Predictor is unknown - if (!.is_Predictor(predictor)) { + if (!lgb.is.Predictor(predictor)) { stop("set_predictor: Can only use lgb.Predictor as predictor") } @@ -780,8 +780,6 @@ Dataset <- R6::R6Class( #' #' @examples #' \donttest{ -#' \dontshow{setLGBMthreads(2L)} -#' \dontshow{data.table::setDTthreads(1L)} #' data(agaricus.train, package = "lightgbm") #' train <- agaricus.train #' dtrain <- lgb.Dataset(train$data, label = train$label) @@ -839,8 +837,6 @@ lgb.Dataset <- function(data, #' #' @examples #' \donttest{ -#' \dontshow{setLGBMthreads(2L)} -#' \dontshow{data.table::setDTthreads(1L)} #' data(agaricus.train, package = "lightgbm") #' train <- agaricus.train #' dtrain <- lgb.Dataset(train$data, label = train$label) @@ -892,7 +888,7 @@ lgb.Dataset.create.valid <- function(dataset, init_score = NULL, params = list()) { - if (!.is_Dataset(x = dataset)) { + if (!lgb.is.Dataset(x = dataset)) { stop("lgb.Dataset.create.valid: input data should be an lgb.Dataset object") } @@ -917,8 +913,6 @@ lgb.Dataset.create.valid <- function(dataset, #' #' @examples #' \donttest{ -#' \dontshow{setLGBMthreads(2L)} -#' \dontshow{data.table::setDTthreads(1L)} #' data(agaricus.train, package = "lightgbm") #' train <- agaricus.train #' dtrain <- lgb.Dataset(train$data, label = train$label) @@ -928,7 +922,7 @@ lgb.Dataset.create.valid <- function(dataset, #' @export lgb.Dataset.construct <- function(dataset) { - if (!.is_Dataset(x = dataset)) { + if (!lgb.is.Dataset(x = dataset)) { stop("lgb.Dataset.construct: input data should be an lgb.Dataset object") } @@ -948,8 +942,6 @@ lgb.Dataset.construct <- function(dataset) { #' #' @examples #' \donttest{ -#' \dontshow{setLGBMthreads(2L)} -#' \dontshow{data.table::setDTthreads(1L)} #' data(agaricus.train, package = "lightgbm") #' train <- agaricus.train #' dtrain <- lgb.Dataset(train$data, label = train$label) @@ -962,7 +954,7 @@ lgb.Dataset.construct <- function(dataset) { #' @export dim.lgb.Dataset <- function(x) { - if (!.is_Dataset(x = x)) { + if (!lgb.is.Dataset(x = x)) { stop("dim.lgb.Dataset: input data should be an lgb.Dataset object") } @@ -983,8 +975,6 @@ dim.lgb.Dataset <- function(x) { #' #' @examples #' \donttest{ -#' \dontshow{setLGBMthreads(2L)} -#' \dontshow{data.table::setDTthreads(1L)} #' data(agaricus.train, package = "lightgbm") #' train <- agaricus.train #' dtrain <- lgb.Dataset(train$data, label = train$label) @@ -999,7 +989,7 @@ dim.lgb.Dataset <- function(x) { #' @export dimnames.lgb.Dataset <- function(x) { - if (!.is_Dataset(x = x)) { + if (!lgb.is.Dataset(x = x)) { stop("dimnames.lgb.Dataset: input data should be an lgb.Dataset object") } @@ -1055,8 +1045,6 @@ dimnames.lgb.Dataset <- function(x) { #' #' @examples #' \donttest{ -#' \dontshow{setLGBMthreads(2L)} -#' \dontshow{data.table::setDTthreads(1L)} #' data(agaricus.train, package = "lightgbm") #' train <- agaricus.train #' dtrain <- lgb.Dataset(train$data, label = train$label) @@ -1074,7 +1062,7 @@ slice <- function(dataset, idxset) { #' @export slice.lgb.Dataset <- function(dataset, idxset) { - if (!.is_Dataset(x = dataset)) { + if (!lgb.is.Dataset(x = dataset)) { stop("slice.lgb.Dataset: input dataset should be an lgb.Dataset object") } @@ -1101,8 +1089,6 @@ slice.lgb.Dataset <- function(dataset, idxset) { #' #' @examples #' \donttest{ -#' \dontshow{setLGBMthreads(2L)} -#' \dontshow{data.table::setDTthreads(1L)} #' data(agaricus.train, package = "lightgbm") #' train <- agaricus.train #' dtrain <- lgb.Dataset(train$data, label = train$label) @@ -1124,7 +1110,7 @@ get_field <- function(dataset, field_name) { get_field.lgb.Dataset <- function(dataset, field_name) { # Check if dataset is not a dataset - if (!.is_Dataset(x = dataset)) { + if (!lgb.is.Dataset(x = dataset)) { stop("get_field.lgb.Dataset(): input dataset should be an lgb.Dataset object") } @@ -1152,8 +1138,6 @@ get_field.lgb.Dataset <- function(dataset, field_name) { #' #' @examples #' \donttest{ -#' \dontshow{setLGBMthreads(2L)} -#' \dontshow{data.table::setDTthreads(1L)} #' data(agaricus.train, package = "lightgbm") #' train <- agaricus.train #' dtrain <- lgb.Dataset(train$data, label = train$label) @@ -1174,7 +1158,7 @@ set_field <- function(dataset, field_name, data) { #' @export set_field.lgb.Dataset <- function(dataset, field_name, data) { - if (!.is_Dataset(x = dataset)) { + if (!lgb.is.Dataset(x = dataset)) { stop("set_field.lgb.Dataset: input dataset should be an lgb.Dataset object") } @@ -1193,8 +1177,6 @@ set_field.lgb.Dataset <- function(dataset, field_name, data) { #' #' @examples #' \donttest{ -#' \dontshow{setLGBMthreads(2L)} -#' \dontshow{data.table::setDTthreads(1L)} #' data(agaricus.train, package = "lightgbm") #' train <- agaricus.train #' dtrain <- lgb.Dataset(train$data, label = train$label) @@ -1207,7 +1189,7 @@ set_field.lgb.Dataset <- function(dataset, field_name, data) { #' @export lgb.Dataset.set.categorical <- function(dataset, categorical_feature) { - if (!.is_Dataset(x = dataset)) { + if (!lgb.is.Dataset(x = dataset)) { stop("lgb.Dataset.set.categorical: input dataset should be an lgb.Dataset object") } @@ -1225,8 +1207,6 @@ lgb.Dataset.set.categorical <- function(dataset, categorical_feature) { #' #' @examples #' \donttest{ -#' \dontshow{setLGBMthreads(2L)} -#' \dontshow{data.table::setDTthreads(1L)} #' # create training Dataset #' data(agaricus.train, package ="lightgbm") #' train <- agaricus.train @@ -1242,7 +1222,7 @@ lgb.Dataset.set.categorical <- function(dataset, categorical_feature) { #' @export lgb.Dataset.set.reference <- function(dataset, reference) { - if (!.is_Dataset(x = dataset)) { + if (!lgb.is.Dataset(x = dataset)) { stop("lgb.Dataset.set.reference: input dataset should be an lgb.Dataset object") } @@ -1260,8 +1240,6 @@ lgb.Dataset.set.reference <- function(dataset, reference) { #' #' @examples #' \donttest{ -#' \dontshow{setLGBMthreads(2L)} -#' \dontshow{data.table::setDTthreads(1L)} #' data(agaricus.train, package = "lightgbm") #' train <- agaricus.train #' dtrain <- lgb.Dataset(train$data, label = train$label) @@ -1270,7 +1248,7 @@ lgb.Dataset.set.reference <- function(dataset, reference) { #' @export lgb.Dataset.save <- function(dataset, fname) { - if (!.is_Dataset(x = dataset)) { + if (!lgb.is.Dataset(x = dataset)) { stop("lgb.Dataset.save: input dataset should be an lgb.Dataset object") } diff --git a/R-package/R/lgb.Predictor.R b/R-package/R/lgb.Predictor.R index 3a411efd75ba..0b7b39e2d8c2 100644 --- a/R-package/R/lgb.Predictor.R +++ b/R-package/R/lgb.Predictor.R @@ -28,7 +28,7 @@ Predictor <- R6::R6Class( # Initialize will create a starter model initialize = function(modelfile, params = list(), fast_predict_config = list()) { - private$params <- .params2str(params = params) + private$params <- lgb.params2str(params = params) handle <- NULL if (is.character(modelfile)) { @@ -46,7 +46,7 @@ Predictor <- R6::R6Class( handle <- modelfile private$need_free_handle <- FALSE - } else if (.is_Booster(modelfile)) { + } else if (lgb.is.Booster(modelfile)) { handle <- modelfile$get_handle() private$need_free_handle <- FALSE @@ -512,7 +512,7 @@ Predictor <- R6::R6Class( return(FALSE) } - if (.is_null_handle(private$fast_predict_config$handle)) { + if (lgb.is.null.handle(private$fast_predict_config$handle)) { warning(paste0("Model had fast CSR predict configuration, but it is inactive." , " Try re-generating it through 'lgb.configure_fast_predict'.")) return(FALSE) @@ -527,8 +527,8 @@ Predictor <- R6::R6Class( private$fast_predict_config$rawscore == rawscore && private$fast_predict_config$predleaf == predleaf && private$fast_predict_config$predcontrib == predcontrib && - .equal_or_both_null(private$fast_predict_config$start_iteration, start_iteration) && - .equal_or_both_null(private$fast_predict_config$num_iteration, num_iteration) + lgb.equal.or.both.null(private$fast_predict_config$start_iteration, start_iteration) && + lgb.equal.or.both.null(private$fast_predict_config$num_iteration, num_iteration) ) } ) diff --git a/R-package/R/lgb.cv.R b/R-package/R/lgb.cv.R index 0545fbf71899..f81026fe673f 100644 --- a/R-package/R/lgb.cv.R +++ b/R-package/R/lgb.cv.R @@ -51,8 +51,6 @@ CVBooster <- R6::R6Class( #' #' @examples #' \donttest{ -#' \dontshow{setLGBMthreads(2L)} -#' \dontshow{data.table::setDTthreads(1L)} #' data(agaricus.train, package = "lightgbm") #' train <- agaricus.train #' dtrain <- lgb.Dataset(train$data, label = train$label) @@ -101,7 +99,7 @@ lgb.cv <- function(params = list() } # If 'data' is not an lgb.Dataset, try to construct one using 'label' - if (!.is_Dataset(x = data)) { + if (!lgb.is.Dataset(x = data)) { if (is.null(label)) { stop("'label' must be provided for lgb.cv if 'data' is not an 'lgb.Dataset'") } @@ -112,27 +110,27 @@ lgb.cv <- function(params = list() # in `params`. # this ensures that the model stored with Booster$save() correctly represents # what was passed in - params <- .check_wrapper_param( + params <- lgb.check.wrapper_param( main_param_name = "verbosity" , params = params , alternative_kwarg_value = verbose ) - params <- .check_wrapper_param( + params <- lgb.check.wrapper_param( main_param_name = "num_iterations" , params = params , alternative_kwarg_value = nrounds ) - params <- .check_wrapper_param( + params <- lgb.check.wrapper_param( main_param_name = "metric" , params = params , alternative_kwarg_value = NULL ) - params <- .check_wrapper_param( + params <- lgb.check.wrapper_param( main_param_name = "objective" , params = params , alternative_kwarg_value = obj ) - params <- .check_wrapper_param( + params <- lgb.check.wrapper_param( main_param_name = "early_stopping_round" , params = params , alternative_kwarg_value = early_stopping_rounds @@ -150,7 +148,7 @@ lgb.cv <- function(params = list() # (for backwards compatibility). If it is a list of functions, store # all of them. This makes it possible to pass any mix of strings like "auc" # and custom functions to eval - params <- .check_eval(params = params, eval = eval) + params <- lgb.check.eval(params = params, eval = eval) eval_functions <- list(NULL) if (is.function(eval)) { eval_functions <- list(eval) @@ -168,7 +166,7 @@ lgb.cv <- function(params = list() # Check for boosting from a trained model if (is.character(init_model)) { predictor <- Predictor$new(modelfile = init_model) - } else if (.is_Booster(x = init_model)) { + } else if (lgb.is.Booster(x = init_model)) { predictor <- init_model$to_predictor() } @@ -195,7 +193,7 @@ lgb.cv <- function(params = list() } else if (!is.null(data$get_colnames())) { cnames <- data$get_colnames() } - params[["interaction_constraints"]] <- .check_interaction_constraints( + params[["interaction_constraints"]] <- lgb.check_interaction_constraints( interaction_constraints = interaction_constraints , column_names = cnames ) @@ -234,7 +232,7 @@ lgb.cv <- function(params = list() } # Create folds - folds <- .generate_cv_folds( + folds <- generate.cv.folds( nfold = nfold , nrows = nrow(data) , stratified = stratified @@ -247,12 +245,12 @@ lgb.cv <- function(params = list() # Add printing log callback if (params[["verbosity"]] > 0L && eval_freq > 0L) { - callbacks <- .add_cb(cb_list = callbacks, cb = cb_print_evaluation(period = eval_freq)) + callbacks <- add.cb(cb_list = callbacks, cb = cb_print_evaluation(period = eval_freq)) } # Add evaluation log callback if (record) { - callbacks <- .add_cb(cb_list = callbacks, cb = cb_record_evaluation()) + callbacks <- add.cb(cb_list = callbacks, cb = cb_record_evaluation()) } # Did user pass parameters that indicate they want to use early stopping? @@ -284,7 +282,7 @@ lgb.cv <- function(params = list() # If user supplied early_stopping_rounds, add the early stopping callback if (using_early_stopping) { - callbacks <- .add_cb( + callbacks <- add.cb( cb_list = callbacks , cb = cb_early_stop( stopping_rounds = early_stopping_rounds @@ -294,7 +292,7 @@ lgb.cv <- function(params = list() ) } - cb <- .categorize_callbacks(cb_list = callbacks) + cb <- categorize.callbacks(cb_list = callbacks) # Construct booster for each fold. The data.table() code below is used to # guarantee that indices are sorted while keeping init_score and weight together @@ -389,7 +387,7 @@ lgb.cv <- function(params = list() }) # Prepare collection of evaluation results - merged_msg <- .merge_cv_result( + merged_msg <- lgb.merge.cv.result( msg = msg , showsd = showsd ) @@ -465,7 +463,7 @@ lgb.cv <- function(params = list() } # Generates random (stratified if needed) CV folds -.generate_cv_folds <- function(nfold, nrows, stratified, label, group, params) { +generate.cv.folds <- function(nfold, nrows, stratified, label, group, params) { # Check for group existence if (is.null(group)) { @@ -478,7 +476,7 @@ lgb.cv <- function(params = list() y <- label[rnd_idx] y <- as.factor(y) - folds <- .stratified_folds(y = y, k = nfold) + folds <- lgb.stratified.folds(y = y, k = nfold) } else { @@ -530,7 +528,7 @@ lgb.cv <- function(params = list() # It was borrowed from caret::createFolds and simplified # by always returning an unnamed list of fold indices. #' @importFrom stats quantile -.stratified_folds <- function(y, k) { +lgb.stratified.folds <- function(y, k) { # Group the numeric data based on their magnitudes # and sample within those groups. @@ -596,7 +594,7 @@ lgb.cv <- function(params = list() return(out) } -.merge_cv_result <- function(msg, showsd) { +lgb.merge.cv.result <- function(msg, showsd) { if (length(msg) == 0L) { stop("lgb.cv: size of cv result error") diff --git a/R-package/R/lgb.drop_serialized.R b/R-package/R/lgb.drop_serialized.R index e53f2cafac11..bcc2480e8ccc 100644 --- a/R-package/R/lgb.drop_serialized.R +++ b/R-package/R/lgb.drop_serialized.R @@ -13,7 +13,7 @@ #' @seealso \link{lgb.restore_handle}, \link{lgb.make_serializable}. #' @export lgb.drop_serialized <- function(model) { - if (!.is_Booster(x = model)) { + if (!lgb.is.Booster(x = model)) { stop("lgb.drop_serialized: model should be an ", sQuote("lgb.Booster")) } model$drop_raw() diff --git a/R-package/R/lgb.importance.R b/R-package/R/lgb.importance.R index 7c76131f4f53..5a58770553f9 100644 --- a/R-package/R/lgb.importance.R +++ b/R-package/R/lgb.importance.R @@ -14,8 +14,6 @@ #' #' @examples #' \donttest{ -#' \dontshow{setLGBMthreads(2L)} -#' \dontshow{data.table::setDTthreads(1L)} #' data(agaricus.train, package = "lightgbm") #' train <- agaricus.train #' dtrain <- lgb.Dataset(train$data, label = train$label) @@ -41,7 +39,7 @@ #' @export lgb.importance <- function(model, percentage = TRUE) { - if (!.is_Booster(x = model)) { + if (!lgb.is.Booster(x = model)) { stop("'model' has to be an object of class lgb.Booster") } diff --git a/R-package/R/lgb.interprete.R b/R-package/R/lgb.interprete.R index 8f93d45429f1..7de772664d8b 100644 --- a/R-package/R/lgb.interprete.R +++ b/R-package/R/lgb.interprete.R @@ -17,8 +17,6 @@ #' #' @examples #' \donttest{ -#' \dontshow{setLGBMthreads(2L)} -#' \dontshow{data.table::setDTthreads(1L)} #' Logit <- function(x) log(x / (1.0 - x)) #' data(agaricus.train, package = "lightgbm") #' train <- agaricus.train @@ -88,7 +86,7 @@ lgb.interprete <- function(model, ) for (i in seq_along(idxset)) { - tree_interpretation_dt_list[[i]] <- .single_row_interprete( + tree_interpretation_dt_list[[i]] <- single.row.interprete( tree_dt = tree_dt , num_class = num_class , tree_index_mat = tree_index_mat_list[[i]] @@ -153,7 +151,7 @@ single.tree.interprete <- function(tree_dt, } #' @importFrom data.table := rbindlist setorder -.multiple_tree_interprete <- function(tree_dt, +multiple.tree.interprete <- function(tree_dt, tree_index, leaf_index) { @@ -188,7 +186,7 @@ single.tree.interprete <- function(tree_dt, } #' @importFrom data.table set setnames -.single_row_interprete <- function(tree_dt, num_class, tree_index_mat, leaf_index_mat) { +single.row.interprete <- function(tree_dt, num_class, tree_index_mat, leaf_index_mat) { # Prepare vector list tree_interpretation <- vector(mode = "list", length = num_class) @@ -196,7 +194,7 @@ single.tree.interprete <- function(tree_dt, # Loop throughout each class for (i in seq_len(num_class)) { - next_interp_dt <- .multiple_tree_interprete( + next_interp_dt <- multiple.tree.interprete( tree_dt = tree_dt , tree_index = tree_index_mat[, i] , leaf_index = leaf_index_mat[, i] diff --git a/R-package/R/lgb.make_serializable.R b/R-package/R/lgb.make_serializable.R index 5a639aacb2b5..58bdd194df4d 100644 --- a/R-package/R/lgb.make_serializable.R +++ b/R-package/R/lgb.make_serializable.R @@ -13,7 +13,7 @@ #' @seealso \link{lgb.restore_handle}, \link{lgb.drop_serialized}. #' @export lgb.make_serializable <- function(model) { - if (!.is_Booster(x = model)) { + if (!lgb.is.Booster(x = model)) { stop("lgb.make_serializable: model should be an ", sQuote("lgb.Booster")) } model$save_raw() diff --git a/R-package/R/lgb.model.dt.tree.R b/R-package/R/lgb.model.dt.tree.R index bf4562e41018..8b0d8d81e2e8 100644 --- a/R-package/R/lgb.model.dt.tree.R +++ b/R-package/R/lgb.model.dt.tree.R @@ -29,8 +29,6 @@ #' #' @examples #' \donttest{ -#' \dontshow{setLGBMthreads(2L)} -#' \dontshow{data.table::setDTthreads(1L)} #' data(agaricus.train, package = "lightgbm") #' train <- agaricus.train #' dtrain <- lgb.Dataset(train$data, label = train$label) @@ -64,10 +62,7 @@ lgb.model.dt.tree <- function(model, num_iteration = NULL) { ) # Parse tree model - tree_list <- lapply( - X = parsed_json_model$tree_info - , FUN = .single_tree_parse - ) + tree_list <- lapply(parsed_json_model$tree_info, single.tree.parse) # Combine into single data.table tree_dt <- data.table::rbindlist(l = tree_list, use.names = TRUE) @@ -89,7 +84,7 @@ lgb.model.dt.tree <- function(model, num_iteration = NULL) { #' @importFrom data.table := data.table rbindlist -.single_tree_parse <- function(lgb_tree) { +single.tree.parse <- function(lgb_tree) { # Traverse tree function pre_order_traversal <- function(env = NULL, tree_node_leaf, current_depth = 0L, parent_index = NA_integer_) { diff --git a/R-package/R/lgb.plot.importance.R b/R-package/R/lgb.plot.importance.R index b8a90ca158ae..fc59ebd0efec 100644 --- a/R-package/R/lgb.plot.importance.R +++ b/R-package/R/lgb.plot.importance.R @@ -19,8 +19,6 @@ #' #' @examples #' \donttest{ -#' \dontshow{setLGBMthreads(2L)} -#' \dontshow{data.table::setDTthreads(1L)} #' data(agaricus.train, package = "lightgbm") #' train <- agaricus.train #' dtrain <- lgb.Dataset(train$data, label = train$label) diff --git a/R-package/R/lgb.plot.interpretation.R b/R-package/R/lgb.plot.interpretation.R index 97650f30a7d3..a88f14bf83f0 100644 --- a/R-package/R/lgb.plot.interpretation.R +++ b/R-package/R/lgb.plot.interpretation.R @@ -16,8 +16,6 @@ #' #' @examples #' \donttest{ -#' \dontshow{setLGBMthreads(2L)} -#' \dontshow{data.table::setDTthreads(1L)} #' Logit <- function(x) { #' log(x / (1.0 - x)) #' } @@ -91,7 +89,7 @@ lgb.plot.interpretation <- function(tree_interpretation_dt, if (num_class == 1L) { # Only one class, plot straight away - .multiple_tree_plot_interpretation( + multiple.tree.plot.interpretation( tree_interpretation = tree_interpretation_dt , top_n = top_n , title = NULL @@ -120,7 +118,7 @@ lgb.plot.interpretation <- function(tree_interpretation_dt, , old = names(plot_dt) , new = c("Feature", "Contribution") ) - .multiple_tree_plot_interpretation( + multiple.tree.plot.interpretation( tree_interpretation = plot_dt , top_n = top_n , title = paste("Class", i - 1L) @@ -133,7 +131,7 @@ lgb.plot.interpretation <- function(tree_interpretation_dt, } #' @importFrom graphics barplot -.multiple_tree_plot_interpretation <- function(tree_interpretation, +multiple.tree.plot.interpretation <- function(tree_interpretation, top_n, title, cex) { diff --git a/R-package/R/lgb.restore_handle.R b/R-package/R/lgb.restore_handle.R index 8a24cc628ca9..4de93d46c96a 100644 --- a/R-package/R/lgb.restore_handle.R +++ b/R-package/R/lgb.restore_handle.R @@ -16,10 +16,7 @@ #' @return \code{lgb.Booster} (the same `model` object that was passed as input, invisibly). #' @seealso \link{lgb.make_serializable}, \link{lgb.drop_serialized}. #' @examples -#' \donttest{ #' library(lightgbm) -#' \dontshow{setLGBMthreads(2L)} -#' \dontshow{data.table::setDTthreads(1L)} #' data("agaricus.train") #' model <- lightgbm( #' agaricus.train$data @@ -36,10 +33,9 @@ #' model_new$check_null_handle() #' lgb.restore_handle(model_new) #' model_new$check_null_handle() -#' } #' @export lgb.restore_handle <- function(model) { - if (!.is_Booster(x = model)) { + if (!lgb.is.Booster(x = model)) { stop("lgb.restore_handle: model should be an ", sQuote("lgb.Booster")) } model$restore_handle() diff --git a/R-package/R/lgb.train.R b/R-package/R/lgb.train.R index 8a299fb6b8ac..20916c9844b5 100644 --- a/R-package/R/lgb.train.R +++ b/R-package/R/lgb.train.R @@ -19,8 +19,6 @@ #' #' @examples #' \donttest{ -#' \dontshow{setLGBMthreads(2L)} -#' \dontshow{data.table::setDTthreads(1L)} #' data(agaricus.train, package = "lightgbm") #' train <- agaricus.train #' dtrain <- lgb.Dataset(train$data, label = train$label) @@ -65,11 +63,11 @@ lgb.train <- function(params = list(), if (nrounds <= 0L) { stop("nrounds should be greater than zero") } - if (!.is_Dataset(x = data)) { + if (!lgb.is.Dataset(x = data)) { stop("lgb.train: data must be an lgb.Dataset instance") } if (length(valids) > 0L) { - if (!identical(class(valids), "list") || !all(vapply(valids, .is_Dataset, logical(1L)))) { + if (!identical(class(valids), "list") || !all(vapply(valids, lgb.is.Dataset, logical(1L)))) { stop("lgb.train: valids must be a list of lgb.Dataset elements") } evnames <- names(valids) @@ -82,27 +80,27 @@ lgb.train <- function(params = list(), # in `params`. # this ensures that the model stored with Booster$save() correctly represents # what was passed in - params <- .check_wrapper_param( + params <- lgb.check.wrapper_param( main_param_name = "verbosity" , params = params , alternative_kwarg_value = verbose ) - params <- .check_wrapper_param( + params <- lgb.check.wrapper_param( main_param_name = "num_iterations" , params = params , alternative_kwarg_value = nrounds ) - params <- .check_wrapper_param( + params <- lgb.check.wrapper_param( main_param_name = "metric" , params = params , alternative_kwarg_value = NULL ) - params <- .check_wrapper_param( + params <- lgb.check.wrapper_param( main_param_name = "objective" , params = params , alternative_kwarg_value = obj ) - params <- .check_wrapper_param( + params <- lgb.check.wrapper_param( main_param_name = "early_stopping_round" , params = params , alternative_kwarg_value = early_stopping_rounds @@ -120,7 +118,7 @@ lgb.train <- function(params = list(), # (for backwards compatibility). If it is a list of functions, store # all of them. This makes it possible to pass any mix of strings like "auc" # and custom functions to eval - params <- .check_eval(params = params, eval = eval) + params <- lgb.check.eval(params = params, eval = eval) eval_functions <- list(NULL) if (is.function(eval)) { eval_functions <- list(eval) @@ -138,7 +136,7 @@ lgb.train <- function(params = list(), # Check for boosting from a trained model if (is.character(init_model)) { predictor <- Predictor$new(modelfile = init_model) - } else if (.is_Booster(x = init_model)) { + } else if (lgb.is.Booster(x = init_model)) { predictor <- init_model$to_predictor() } @@ -168,7 +166,7 @@ lgb.train <- function(params = list(), } else if (!is.null(data$get_colnames())) { cnames <- data$get_colnames() } - params[["interaction_constraints"]] <- .check_interaction_constraints( + params[["interaction_constraints"]] <- lgb.check_interaction_constraints( interaction_constraints = interaction_constraints , column_names = cnames ) @@ -214,18 +212,12 @@ lgb.train <- function(params = list(), # Add printing log callback if (params[["verbosity"]] > 0L && eval_freq > 0L) { - callbacks <- .add_cb( - cb_list = callbacks - , cb = cb_print_evaluation(period = eval_freq) - ) + callbacks <- add.cb(cb_list = callbacks, cb = cb_print_evaluation(period = eval_freq)) } # Add evaluation log callback if (record && length(valids) > 0L) { - callbacks <- .add_cb( - cb_list = callbacks - , cb = cb_record_evaluation() - ) + callbacks <- add.cb(cb_list = callbacks, cb = cb_record_evaluation()) } # Did user pass parameters that indicate they want to use early stopping? @@ -257,7 +249,7 @@ lgb.train <- function(params = list(), # If user supplied early_stopping_rounds, add the early stopping callback if (using_early_stopping) { - callbacks <- .add_cb( + callbacks <- add.cb( cb_list = callbacks , cb = cb_early_stop( stopping_rounds = early_stopping_rounds @@ -267,7 +259,7 @@ lgb.train <- function(params = list(), ) } - cb <- .categorize_callbacks(cb_list = callbacks) + cb <- categorize.callbacks(cb_list = callbacks) # Construct booster with datasets booster <- Booster$new(params = params, train_set = data) diff --git a/R-package/R/lightgbm.R b/R-package/R/lightgbm.R index e5df7a93fc97..711b3ef0dc38 100644 --- a/R-package/R/lightgbm.R +++ b/R-package/R/lightgbm.R @@ -184,21 +184,21 @@ lightgbm <- function(data, } if (is.null(num_threads)) { - num_threads <- .get_default_num_threads() + num_threads <- lgb.get.default.num.threads() } - params <- .check_wrapper_param( + params <- lgb.check.wrapper_param( main_param_name = "num_threads" , params = params , alternative_kwarg_value = num_threads ) - params <- .check_wrapper_param( + params <- lgb.check.wrapper_param( main_param_name = "verbosity" , params = params , alternative_kwarg_value = verbose ) # Process factors as labels and auto-determine objective - if (!.is_Dataset(data)) { + if (!lgb.is.Dataset(data)) { data_processor <- DataProcessor$new() temp <- data_processor$process_label( label = label @@ -220,7 +220,7 @@ lightgbm <- function(data, dtrain <- data # Check whether data is lgb.Dataset, if not then create lgb.Dataset manually - if (!.is_Dataset(x = dtrain)) { + if (!lgb.is.Dataset(x = dtrain)) { dtrain <- lgb.Dataset(data = data, label = label, weight = weights, init_score = init_score) } diff --git a/R-package/R/multithreading.R b/R-package/R/multithreading.R deleted file mode 100644 index a8d6b51a8968..000000000000 --- a/R-package/R/multithreading.R +++ /dev/null @@ -1,51 +0,0 @@ -#' @name setLGBMThreads -#' @title Set maximum number of threads used by LightGBM -#' @description LightGBM attempts to speed up many operations by using multi-threading. -#' The number of threads used in those operations can be controlled via the -#' \code{num_threads} parameter passed through \code{params} to functions like -#' \link{lgb.train} and \link{lgb.Dataset}. However, some operations (like materializing -#' a model from a text file) are done via code paths that don't explicitly accept thread-control -#' configuration. -#' -#' Use this function to set the maximum number of threads LightGBM will use for such operations. -#' -#' This function affects all LightGBM operations in the same process. -#' -#' So, for example, if you call \code{setLGBMthreads(4)}, no other multi-threaded LightGBM -#' operation in the same process will use more than 4 threads. -#' -#' Call \code{setLGBMthreads(-1)} to remove this limitation. -#' @param num_threads maximum number of threads to be used by LightGBM in multi-threaded operations -#' @return NULL -#' @seealso \link{getLGBMthreads} -#' @export -setLGBMthreads <- function(num_threads) { - .Call( - LGBM_SetMaxThreads_R, - num_threads - ) - return(invisible(NULL)) -} - -#' @name getLGBMThreads -#' @title Get default number of threads used by LightGBM -#' @description LightGBM attempts to speed up many operations by using multi-threading. -#' The number of threads used in those operations can be controlled via the -#' \code{num_threads} parameter passed through \code{params} to functions like -#' \link{lgb.train} and \link{lgb.Dataset}. However, some operations (like materializing -#' a model from a text file) are done via code paths that don't explicitly accept thread-control -#' configuration. -#' -#' Use this function to see the default number of threads LightGBM will use for such operations. -#' @return number of threads as an integer. \code{-1} means that in situations where parameter \code{num_threads} is -#' not explicitly supplied, LightGBM will choose a number of threads to use automatically. -#' @seealso \link{setLGBMthreads} -#' @export -getLGBMthreads <- function() { - out <- 0L - .Call( - LGBM_GetMaxThreads_R, - out - ) - return(out) -} diff --git a/R-package/R/readRDS.lgb.Booster.R b/R-package/R/readRDS.lgb.Booster.R new file mode 100644 index 000000000000..a8abac642c24 --- /dev/null +++ b/R-package/R/readRDS.lgb.Booster.R @@ -0,0 +1,48 @@ +#' @name readRDS.lgb.Booster +#' @title readRDS for \code{lgb.Booster} models (DEPRECATED) +#' @description Calls \code{readRDS} in what is expected to be a serialized \code{lgb.Booster} object, +#' and then restores its handle through \code{lgb.restore_handle}. +#' +#' \bold{This function throws a warning and will be removed in future versions.} +#' @param file a connection or the name of the file where the R object is saved to or read from. +#' @param refhook a hook function for handling reference objects. +#' +#' @return \code{lgb.Booster} +#' +#' @examples +#' \donttest{ +#' library(lightgbm) +#' data(agaricus.train, package = "lightgbm") +#' train <- agaricus.train +#' dtrain <- lgb.Dataset(train$data, label = train$label) +#' data(agaricus.test, package = "lightgbm") +#' test <- agaricus.test +#' dtest <- lgb.Dataset.create.valid(dtrain, test$data, label = test$label) +#' params <- list( +#' objective = "regression" +#' , metric = "l2" +#' , min_data = 1L +#' , learning_rate = 1.0 +#' , num_threads = 2L +#' ) +#' valids <- list(test = dtest) +#' model <- lgb.train( +#' params = params +#' , data = dtrain +#' , nrounds = 10L +#' , valids = valids +#' , early_stopping_rounds = 5L +#' ) +#' model_file <- tempfile(fileext = ".rds") +#' saveRDS.lgb.Booster(model, model_file) +#' new_model <- readRDS.lgb.Booster(model_file) +#' } +#' @export +readRDS.lgb.Booster <- function(file, refhook = NULL) { + + warning("'readRDS.lgb.Booster' is deprecated and will be removed in a future release. Use readRDS() instead.") + + object <- readRDS(file = file, refhook = refhook) + lgb.restore_handle(object) + return(object) +} diff --git a/R-package/R/saveRDS.lgb.Booster.R b/R-package/R/saveRDS.lgb.Booster.R new file mode 100644 index 000000000000..5d3af097301f --- /dev/null +++ b/R-package/R/saveRDS.lgb.Booster.R @@ -0,0 +1,78 @@ +#' @name saveRDS.lgb.Booster +#' @title saveRDS for \code{lgb.Booster} models (DEPRECATED) +#' @description Calls \code{saveRDS} on an \code{lgb.Booster} object, making it serializable before the call if +#' it isn't already. +#' +#' \bold{This function throws a warning and will be removed in future versions.} +#' @param object \code{lgb.Booster} object to serialize. +#' @param file a connection or the name of the file where the R object is saved to or read from. +#' @param ascii a logical. If TRUE or NA, an ASCII representation is written; otherwise (default), +#' a binary one is used. See the comments in the help for save. +#' @param version the workspace format version to use. \code{NULL} specifies the current default +#' version (2). Versions prior to 2 are not supported, so this will only be relevant +#' when there are later versions. +#' @param compress a logical specifying whether saving to a named file is to use "gzip" compression, +#' or one of \code{"gzip"}, \code{"bzip2"} or \code{"xz"} to indicate the type of +#' compression to be used. Ignored if file is a connection. +#' @param refhook a hook function for handling reference objects. +#' @param raw whether to save the model in a raw variable or not, recommended to leave it to \code{TRUE}. +#' +#' @return NULL invisibly. +#' +#' @examples +#' \donttest{ +#' library(lightgbm) +#' data(agaricus.train, package = "lightgbm") +#' train <- agaricus.train +#' dtrain <- lgb.Dataset(train$data, label = train$label) +#' data(agaricus.test, package = "lightgbm") +#' test <- agaricus.test +#' dtest <- lgb.Dataset.create.valid(dtrain, test$data, label = test$label) +#' params <- list( +#' objective = "regression" +#' , metric = "l2" +#' , min_data = 1L +#' , learning_rate = 1.0 +#' , num_threads = 2L +#' ) +#' valids <- list(test = dtest) +#' model <- lgb.train( +#' params = params +#' , data = dtrain +#' , nrounds = 10L +#' , valids = valids +#' , early_stopping_rounds = 5L +#' ) +#' model_file <- tempfile(fileext = ".rds") +#' saveRDS.lgb.Booster(model, model_file) +#' } +#' @export +saveRDS.lgb.Booster <- function(object, + file, + ascii = FALSE, + version = NULL, + compress = TRUE, + refhook = NULL, + raw = TRUE) { + + warning("'saveRDS.lgb.Booster' is deprecated and will be removed in a future release. Use saveRDS() instead.") + + if (!lgb.is.Booster(x = object)) { + stop("saveRDS.lgb.Booster: object should be an ", sQuote("lgb.Booster")) + } + + if (is.null(object$raw)) { + lgb.make_serializable(object) + } + + saveRDS( + object + , file = file + , ascii = ascii + , version = version + , compress = compress + , refhook = refhook + ) + + return(invisible(NULL)) +} diff --git a/R-package/R/utils.R b/R-package/R/utils.R index 1ac6f197ca77..c9ba780316df 100644 --- a/R-package/R/utils.R +++ b/R-package/R/utils.R @@ -1,16 +1,16 @@ -.is_Booster <- function(x) { +lgb.is.Booster <- function(x) { return(all(c("R6", "lgb.Booster") %in% class(x))) # nolint: class_equals } -.is_Dataset <- function(x) { +lgb.is.Dataset <- function(x) { return(all(c("R6", "lgb.Dataset") %in% class(x))) # nolint: class_equals } -.is_Predictor <- function(x) { +lgb.is.Predictor <- function(x) { return(all(c("R6", "lgb.Predictor") %in% class(x))) # nolint: class_equals } -.is_null_handle <- function(x) { +lgb.is.null.handle <- function(x) { if (is.null(x)) { return(TRUE) } @@ -19,7 +19,7 @@ ) } -.params2str <- function(params) { +lgb.params2str <- function(params) { if (!identical(class(params), "list")) { stop("params must be a list") @@ -59,7 +59,7 @@ } -.check_interaction_constraints <- function(interaction_constraints, column_names) { +lgb.check_interaction_constraints <- function(interaction_constraints, column_names) { # Convert interaction constraints to feature numbers string_constraints <- list() @@ -129,7 +129,7 @@ # This has to account for the fact that `eval` could be a character vector, # a function, a list of functions, or a list with a mix of strings and # functions -.check_eval <- function(params, eval) { +lgb.check.eval <- function(params, eval) { if (is.null(params$metric)) { params$metric <- list() @@ -194,7 +194,7 @@ # [return] # params with num_iterations set to the chosen value, and other aliases # of num_iterations removed -.check_wrapper_param <- function(main_param_name, params, alternative_kwarg_value) { +lgb.check.wrapper_param <- function(main_param_name, params, alternative_kwarg_value) { aliases <- .PARAMETER_ALIASES()[[main_param_name]] aliases_provided <- aliases[aliases %in% names(params)] @@ -225,7 +225,7 @@ } #' @importFrom parallel detectCores -.get_default_num_threads <- function() { +lgb.get.default.num.threads <- function() { if (requireNamespace("RhpcBLASctl", quietly = TRUE)) { # nolint: undesirable_function return(RhpcBLASctl::get_num_cores()) } else { @@ -247,7 +247,7 @@ } } -.equal_or_both_null <- function(a, b) { +lgb.equal.or.both.null <- function(a, b) { if (is.null(a)) { if (!is.null(b)) { return(FALSE) diff --git a/R-package/README.md b/R-package/README.md index a9dc65b5d7b3..69f6dba67053 100644 --- a/R-package/README.md +++ b/R-package/README.md @@ -97,10 +97,9 @@ After installing `Rtools` and `CMake`, be sure the following paths are added to - If you have `Rtools` 4.0, example: - `C:\rtools40\mingw64\bin` - `C:\rtools40\usr\bin` - - If you have `Rtools` 4.2+, example: + - If you have `Rtools` 4.2, example: - `C:\rtools42\x86_64-w64-mingw32.static.posix\bin` - `C:\rtools42\usr\bin` - - **NOTE**: this is e.g. `rtools43\` for R 4.3 * `CMake` - example: `C:\Program Files\CMake\bin` * `R` @@ -108,7 +107,7 @@ After installing `Rtools` and `CMake`, be sure the following paths are added to NOTE: Two `Rtools` paths are required from `Rtools` 4.0 onwards because paths and the list of included software was changed in `Rtools` 4.0. -NOTE: `Rtools42` and later take a very different approach to the compiler toolchain than previous releases, and how you install it changes what is required to build packages. See ["Howto: Building R 4.2 and packages on Windows"](https://cran.r-project.org/bin/windows/base/howto-R-4.2.html). +NOTE: `Rtools42` takes a very different approach to the compiler toolchain than previous releases, and how you install it changes what is required to build packages. See ["Howto: Building R 4.2 and packages on Windows"](https://cran.r-project.org/bin/windows/base/howto-R-4.2.html). #### Windows Toolchain Options @@ -428,7 +427,7 @@ docker run \ # install dependencies RDscript${R_CUSTOMIZATION} \ - -e "install.packages(c('R6', 'data.table', 'jsonlite', 'knitr', 'markdown', 'Matrix', 'RhpcBLASctl', 'testthat'), repos = 'https://cran.r-project.org', Ncpus = parallel::detectCores())" + -e "install.packages(c('R6', 'data.table', 'jsonlite', 'knitr', 'Matrix', 'RhpcBLASctl', 'rmarkdown', 'testthat'), repos = 'https://cran.r-project.org', Ncpus = parallel::detectCores())" # install lightgbm sh build-cran-package.sh --r-executable=RD${R_CUSTOMIZATION} @@ -459,7 +458,7 @@ docker run \ -it \ wch1/r-debug -RDscriptvalgrind -e "install.packages(c('R6', 'data.table', 'jsonlite', 'knitr', 'markdown', 'Matrix', 'RhpcBLASctl', 'testthat'), repos = 'https://cran.rstudio.com', Ncpus = parallel::detectCores())" +RDscriptvalgrind -e "install.packages(c('R6', 'data.table', 'jsonlite', 'knitr', 'Matrix', 'RhpcBLASctl', 'rmarkdown', 'testthat'), repos = 'https://cran.rstudio.com', Ncpus = parallel::detectCores())" sh build-cran-package.sh \ --r-executable=RDvalgrind diff --git a/R-package/configure b/R-package/configure index 37dfdbfbf6c7..39a18d669833 100755 --- a/R-package/configure +++ b/R-package/configure @@ -1,6 +1,6 @@ #! /bin/sh # Guess values for system-dependent variables and create Makefiles. -# Generated by GNU Autoconf 2.71 for lightgbm 4.2.0.99. +# Generated by GNU Autoconf 2.71 for lightgbm 4.1.0.99. # # # Copyright (C) 1992-1996, 1998-2017, 2020-2021 Free Software Foundation, @@ -607,8 +607,8 @@ MAKEFLAGS= # Identity of this package. PACKAGE_NAME='lightgbm' PACKAGE_TARNAME='lightgbm' -PACKAGE_VERSION='4.2.0.99' -PACKAGE_STRING='lightgbm 4.2.0.99' +PACKAGE_VERSION='4.1.0.99' +PACKAGE_STRING='lightgbm 4.1.0.99' PACKAGE_BUGREPORT='' PACKAGE_URL='' @@ -1211,7 +1211,7 @@ if test "$ac_init_help" = "long"; then # Omit some internal or obsolete options to make the list less imposing. # This message is too long to be a string in the A/UX 3.1 sh. cat <<_ACEOF -\`configure' configures lightgbm 4.2.0.99 to adapt to many kinds of systems. +\`configure' configures lightgbm 4.1.0.99 to adapt to many kinds of systems. Usage: $0 [OPTION]... [VAR=VALUE]... @@ -1273,7 +1273,7 @@ fi if test -n "$ac_init_help"; then case $ac_init_help in - short | recursive ) echo "Configuration of lightgbm 4.2.0.99:";; + short | recursive ) echo "Configuration of lightgbm 4.1.0.99:";; esac cat <<\_ACEOF @@ -1341,7 +1341,7 @@ fi test -n "$ac_init_help" && exit $ac_status if $ac_init_version; then cat <<\_ACEOF -lightgbm configure 4.2.0.99 +lightgbm configure 4.1.0.99 generated by GNU Autoconf 2.71 Copyright (C) 2021 Free Software Foundation, Inc. @@ -1378,7 +1378,7 @@ cat >config.log <<_ACEOF This file contains any messages produced by compilers while running configure, to aid debugging if configure makes a mistake. -It was created by lightgbm $as_me 4.2.0.99, which was +It was created by lightgbm $as_me 4.1.0.99, which was generated by GNU Autoconf 2.71. Invocation command line was $ $0$ac_configure_args_raw @@ -2454,7 +2454,7 @@ cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 # report actual input values of CONFIG_FILES etc. instead of their # values after options handling. ac_log=" -This file was extended by lightgbm $as_me 4.2.0.99, which was +This file was extended by lightgbm $as_me 4.1.0.99, which was generated by GNU Autoconf 2.71. Invocation command line was CONFIG_FILES = $CONFIG_FILES @@ -2509,7 +2509,7 @@ ac_cs_config_escaped=`printf "%s\n" "$ac_cs_config" | sed "s/^ //; s/'/'\\\\\\\\ cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 ac_cs_config='$ac_cs_config_escaped' ac_cs_version="\\ -lightgbm config.status 4.2.0.99 +lightgbm config.status 4.1.0.99 configured by $0, generated by GNU Autoconf 2.71, with options \\"\$ac_cs_config\\" diff --git a/R-package/cran-comments.md b/R-package/cran-comments.md index 80f84b924a48..44b8ed391bfc 100644 --- a/R-package/cran-comments.md +++ b/R-package/cran-comments.md @@ -1,16 +1,5 @@ # CRAN Submission History -## v4.2.0 - Submission 1 - (December 7, 2023) - -### CRAN response - -Accepted to CRAN - -### Maintainer Notes - -This submission included many changes from the last 2 years, as well as fixes for a warning -CRAN said could cause the package to be archived: https://github.com/microsoft/LightGBM/issues/6221. - ## v4.1.0 - not submitted v4.1.0 was not submitted to CRAN, because https://github.com/microsoft/LightGBM/issues/5987 had not been resolved. diff --git a/R-package/demo/basic_walkthrough.R b/R-package/demo/basic_walkthrough.R index c9ac484e68cb..b5bfed26d935 100644 --- a/R-package/demo/basic_walkthrough.R +++ b/R-package/demo/basic_walkthrough.R @@ -1,4 +1,5 @@ library(lightgbm) +library(methods) # We load in the agaricus dataset # In this example, we are aiming to predict whether a mushroom is edible diff --git a/R-package/demo/boost_from_prediction.R b/R-package/demo/boost_from_prediction.R index 68d33a73cac0..5ee04f2d756e 100644 --- a/R-package/demo/boost_from_prediction.R +++ b/R-package/demo/boost_from_prediction.R @@ -1,4 +1,5 @@ library(lightgbm) +library(methods) # Load in the agaricus dataset data(agaricus.train, package = "lightgbm") diff --git a/R-package/demo/early_stopping.R b/R-package/demo/early_stopping.R index 6ca214c5ac7b..5179195f02ad 100644 --- a/R-package/demo/early_stopping.R +++ b/R-package/demo/early_stopping.R @@ -1,4 +1,5 @@ library(lightgbm) +library(methods) # Load in the agaricus dataset data(agaricus.train, package = "lightgbm") diff --git a/R-package/man/dim.Rd b/R-package/man/dim.Rd index 69332d0ec397..94ca192d8291 100644 --- a/R-package/man/dim.Rd +++ b/R-package/man/dim.Rd @@ -21,8 +21,6 @@ be directly used with an \code{lgb.Dataset} object. } \examples{ \donttest{ -\dontshow{setLGBMthreads(2L)} -\dontshow{data.table::setDTthreads(1L)} data(agaricus.train, package = "lightgbm") train <- agaricus.train dtrain <- lgb.Dataset(train$data, label = train$label) diff --git a/R-package/man/dimnames.lgb.Dataset.Rd b/R-package/man/dimnames.lgb.Dataset.Rd index 85f2085f1d77..ec01a04f607b 100644 --- a/R-package/man/dimnames.lgb.Dataset.Rd +++ b/R-package/man/dimnames.lgb.Dataset.Rd @@ -28,8 +28,6 @@ Since row names are irrelevant, it is recommended to use \code{colnames} directl } \examples{ \donttest{ -\dontshow{setLGBMthreads(2L)} -\dontshow{data.table::setDTthreads(1L)} data(agaricus.train, package = "lightgbm") train <- agaricus.train dtrain <- lgb.Dataset(train$data, label = train$label) diff --git a/R-package/man/getLGBMThreads.Rd b/R-package/man/getLGBMThreads.Rd deleted file mode 100644 index 21af4f4849d4..000000000000 --- a/R-package/man/getLGBMThreads.Rd +++ /dev/null @@ -1,26 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/multithreading.R -\name{getLGBMThreads} -\alias{getLGBMThreads} -\alias{getLGBMthreads} -\title{Get default number of threads used by LightGBM} -\usage{ -getLGBMthreads() -} -\value{ -number of threads as an integer. \code{-1} means that in situations where parameter \code{num_threads} is - not explicitly supplied, LightGBM will choose a number of threads to use automatically. -} -\description{ -LightGBM attempts to speed up many operations by using multi-threading. - The number of threads used in those operations can be controlled via the - \code{num_threads} parameter passed through \code{params} to functions like - \link{lgb.train} and \link{lgb.Dataset}. However, some operations (like materializing - a model from a text file) are done via code paths that don't explicitly accept thread-control - configuration. - - Use this function to see the default number of threads LightGBM will use for such operations. -} -\seealso{ -\link{setLGBMthreads} -} diff --git a/R-package/man/get_field.Rd b/R-package/man/get_field.Rd index e2562cc21364..1b6692fcf807 100644 --- a/R-package/man/get_field.Rd +++ b/R-package/man/get_field.Rd @@ -32,8 +32,6 @@ Get one attribute of a \code{lgb.Dataset} } \examples{ \donttest{ -\dontshow{setLGBMthreads(2L)} -\dontshow{data.table::setDTthreads(1L)} data(agaricus.train, package = "lightgbm") train <- agaricus.train dtrain <- lgb.Dataset(train$data, label = train$label) diff --git a/R-package/man/lgb.Dataset.Rd b/R-package/man/lgb.Dataset.Rd index 2605657b060a..4895600ff922 100644 --- a/R-package/man/lgb.Dataset.Rd +++ b/R-package/man/lgb.Dataset.Rd @@ -65,8 +65,6 @@ Construct \code{lgb.Dataset} object from dense matrix, sparse matrix } \examples{ \donttest{ -\dontshow{setLGBMthreads(2L)} -\dontshow{data.table::setDTthreads(1L)} data(agaricus.train, package = "lightgbm") train <- agaricus.train dtrain <- lgb.Dataset(train$data, label = train$label) diff --git a/R-package/man/lgb.Dataset.construct.Rd b/R-package/man/lgb.Dataset.construct.Rd index e400e0a5f8d5..97c9e7887602 100644 --- a/R-package/man/lgb.Dataset.construct.Rd +++ b/R-package/man/lgb.Dataset.construct.Rd @@ -17,8 +17,6 @@ Construct Dataset explicitly } \examples{ \donttest{ -\dontshow{setLGBMthreads(2L)} -\dontshow{data.table::setDTthreads(1L)} data(agaricus.train, package = "lightgbm") train <- agaricus.train dtrain <- lgb.Dataset(train$data, label = train$label) diff --git a/R-package/man/lgb.Dataset.create.valid.Rd b/R-package/man/lgb.Dataset.create.valid.Rd index fc50dff19986..ab8ca753c2b9 100644 --- a/R-package/man/lgb.Dataset.create.valid.Rd +++ b/R-package/man/lgb.Dataset.create.valid.Rd @@ -48,8 +48,6 @@ Construct validation data according to training data } \examples{ \donttest{ -\dontshow{setLGBMthreads(2L)} -\dontshow{data.table::setDTthreads(1L)} data(agaricus.train, package = "lightgbm") train <- agaricus.train dtrain <- lgb.Dataset(train$data, label = train$label) diff --git a/R-package/man/lgb.Dataset.save.Rd b/R-package/man/lgb.Dataset.save.Rd index b03c2c5e0ac5..5ea38227ba66 100644 --- a/R-package/man/lgb.Dataset.save.Rd +++ b/R-package/man/lgb.Dataset.save.Rd @@ -20,8 +20,6 @@ Please note that \code{init_score} is not saved in binary file. } \examples{ \donttest{ -\dontshow{setLGBMthreads(2L)} -\dontshow{data.table::setDTthreads(1L)} data(agaricus.train, package = "lightgbm") train <- agaricus.train dtrain <- lgb.Dataset(train$data, label = train$label) diff --git a/R-package/man/lgb.Dataset.set.categorical.Rd b/R-package/man/lgb.Dataset.set.categorical.Rd index 5dfcc9a771e8..26eb10770e47 100644 --- a/R-package/man/lgb.Dataset.set.categorical.Rd +++ b/R-package/man/lgb.Dataset.set.categorical.Rd @@ -22,8 +22,6 @@ Set the categorical features of an \code{lgb.Dataset} object. Use this function } \examples{ \donttest{ -\dontshow{setLGBMthreads(2L)} -\dontshow{data.table::setDTthreads(1L)} data(agaricus.train, package = "lightgbm") train <- agaricus.train dtrain <- lgb.Dataset(train$data, label = train$label) diff --git a/R-package/man/lgb.Dataset.set.reference.Rd b/R-package/man/lgb.Dataset.set.reference.Rd index a4efbfac5962..349b0b22913e 100644 --- a/R-package/man/lgb.Dataset.set.reference.Rd +++ b/R-package/man/lgb.Dataset.set.reference.Rd @@ -19,8 +19,6 @@ If you want to use validation data, you should set reference to training data } \examples{ \donttest{ -\dontshow{setLGBMthreads(2L)} -\dontshow{data.table::setDTthreads(1L)} # create training Dataset data(agaricus.train, package ="lightgbm") train <- agaricus.train diff --git a/R-package/man/lgb.configure_fast_predict.Rd b/R-package/man/lgb.configure_fast_predict.Rd index e02600451df5..39fe6afa6b18 100644 --- a/R-package/man/lgb.configure_fast_predict.Rd +++ b/R-package/man/lgb.configure_fast_predict.Rd @@ -114,8 +114,6 @@ Calling this function multiple times with different parameters might not overrid } \examples{ \donttest{ -\dontshow{setLGBMthreads(2L)} -\dontshow{data.table::setDTthreads(1L)} library(lightgbm) data(mtcars) X <- as.matrix(mtcars[, -1L]) diff --git a/R-package/man/lgb.cv.Rd b/R-package/man/lgb.cv.Rd index 7ea2928c6166..555cb11c7bb3 100644 --- a/R-package/man/lgb.cv.Rd +++ b/R-package/man/lgb.cv.Rd @@ -152,8 +152,6 @@ Cross validation logic used by LightGBM \examples{ \donttest{ -\dontshow{setLGBMthreads(2L)} -\dontshow{data.table::setDTthreads(1L)} data(agaricus.train, package = "lightgbm") train <- agaricus.train dtrain <- lgb.Dataset(train$data, label = train$label) diff --git a/R-package/man/lgb.dump.Rd b/R-package/man/lgb.dump.Rd index 39f0e3018ac7..f4e90242fd75 100644 --- a/R-package/man/lgb.dump.Rd +++ b/R-package/man/lgb.dump.Rd @@ -20,8 +20,6 @@ Dump LightGBM model to json \examples{ \donttest{ library(lightgbm) -\dontshow{setLGBMthreads(2L)} -\dontshow{data.table::setDTthreads(1L)} data(agaricus.train, package = "lightgbm") train <- agaricus.train dtrain <- lgb.Dataset(train$data, label = train$label) diff --git a/R-package/man/lgb.get.eval.result.Rd b/R-package/man/lgb.get.eval.result.Rd index 0dc7eb0845c3..9c2293a0f909 100644 --- a/R-package/man/lgb.get.eval.result.Rd +++ b/R-package/man/lgb.get.eval.result.Rd @@ -33,8 +33,6 @@ Given a \code{lgb.Booster}, return evaluation results for a } \examples{ \donttest{ -\dontshow{setLGBMthreads(2L)} -\dontshow{data.table::setDTthreads(1L)} # train a regression model data(agaricus.train, package = "lightgbm") train <- agaricus.train diff --git a/R-package/man/lgb.importance.Rd b/R-package/man/lgb.importance.Rd index 79cb82f5d8ef..89a3d4e6b5b7 100644 --- a/R-package/man/lgb.importance.Rd +++ b/R-package/man/lgb.importance.Rd @@ -25,8 +25,6 @@ Creates a \code{data.table} of feature importances in a model. } \examples{ \donttest{ -\dontshow{setLGBMthreads(2L)} -\dontshow{data.table::setDTthreads(1L)} data(agaricus.train, package = "lightgbm") train <- agaricus.train dtrain <- lgb.Dataset(train$data, label = train$label) diff --git a/R-package/man/lgb.interprete.Rd b/R-package/man/lgb.interprete.Rd index 3acc27955c46..c1166b2c1cc9 100644 --- a/R-package/man/lgb.interprete.Rd +++ b/R-package/man/lgb.interprete.Rd @@ -30,8 +30,6 @@ Computes feature contribution components of rawscore prediction. } \examples{ \donttest{ -\dontshow{setLGBMthreads(2L)} -\dontshow{data.table::setDTthreads(1L)} Logit <- function(x) log(x / (1.0 - x)) data(agaricus.train, package = "lightgbm") train <- agaricus.train diff --git a/R-package/man/lgb.load.Rd b/R-package/man/lgb.load.Rd index f145db5a245e..c1a00a20974b 100644 --- a/R-package/man/lgb.load.Rd +++ b/R-package/man/lgb.load.Rd @@ -20,8 +20,6 @@ Load LightGBM takes in either a file path or model string. } \examples{ \donttest{ -\dontshow{setLGBMthreads(2L)} -\dontshow{data.table::setDTthreads(1L)} data(agaricus.train, package = "lightgbm") train <- agaricus.train dtrain <- lgb.Dataset(train$data, label = train$label) diff --git a/R-package/man/lgb.model.dt.tree.Rd b/R-package/man/lgb.model.dt.tree.Rd index 60ef8cdac133..4d02ede9a001 100644 --- a/R-package/man/lgb.model.dt.tree.Rd +++ b/R-package/man/lgb.model.dt.tree.Rd @@ -40,8 +40,6 @@ Parse a LightGBM model json dump into a \code{data.table} structure. } \examples{ \donttest{ -\dontshow{setLGBMthreads(2L)} -\dontshow{data.table::setDTthreads(1L)} data(agaricus.train, package = "lightgbm") train <- agaricus.train dtrain <- lgb.Dataset(train$data, label = train$label) diff --git a/R-package/man/lgb.plot.importance.Rd b/R-package/man/lgb.plot.importance.Rd index bdf354da0385..302f46460e3f 100644 --- a/R-package/man/lgb.plot.importance.Rd +++ b/R-package/man/lgb.plot.importance.Rd @@ -38,8 +38,6 @@ Features are shown ranked in a decreasing importance order. } \examples{ \donttest{ -\dontshow{setLGBMthreads(2L)} -\dontshow{data.table::setDTthreads(1L)} data(agaricus.train, package = "lightgbm") train <- agaricus.train dtrain <- lgb.Dataset(train$data, label = train$label) diff --git a/R-package/man/lgb.plot.interpretation.Rd b/R-package/man/lgb.plot.interpretation.Rd index 6f168e120a4e..a914071e896f 100644 --- a/R-package/man/lgb.plot.interpretation.Rd +++ b/R-package/man/lgb.plot.interpretation.Rd @@ -35,8 +35,6 @@ contribution of a feature. Features are shown ranked in a decreasing contributio } \examples{ \donttest{ -\dontshow{setLGBMthreads(2L)} -\dontshow{data.table::setDTthreads(1L)} Logit <- function(x) { log(x / (1.0 - x)) } diff --git a/R-package/man/lgb.restore_handle.Rd b/R-package/man/lgb.restore_handle.Rd index 37922c077642..95cbdc64485d 100644 --- a/R-package/man/lgb.restore_handle.Rd +++ b/R-package/man/lgb.restore_handle.Rd @@ -27,10 +27,7 @@ function. If you wish to make fast single-row predictions using a \code{lgb.Boos call \link{lgb.configure_fast_predict} on the loaded \code{lgb.Booster} object. } \examples{ -\donttest{ library(lightgbm) -\dontshow{setLGBMthreads(2L)} -\dontshow{data.table::setDTthreads(1L)} data("agaricus.train") model <- lightgbm( agaricus.train$data @@ -48,7 +45,6 @@ model_new$check_null_handle() lgb.restore_handle(model_new) model_new$check_null_handle() } -} \seealso{ \link{lgb.make_serializable}, \link{lgb.drop_serialized}. } diff --git a/R-package/man/lgb.save.Rd b/R-package/man/lgb.save.Rd index 62ec0ed462f6..efd110c7d816 100644 --- a/R-package/man/lgb.save.Rd +++ b/R-package/man/lgb.save.Rd @@ -21,8 +21,6 @@ Save LightGBM model } \examples{ \donttest{ -\dontshow{setLGBMthreads(2L)} -\dontshow{data.table::setDTthreads(1L)} library(lightgbm) data(agaricus.train, package = "lightgbm") train <- agaricus.train diff --git a/R-package/man/lgb.train.Rd b/R-package/man/lgb.train.Rd index 557c85b7f9dc..0f2961edc415 100644 --- a/R-package/man/lgb.train.Rd +++ b/R-package/man/lgb.train.Rd @@ -130,8 +130,6 @@ Low-level R interface to train a LightGBM model. Unlike \code{\link{lightgbm}}, \examples{ \donttest{ -\dontshow{setLGBMthreads(2L)} -\dontshow{data.table::setDTthreads(1L)} data(agaricus.train, package = "lightgbm") train <- agaricus.train dtrain <- lgb.Dataset(train$data, label = train$label) diff --git a/R-package/man/predict.lgb.Booster.Rd b/R-package/man/predict.lgb.Booster.Rd index bcb2f3f980fb..2df13b9bc374 100644 --- a/R-package/man/predict.lgb.Booster.Rd +++ b/R-package/man/predict.lgb.Booster.Rd @@ -121,8 +121,6 @@ If the model object has been configured for fast single-row predictions through } \examples{ \donttest{ -\dontshow{setLGBMthreads(2L)} -\dontshow{data.table::setDTthreads(1L)} data(agaricus.train, package = "lightgbm") train <- agaricus.train dtrain <- lgb.Dataset(train$data, label = train$label) diff --git a/R-package/man/readRDS.lgb.Booster.Rd b/R-package/man/readRDS.lgb.Booster.Rd new file mode 100644 index 000000000000..6a8e4c80ca91 --- /dev/null +++ b/R-package/man/readRDS.lgb.Booster.Rd @@ -0,0 +1,51 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/readRDS.lgb.Booster.R +\name{readRDS.lgb.Booster} +\alias{readRDS.lgb.Booster} +\title{readRDS for \code{lgb.Booster} models (DEPRECATED)} +\usage{ +readRDS.lgb.Booster(file, refhook = NULL) +} +\arguments{ +\item{file}{a connection or the name of the file where the R object is saved to or read from.} + +\item{refhook}{a hook function for handling reference objects.} +} +\value{ +\code{lgb.Booster} +} +\description{ +Calls \code{readRDS} in what is expected to be a serialized \code{lgb.Booster} object, + and then restores its handle through \code{lgb.restore_handle}. + + \bold{This function throws a warning and will be removed in future versions.} +} +\examples{ +\donttest{ +library(lightgbm) +data(agaricus.train, package = "lightgbm") +train <- agaricus.train +dtrain <- lgb.Dataset(train$data, label = train$label) +data(agaricus.test, package = "lightgbm") +test <- agaricus.test +dtest <- lgb.Dataset.create.valid(dtrain, test$data, label = test$label) +params <- list( + objective = "regression" + , metric = "l2" + , min_data = 1L + , learning_rate = 1.0 + , num_threads = 2L +) +valids <- list(test = dtest) +model <- lgb.train( + params = params + , data = dtrain + , nrounds = 10L + , valids = valids + , early_stopping_rounds = 5L +) +model_file <- tempfile(fileext = ".rds") +saveRDS.lgb.Booster(model, model_file) +new_model <- readRDS.lgb.Booster(model_file) +} +} diff --git a/R-package/man/saveRDS.lgb.Booster.Rd b/R-package/man/saveRDS.lgb.Booster.Rd new file mode 100644 index 000000000000..a8664243dce2 --- /dev/null +++ b/R-package/man/saveRDS.lgb.Booster.Rd @@ -0,0 +1,73 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/saveRDS.lgb.Booster.R +\name{saveRDS.lgb.Booster} +\alias{saveRDS.lgb.Booster} +\title{saveRDS for \code{lgb.Booster} models (DEPRECATED)} +\usage{ +saveRDS.lgb.Booster( + object, + file, + ascii = FALSE, + version = NULL, + compress = TRUE, + refhook = NULL, + raw = TRUE +) +} +\arguments{ +\item{object}{\code{lgb.Booster} object to serialize.} + +\item{file}{a connection or the name of the file where the R object is saved to or read from.} + +\item{ascii}{a logical. If TRUE or NA, an ASCII representation is written; otherwise (default), +a binary one is used. See the comments in the help for save.} + +\item{version}{the workspace format version to use. \code{NULL} specifies the current default +version (2). Versions prior to 2 are not supported, so this will only be relevant +when there are later versions.} + +\item{compress}{a logical specifying whether saving to a named file is to use "gzip" compression, +or one of \code{"gzip"}, \code{"bzip2"} or \code{"xz"} to indicate the type of +compression to be used. Ignored if file is a connection.} + +\item{refhook}{a hook function for handling reference objects.} + +\item{raw}{whether to save the model in a raw variable or not, recommended to leave it to \code{TRUE}.} +} +\value{ +NULL invisibly. +} +\description{ +Calls \code{saveRDS} on an \code{lgb.Booster} object, making it serializable before the call if + it isn't already. + + \bold{This function throws a warning and will be removed in future versions.} +} +\examples{ +\donttest{ +library(lightgbm) +data(agaricus.train, package = "lightgbm") +train <- agaricus.train +dtrain <- lgb.Dataset(train$data, label = train$label) +data(agaricus.test, package = "lightgbm") +test <- agaricus.test +dtest <- lgb.Dataset.create.valid(dtrain, test$data, label = test$label) +params <- list( + objective = "regression" + , metric = "l2" + , min_data = 1L + , learning_rate = 1.0 + , num_threads = 2L +) +valids <- list(test = dtest) +model <- lgb.train( + params = params + , data = dtrain + , nrounds = 10L + , valids = valids + , early_stopping_rounds = 5L +) +model_file <- tempfile(fileext = ".rds") +saveRDS.lgb.Booster(model, model_file) +} +} diff --git a/R-package/man/setLGBMThreads.Rd b/R-package/man/setLGBMThreads.Rd deleted file mode 100644 index 53336fc2548e..000000000000 --- a/R-package/man/setLGBMThreads.Rd +++ /dev/null @@ -1,32 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/multithreading.R -\name{setLGBMThreads} -\alias{setLGBMThreads} -\alias{setLGBMthreads} -\title{Set maximum number of threads used by LightGBM} -\usage{ -setLGBMthreads(num_threads) -} -\arguments{ -\item{num_threads}{maximum number of threads to be used by LightGBM in multi-threaded operations} -} -\description{ -LightGBM attempts to speed up many operations by using multi-threading. - The number of threads used in those operations can be controlled via the - \code{num_threads} parameter passed through \code{params} to functions like - \link{lgb.train} and \link{lgb.Dataset}. However, some operations (like materializing - a model from a text file) are done via code paths that don't explicitly accept thread-control - configuration. - - Use this function to set the maximum number of threads LightGBM will use for such operations. - - This function affects all LightGBM operations in the same process. - - So, for example, if you call \code{setLGBMthreads(4)}, no other multi-threaded LightGBM - operation in the same process will use more than 4 threads. - - Call \code{setLGBMthreads(-1)} to remove this limitation. -} -\seealso{ -\link{getLGBMthreads} -} diff --git a/R-package/man/set_field.Rd b/R-package/man/set_field.Rd index 2ceebfb87753..f9901e27eefd 100644 --- a/R-package/man/set_field.Rd +++ b/R-package/man/set_field.Rd @@ -34,8 +34,6 @@ Set one attribute of a \code{lgb.Dataset} } \examples{ \donttest{ -\dontshow{setLGBMthreads(2L)} -\dontshow{data.table::setDTthreads(1L)} data(agaricus.train, package = "lightgbm") train <- agaricus.train dtrain <- lgb.Dataset(train$data, label = train$label) diff --git a/R-package/man/slice.Rd b/R-package/man/slice.Rd index a65809a239d8..1d7bec08de0f 100644 --- a/R-package/man/slice.Rd +++ b/R-package/man/slice.Rd @@ -23,8 +23,6 @@ Get a new \code{lgb.Dataset} containing the specified rows of } \examples{ \donttest{ -\dontshow{setLGBMthreads(2L)} -\dontshow{data.table::setDTthreads(1L)} data(agaricus.train, package = "lightgbm") train <- agaricus.train dtrain <- lgb.Dataset(train$data, label = train$label) diff --git a/R-package/pkgdown/_pkgdown.yml b/R-package/pkgdown/_pkgdown.yml index 99a3b1010d41..233a31f0ead9 100644 --- a/R-package/pkgdown/_pkgdown.yml +++ b/R-package/pkgdown/_pkgdown.yml @@ -85,6 +85,8 @@ reference: - '`lgb.save`' - '`lgb.load`' - '`lgb.model.dt.tree`' + - '`saveRDS.lgb.Booster`' + - '`readRDS.lgb.Booster`' - title: Model Interpretation desc: Analyze your models contents: diff --git a/R-package/src/Makevars.in b/R-package/src/Makevars.in index c04263f62c1c..ba9ef054bfab 100644 --- a/R-package/src/Makevars.in +++ b/R-package/src/Makevars.in @@ -53,6 +53,5 @@ OBJECTS = \ treelearner/serial_tree_learner.o \ treelearner/tree_learner.o \ treelearner/voting_parallel_tree_learner.o \ - utils/openmp_wrapper.o \ c_api.o \ lightgbm_R.o diff --git a/R-package/src/Makevars.win.in b/R-package/src/Makevars.win.in index 86d56fecdf34..14f5afde002f 100644 --- a/R-package/src/Makevars.win.in +++ b/R-package/src/Makevars.win.in @@ -54,6 +54,5 @@ OBJECTS = \ treelearner/serial_tree_learner.o \ treelearner/tree_learner.o \ treelearner/voting_parallel_tree_learner.o \ - utils/openmp_wrapper.o \ c_api.o \ lightgbm_R.o diff --git a/R-package/src/lightgbm_R.cpp b/R-package/src/lightgbm_R.cpp index a76a56c06b24..82956daef4b9 100644 --- a/R-package/src/lightgbm_R.cpp +++ b/R-package/src/lightgbm_R.cpp @@ -40,7 +40,7 @@ void LGBM_R_save_exception_msg(const std::string &err); catch(std::exception& ex) { LGBM_R_save_exception_msg(ex); } \ catch(std::string& ex) { LGBM_R_save_exception_msg(ex); } \ catch(...) { Rf_error("unknown exception"); } \ - Rf_error("%s", R_errmsg_buffer); \ + Rf_error(R_errmsg_buffer); \ return R_NilValue; /* <- won't be reached */ #define CHECK_CALL(x) \ @@ -224,19 +224,16 @@ SEXP LGBM_DatasetGetSubset_R(SEXP handle, _AssertDatasetHandleNotNull(handle); SEXP ret = PROTECT(R_MakeExternalPtr(nullptr, R_NilValue, R_NilValue)); int32_t len = static_cast(Rf_asInteger(len_used_row_indices)); - std::unique_ptr idxvec(new int32_t[len]); + std::vector idxvec(len); // convert from one-based to zero-based index - const int *used_row_indices_ = INTEGER(used_row_indices); -#ifndef _MSC_VER -#pragma omp simd -#endif +#pragma omp parallel for schedule(static, 512) if (len >= 1024) for (int32_t i = 0; i < len; ++i) { - idxvec[i] = static_cast(used_row_indices_[i] - 1); + idxvec[i] = static_cast(INTEGER(used_row_indices)[i] - 1); } const char* parameters_ptr = CHAR(PROTECT(Rf_asChar(parameters))); DatasetHandle res = nullptr; CHECK_CALL(LGBM_DatasetGetSubset(R_ExternalPtrAddr(handle), - idxvec.get(), len, parameters_ptr, + idxvec.data(), len, parameters_ptr, &res)); R_SetExternalPtrAddr(ret, res); R_RegisterCFinalizerEx(ret, _DatasetFinalizer, TRUE); @@ -250,13 +247,13 @@ SEXP LGBM_DatasetSetFeatureNames_R(SEXP handle, R_API_BEGIN(); _AssertDatasetHandleNotNull(handle); auto vec_names = Split(CHAR(PROTECT(Rf_asChar(feature_names))), '\t'); + std::vector vec_sptr; int len = static_cast(vec_names.size()); - std::unique_ptr vec_sptr(new const char*[len]); for (int i = 0; i < len; ++i) { - vec_sptr[i] = vec_names[i].c_str(); + vec_sptr.push_back(vec_names[i].c_str()); } CHECK_CALL(LGBM_DatasetSetFeatureNames(R_ExternalPtrAddr(handle), - vec_sptr.get(), len)); + vec_sptr.data(), len)); UNPROTECT(1); return R_NilValue; R_API_END(); @@ -341,13 +338,21 @@ SEXP LGBM_DatasetSetField_R(SEXP handle, int len = Rf_asInteger(num_element); const char* name = CHAR(PROTECT(Rf_asChar(field_name))); if (!strcmp("group", name) || !strcmp("query", name)) { - CHECK_CALL(LGBM_DatasetSetField(R_ExternalPtrAddr(handle), name, INTEGER(field_data), len, C_API_DTYPE_INT32)); + std::vector vec(len); +#pragma omp parallel for schedule(static, 512) if (len >= 1024) + for (int i = 0; i < len; ++i) { + vec[i] = static_cast(INTEGER(field_data)[i]); + } + CHECK_CALL(LGBM_DatasetSetField(R_ExternalPtrAddr(handle), name, vec.data(), len, C_API_DTYPE_INT32)); } else if (!strcmp("init_score", name)) { CHECK_CALL(LGBM_DatasetSetField(R_ExternalPtrAddr(handle), name, REAL(field_data), len, C_API_DTYPE_FLOAT64)); } else { - std::unique_ptr vec(new float[len]); - std::copy(REAL(field_data), REAL(field_data) + len, vec.get()); - CHECK_CALL(LGBM_DatasetSetField(R_ExternalPtrAddr(handle), name, vec.get(), len, C_API_DTYPE_FLOAT32)); + std::vector vec(len); +#pragma omp parallel for schedule(static, 512) if (len >= 1024) + for (int i = 0; i < len; ++i) { + vec[i] = static_cast(REAL(field_data)[i]); + } + CHECK_CALL(LGBM_DatasetSetField(R_ExternalPtrAddr(handle), name, vec.data(), len, C_API_DTYPE_FLOAT32)); } UNPROTECT(1); return R_NilValue; @@ -367,19 +372,22 @@ SEXP LGBM_DatasetGetField_R(SEXP handle, if (!strcmp("group", name) || !strcmp("query", name)) { auto p_data = reinterpret_cast(res); // convert from boundaries to size - int *field_data_ = INTEGER(field_data); -#ifndef _MSC_VER -#pragma omp simd -#endif +#pragma omp parallel for schedule(static, 512) if (out_len >= 1024) for (int i = 0; i < out_len - 1; ++i) { - field_data_[i] = p_data[i + 1] - p_data[i]; + INTEGER(field_data)[i] = p_data[i + 1] - p_data[i]; } } else if (!strcmp("init_score", name)) { auto p_data = reinterpret_cast(res); - std::copy(p_data, p_data + out_len, REAL(field_data)); +#pragma omp parallel for schedule(static, 512) if (out_len >= 1024) + for (int i = 0; i < out_len; ++i) { + REAL(field_data)[i] = p_data[i]; + } } else { auto p_data = reinterpret_cast(res); - std::copy(p_data, p_data + out_len, REAL(field_data)); +#pragma omp parallel for schedule(static, 512) if (out_len >= 1024) + for (int i = 0; i < out_len; ++i) { + REAL(field_data)[i] = p_data[i]; + } } UNPROTECT(1); return R_NilValue; @@ -602,10 +610,13 @@ SEXP LGBM_BoosterUpdateOneIterCustom_R(SEXP handle, _AssertBoosterHandleNotNull(handle); int is_finished = 0; int int_len = Rf_asInteger(len); - std::unique_ptr tgrad(new float[int_len]), thess(new float[int_len]); - std::copy(REAL(grad), REAL(grad) + int_len, tgrad.get()); - std::copy(REAL(hess), REAL(hess) + int_len, thess.get()); - CHECK_CALL(LGBM_BoosterUpdateOneIterCustom(R_ExternalPtrAddr(handle), tgrad.get(), thess.get(), &is_finished)); + std::vector tgrad(int_len), thess(int_len); +#pragma omp parallel for schedule(static, 512) if (int_len >= 1024) + for (int j = 0; j < int_len; ++j) { + tgrad[j] = static_cast(REAL(grad)[j]); + thess[j] = static_cast(REAL(hess)[j]); + } + CHECK_CALL(LGBM_BoosterUpdateOneIterCustom(R_ExternalPtrAddr(handle), tgrad.data(), thess.data(), &is_finished)); return R_NilValue; R_API_END(); } @@ -1193,23 +1204,6 @@ SEXP LGBM_BoosterGetLoadedParam_R(SEXP handle) { R_API_END(); } -SEXP LGBM_GetMaxThreads_R(SEXP out) { - R_API_BEGIN(); - int num_threads; - CHECK_CALL(LGBM_GetMaxThreads(&num_threads)); - INTEGER(out)[0] = num_threads; - return R_NilValue; - R_API_END(); -} - -SEXP LGBM_SetMaxThreads_R(SEXP num_threads) { - R_API_BEGIN(); - int new_num_threads = Rf_asInteger(num_threads); - CHECK_CALL(LGBM_SetMaxThreads(new_num_threads)); - return R_NilValue; - R_API_END(); -} - // .Call() calls static const R_CallMethodDef CallEntries[] = { {"LGBM_HandleIsNull_R" , (DL_FUNC) &LGBM_HandleIsNull_R , 1}, @@ -1266,8 +1260,6 @@ static const R_CallMethodDef CallEntries[] = { {"LGBM_BoosterDumpModel_R" , (DL_FUNC) &LGBM_BoosterDumpModel_R , 3}, {"LGBM_NullBoosterHandleError_R" , (DL_FUNC) &LGBM_NullBoosterHandleError_R , 0}, {"LGBM_DumpParamAliases_R" , (DL_FUNC) &LGBM_DumpParamAliases_R , 0}, - {"LGBM_GetMaxThreads_R" , (DL_FUNC) &LGBM_GetMaxThreads_R , 1}, - {"LGBM_SetMaxThreads_R" , (DL_FUNC) &LGBM_SetMaxThreads_R , 1}, {NULL, NULL, 0} }; diff --git a/R-package/src/lightgbm_R.h b/R-package/src/lightgbm_R.h index 4f0407e8f2ec..7141a06a207c 100644 --- a/R-package/src/lightgbm_R.h +++ b/R-package/src/lightgbm_R.h @@ -850,23 +850,4 @@ LIGHTGBM_C_EXPORT SEXP LGBM_BoosterDumpModel_R( */ LIGHTGBM_C_EXPORT SEXP LGBM_DumpParamAliases_R(); -/*! -* \brief Get current maximum number of threads used by LightGBM routines in this process. -* \param[out] out current maximum number of threads used by LightGBM. -1 means defaulting to omp_get_num_threads(). -* \return R NULL value -*/ -LIGHTGBM_C_EXPORT SEXP LGBM_GetMaxThreads_R( - SEXP out -); - - -/*! -* \brief Set maximum number of threads used by LightGBM routines in this process. -* \param num_threads maximum number of threads used by LightGBM. -1 means defaulting to omp_get_num_threads(). -* \return R NULL value -*/ -LIGHTGBM_C_EXPORT SEXP LGBM_SetMaxThreads_R( - SEXP num_threads -); - #endif // LIGHTGBM_R_H_ diff --git a/R-package/tests/testthat.R b/R-package/tests/testthat.R index 72a440c275b0..4752952740b2 100644 --- a/R-package/tests/testthat.R +++ b/R-package/tests/testthat.R @@ -1,5 +1,5 @@ library(testthat) -library(lightgbm) # nolint: [unused_import] +library(lightgbm) test_check( package = "lightgbm" diff --git a/R-package/tests/testthat/helper.R b/R-package/tests/testthat/helper.R index 45edf40efbeb..9c928c1f71d1 100644 --- a/R-package/tests/testthat/helper.R +++ b/R-package/tests/testthat/helper.R @@ -11,11 +11,6 @@ # the check farm is a shared resource and will typically be running many checks simultaneously. # .LGB_MAX_THREADS <- 2L -setLGBMthreads(.LGB_MAX_THREADS) - -# control data.table parallelism -# ref: https://github.com/Rdatatable/data.table/issues/5658 -data.table::setDTthreads(1L) # by default, how much should results in tests be allowed to differ from hard-coded expected numbers? .LGB_NUMERIC_TOLERANCE <- 1e-6 diff --git a/R-package/tests/testthat/test_Predictor.R b/R-package/tests/testthat/test_Predictor.R index 192171c915bf..90be1d08cf67 100644 --- a/R-package/tests/testthat/test_Predictor.R +++ b/R-package/tests/testthat/test_Predictor.R @@ -17,16 +17,16 @@ test_that("Predictor$finalize() should not fail", { bst$save_model(filename = model_file) predictor <- Predictor$new(modelfile = model_file) - expect_true(.is_Predictor(predictor)) + expect_true(lgb.is.Predictor(predictor)) - expect_false(.is_null_handle(predictor$.__enclos_env__$private$handle)) + expect_false(lgb.is.null.handle(predictor$.__enclos_env__$private$handle)) predictor$finalize() - expect_true(.is_null_handle(predictor$.__enclos_env__$private$handle)) + expect_true(lgb.is.null.handle(predictor$.__enclos_env__$private$handle)) # calling finalize() a second time shouldn't cause any issues predictor$finalize() - expect_true(.is_null_handle(predictor$.__enclos_env__$private$handle)) + expect_true(lgb.is.null.handle(predictor$.__enclos_env__$private$handle)) }) test_that("predictions do not fail for integer input", { @@ -79,7 +79,7 @@ test_that("start_iteration works correctly", { , valids = list("test" = dtest) , early_stopping_rounds = 2L ) - expect_true(.is_Booster(bst)) + expect_true(lgb.is.Booster(bst)) pred1 <- predict(bst, newdata = test$data, type = "raw") pred_contrib1 <- predict(bst, test$data, type = "contrib") pred2 <- rep(0.0, length(pred1)) diff --git a/R-package/tests/testthat/test_basic.R b/R-package/tests/testthat/test_basic.R index 75abd26dd152..57c33c35dfee 100644 --- a/R-package/tests/testthat/test_basic.R +++ b/R-package/tests/testthat/test_basic.R @@ -1094,7 +1094,7 @@ test_that("lgb.train() works as expected with sparse features", { , nrounds = nrounds ) - expect_true(.is_Booster(bst)) + expect_true(lgb.is.Booster(bst)) expect_equal(bst$current_iter(), nrounds) parsed_model <- jsonlite::fromJSON(bst$dump_model()) expect_equal(parsed_model$objective, "binary sigmoid:1") @@ -1816,7 +1816,7 @@ test_that("lgb.train() supports non-ASCII feature names", { ) , colnames = feature_names ) - expect_true(.is_Booster(bst)) + expect_true(lgb.is.Booster(bst)) dumped_model <- jsonlite::fromJSON(bst$dump_model()) # UTF-8 strings are not well-supported on Windows @@ -2522,7 +2522,7 @@ test_that("lgb.train() fit on linearly-relatead data improves when using linear , params = params , valids = list("train" = dtrain) ) - expect_true(.is_Booster(bst)) + expect_true(lgb.is.Booster(bst)) dtrain <- .new_dataset() bst_linear <- lgb.train( @@ -2531,7 +2531,7 @@ test_that("lgb.train() fit on linearly-relatead data improves when using linear , params = utils::modifyList(params, list(linear_tree = TRUE)) , valids = list("train" = dtrain) ) - expect_true(.is_Booster(bst_linear)) + expect_true(lgb.is.Booster(bst_linear)) bst_last_mse <- bst$record_evals[["train"]][["l2"]][["eval"]][[10L]] bst_lin_last_mse <- bst_linear$record_evals[["train"]][["l2"]][["eval"]][[10L]] @@ -2599,7 +2599,7 @@ test_that("lgb.train() works with linear learners even if Dataset has missing va , params = params , valids = list("train" = dtrain) ) - expect_true(.is_Booster(bst)) + expect_true(lgb.is.Booster(bst)) dtrain <- .new_dataset() bst_linear <- lgb.train( @@ -2608,7 +2608,7 @@ test_that("lgb.train() works with linear learners even if Dataset has missing va , params = utils::modifyList(params, list(linear_tree = TRUE)) , valids = list("train" = dtrain) ) - expect_true(.is_Booster(bst_linear)) + expect_true(lgb.is.Booster(bst_linear)) bst_last_mse <- bst$record_evals[["train"]][["l2"]][["eval"]][[10L]] bst_lin_last_mse <- bst_linear$record_evals[["train"]][["l2"]][["eval"]][[10L]] @@ -2649,7 +2649,7 @@ test_that("lgb.train() works with linear learners, bagging, and a Dataset that h , params = params , valids = list("train" = dtrain) ) - expect_true(.is_Booster(bst)) + expect_true(lgb.is.Booster(bst)) dtrain <- .new_dataset() bst_linear <- lgb.train( @@ -2658,7 +2658,7 @@ test_that("lgb.train() works with linear learners, bagging, and a Dataset that h , params = utils::modifyList(params, list(linear_tree = TRUE)) , valids = list("train" = dtrain) ) - expect_true(.is_Booster(bst_linear)) + expect_true(lgb.is.Booster(bst_linear)) bst_last_mse <- bst$record_evals[["train"]][["l2"]][["eval"]][[10L]] bst_lin_last_mse <- bst_linear$record_evals[["train"]][["l2"]][["eval"]][[10L]] @@ -2699,7 +2699,7 @@ test_that("lgb.train() works with linear learners and data where a feature has o , nrounds = 10L , params = utils::modifyList(params, list(linear_tree = TRUE)) ) - expect_true(.is_Booster(bst_linear)) + expect_true(lgb.is.Booster(bst_linear)) }) test_that("lgb.train() works with linear learners when Dataset has categorical features", { @@ -2732,7 +2732,7 @@ test_that("lgb.train() works with linear learners when Dataset has categorical f , params = params , valids = list("train" = dtrain) ) - expect_true(.is_Booster(bst)) + expect_true(lgb.is.Booster(bst)) dtrain <- .new_dataset() bst_linear <- lgb.train( @@ -2741,7 +2741,7 @@ test_that("lgb.train() works with linear learners when Dataset has categorical f , params = utils::modifyList(params, list(linear_tree = TRUE)) , valids = list("train" = dtrain) ) - expect_true(.is_Booster(bst_linear)) + expect_true(lgb.is.Booster(bst_linear)) bst_last_mse <- bst$record_evals[["train"]][["l2"]][["eval"]][[10L]] bst_lin_last_mse <- bst_linear$record_evals[["train"]][["l2"]][["eval"]][[10L]] @@ -3805,26 +3805,3 @@ test_that("lightgbm() correctly sets objective when passing lgb.Dataset as input ) expect_equal(model$params$objective, "regression") }) - -test_that("Evaluation metrics aren't printed as a single-element vector", { - log_txt <- capture_output({ - data(mtcars) - y <- mtcars$mpg - x <- as.matrix(mtcars[, -1L]) - cv_result <- lgb.cv( - data = lgb.Dataset(x, label = y) - , params = list( - objective = "regression" - , metric = "l2" - , min_data_in_leaf = 5L - , max_depth = 3L - , num_threads = .LGB_MAX_THREADS - ) - , nrounds = 2L - , nfold = 3L - , verbose = 1L - , eval_train_metric = TRUE - ) - }) - expect_false(grepl("[1] \"[1]", log_txt, fixed = TRUE)) -}) diff --git a/R-package/tests/testthat/test_dataset.R b/R-package/tests/testthat/test_dataset.R index a8585baa2621..cf68ce9262a3 100644 --- a/R-package/tests/testthat/test_dataset.R +++ b/R-package/tests/testthat/test_dataset.R @@ -183,9 +183,7 @@ test_that("lgb.Dataset: colnames", { colnames(dtest) <- "asdf" }) new_names <- make.names(seq_len(ncol(test_data))) - expect_silent({ - colnames(dtest) <- new_names - }) + expect_silent(colnames(dtest) <- new_names) expect_equal(colnames(dtest), new_names) }) @@ -206,7 +204,7 @@ test_that("lgb.Dataset: Dataset should be able to construct from matrix and retu , rawData , nrow(rawData) , ncol(rawData) - , lightgbm:::.params2str(params = list()) + , lightgbm:::lgb.params2str(params = list()) , ref_handle ) expect_true(methods::is(handle, "externalptr")) @@ -322,7 +320,7 @@ test_that("Dataset$update_parameters() does nothing for empty inputs", { res <- ds$update_params( params = list() ) - expect_true(.is_Dataset(res)) + expect_true(lgb.is.Dataset(res)) new_params <- ds$get_params() expect_identical(new_params, initial_params) @@ -343,7 +341,7 @@ test_that("Dataset$update_params() works correctly for recognized Dataset parame res <- ds$update_params( params = new_params ) - expect_true(.is_Dataset(res)) + expect_true(lgb.is.Dataset(res)) updated_params <- ds$get_params() for (param_name in names(new_params)) { @@ -356,17 +354,17 @@ test_that("Dataset$finalize() should not fail on an already-finalized Dataset", data = test_data , label = test_label ) - expect_true(.is_null_handle(dtest$.__enclos_env__$private$handle)) + expect_true(lgb.is.null.handle(dtest$.__enclos_env__$private$handle)) dtest$construct() - expect_false(.is_null_handle(dtest$.__enclos_env__$private$handle)) + expect_false(lgb.is.null.handle(dtest$.__enclos_env__$private$handle)) dtest$finalize() - expect_true(.is_null_handle(dtest$.__enclos_env__$private$handle)) + expect_true(lgb.is.null.handle(dtest$.__enclos_env__$private$handle)) # calling finalize() a second time shouldn't cause any issues dtest$finalize() - expect_true(.is_null_handle(dtest$.__enclos_env__$private$handle)) + expect_true(lgb.is.null.handle(dtest$.__enclos_env__$private$handle)) }) test_that("lgb.Dataset: should be able to run lgb.train() immediately after using lgb.Dataset() on a file", { @@ -401,7 +399,7 @@ test_that("lgb.Dataset: should be able to run lgb.train() immediately after usin , data = dtest_read_in ) - expect_true(.is_Booster(x = bst)) + expect_true(lgb.is.Booster(x = bst)) }) test_that("lgb.Dataset: should be able to run lgb.cv() immediately after using lgb.Dataset() on a file", { diff --git a/R-package/tests/testthat/test_learning_to_rank.R b/R-package/tests/testthat/test_learning_to_rank.R index e99aff44ceb3..6868794cf8ec 100644 --- a/R-package/tests/testthat/test_learning_to_rank.R +++ b/R-package/tests/testthat/test_learning_to_rank.R @@ -25,7 +25,7 @@ test_that("learning-to-rank with lgb.train() works as expected", { , data = dtrain , nrounds = 10L ) - expect_true(.is_Booster(model)) + expect_true(lgb.is.Booster(model)) dumped_model <- jsonlite::fromJSON( model$dump_model() @@ -37,8 +37,7 @@ test_that("learning-to-rank with lgb.train() works as expected", { eval_results <- model$eval_train() expect_equal(length(eval_results), length(eval_names)) for (result in eval_results) { - expect_true(result[["value"]] > 0.0) - expect_true(result[["value"]] < 1.0) + expect_true(result[["value"]] > 0.0 && result[["value"]] < 1.0) expect_true(result[["higher_better"]]) expect_identical(result[["data_name"]], "training") } @@ -105,10 +104,8 @@ test_that("learning-to-rank with lgb.cv() works as expected", { # check that best score and iter make sense (0.0 < nDCG < 1.0) best_iter <- cv_bst$best_iter best_score <- cv_bst$best_score - expect_true(best_iter > 0L) - expect_true(best_iter <= nrounds) - expect_true(best_score > 0.0) - expect_true(best_score < 1.0) + expect_true(best_iter > 0L && best_iter <= nrounds) + expect_true(best_score > 0.0 && best_score < 1.0) expect_true(abs(best_score - 0.75) < .LGB_NUMERIC_TOLERANCE) # best_score should be set for the first metric diff --git a/R-package/tests/testthat/test_lgb.Booster.R b/R-package/tests/testthat/test_lgb.Booster.R index e6b0e8abda64..5f398f1c081d 100644 --- a/R-package/tests/testthat/test_lgb.Booster.R +++ b/R-package/tests/testthat/test_lgb.Booster.R @@ -11,16 +11,16 @@ test_that("Booster$finalize() should not fail", { , verbose = .LGB_VERBOSITY , nrounds = 3L ) - expect_true(.is_Booster(bst)) + expect_true(lgb.is.Booster(bst)) - expect_false(.is_null_handle(bst$.__enclos_env__$private$handle)) + expect_false(lgb.is.null.handle(bst$.__enclos_env__$private$handle)) bst$finalize() - expect_true(.is_null_handle(bst$.__enclos_env__$private$handle)) + expect_true(lgb.is.null.handle(bst$.__enclos_env__$private$handle)) # calling finalize() a second time shouldn't cause any issues bst$finalize() - expect_true(.is_null_handle(bst$.__enclos_env__$private$handle)) + expect_true(lgb.is.null.handle(bst$.__enclos_env__$private$handle)) }) test_that("lgb.get.eval.result() should throw an informative error if booster is not an lgb.Booster", { @@ -188,7 +188,7 @@ test_that("Loading a Booster from a text file works", { , params = params , nrounds = 2L ) - expect_true(.is_Booster(bst)) + expect_true(lgb.is.Booster(bst)) pred <- predict(bst, test$data) model_file <- tempfile(fileext = ".model") @@ -232,7 +232,7 @@ test_that("boosters with linear models at leaves can be written to text file and , params = params , verbose = .LGB_VERBOSITY ) - expect_true(.is_Booster(bst)) + expect_true(lgb.is.Booster(bst)) # save predictions, then write the model to a file and destroy it in R preds <- predict(bst, X) @@ -269,7 +269,7 @@ test_that("Loading a Booster from a string works", { ) , nrounds = 2L ) - expect_true(.is_Booster(bst)) + expect_true(lgb.is.Booster(bst)) pred <- predict(bst, test$data) model_string <- bst$save_model_to_string() @@ -376,7 +376,7 @@ test_that("If a string and a file are both passed to lgb.load() the file is used ) , nrounds = 2L ) - expect_true(.is_Booster(bst)) + expect_true(lgb.is.Booster(bst)) pred <- predict(bst, test$data) model_file <- tempfile(fileext = ".model") @@ -411,7 +411,7 @@ test_that("Creating a Booster from a Dataset should work", { ), train_set = dtrain ) - expect_true(.is_Booster(bst)) + expect_true(lgb.is.Booster(bst)) expect_equal(bst$current_iter(), 0L) expect_true(is.na(bst$best_score)) expect_true(all(bst$predict(agaricus.train$data) == 0.5)) @@ -446,10 +446,10 @@ test_that("Creating a Booster from a Dataset with an existing predictor should w , num_threads = .LGB_MAX_THREADS ) ) - expect_true(.is_Booster(bst)) + expect_true(lgb.is.Booster(bst)) expect_equal(bst$current_iter(), nrounds) expect_equal(bst$eval_train()[[1L]][["value"]], 0.1115352) - expect_true(.is_Booster(bst_from_ds)) + expect_true(lgb.is.Booster(bst_from_ds)) expect_equal(bst_from_ds$current_iter(), nrounds) expect_equal(bst_from_ds$eval_train()[[1L]][["value"]], 5.65704892) dumped_model <- jsonlite::fromJSON(bst$dump_model()) @@ -531,7 +531,7 @@ test_that("Booster$rollback_one_iter() should work as expected", { , nrounds = nrounds ) expect_equal(bst$current_iter(), nrounds) - expect_true(.is_Booster(bst)) + expect_true(lgb.is.Booster(bst)) logloss <- bst$eval_train()[[1L]][["value"]] expect_equal(logloss, 0.01904786) @@ -539,7 +539,7 @@ test_that("Booster$rollback_one_iter() should work as expected", { # rollback_one_iter() should return a booster and modify the original # booster in place - expect_true(.is_Booster(x)) + expect_true(lgb.is.Booster(x)) expect_equal(bst$current_iter(), nrounds - 1L) # score should now come from the model as of 4 iterations @@ -565,7 +565,7 @@ test_that("Booster$update() passing a train_set works as expected", { ) , nrounds = nrounds ) - expect_true(.is_Booster(bst)) + expect_true(lgb.is.Booster(bst)) expect_equal(bst$current_iter(), nrounds) bst$update( train_set = Dataset$new( @@ -574,7 +574,7 @@ test_that("Booster$update() passing a train_set works as expected", { , params = list(verbose = .LGB_VERBOSITY) ) ) - expect_true(.is_Booster(bst)) + expect_true(lgb.is.Booster(bst)) expect_equal(bst$current_iter(), nrounds + 1L) # train with 3 rounds directly @@ -590,7 +590,7 @@ test_that("Booster$update() passing a train_set works as expected", { ) , nrounds = nrounds + 1L ) - expect_true(.is_Booster(bst2)) + expect_true(lgb.is.Booster(bst2)) expect_equal(bst2$current_iter(), nrounds + 1L) # model with 2 rounds + 1 update should be identical to 3 rounds @@ -716,7 +716,7 @@ test_that("Saving a model with different feature importance types works", { ) , nrounds = 2L ) - expect_true(.is_Booster(bst)) + expect_true(lgb.is.Booster(bst)) .feat_importance_from_string <- function(model_string) { file_lines <- strsplit(model_string, "\n", fixed = TRUE)[[1L]] @@ -772,7 +772,7 @@ test_that("Saving a model with unknown importance type fails", { ) , nrounds = 2L ) - expect_true(.is_Booster(bst)) + expect_true(lgb.is.Booster(bst)) UNSUPPORTED_IMPORTANCE <- 2L expect_error({ @@ -1252,6 +1252,43 @@ test_that("lgb.cv() correctly handles passing through params to the model file", }) +test_that("params (including dataset params) should be stored in .rds file for Booster", { + data(agaricus.train, package = "lightgbm") + dtrain <- lgb.Dataset( + agaricus.train$data + , label = agaricus.train$label + , params = list( + max_bin = 17L + ) + ) + params <- list( + objective = "binary" + , max_depth = 4L + , bagging_fraction = 0.8 + , verbose = .LGB_VERBOSITY + , num_threads = .LGB_MAX_THREADS + ) + bst <- Booster$new( + params = params + , train_set = dtrain + ) + bst_file <- tempfile(fileext = ".rds") + expect_warning(saveRDS.lgb.Booster(bst, file = bst_file)) + + expect_warning(bst_from_file <- readRDS.lgb.Booster(file = bst_file)) + expect_identical( + bst_from_file$params + , list( + objective = "binary" + , max_depth = 4L + , bagging_fraction = 0.8 + , verbose = .LGB_VERBOSITY + , num_threads = .LGB_MAX_THREADS + , max_bin = 17L + ) + ) +}) + test_that("params (including dataset params) should be stored in .rds file for Booster", { data(agaricus.train, package = "lightgbm") dtrain <- lgb.Dataset( @@ -1311,6 +1348,46 @@ test_that("Handle is automatically restored when calling predict", { expect_equal(pred_before, pred_after) }) +test_that("boosters with linear models at leaves work with saveRDS.lgb.Booster and readRDS.lgb.Booster", { + X <- matrix(rnorm(100L), ncol = 1L) + labels <- 2L * X + runif(nrow(X), 0L, 0.1) + dtrain <- lgb.Dataset( + data = X + , label = labels + ) + + params <- list( + objective = "regression" + , verbose = .LGB_VERBOSITY + , metric = "mse" + , seed = 0L + , num_leaves = 2L + , num_threads = .LGB_MAX_THREADS + ) + + bst <- lgb.train( + data = dtrain + , nrounds = 10L + , params = params + ) + expect_true(lgb.is.Booster(bst)) + + # save predictions, then write the model to a file and destroy it in R + preds <- predict(bst, X) + model_file <- tempfile(fileext = ".rds") + expect_warning(saveRDS.lgb.Booster(bst, file = model_file)) + bst$finalize() + expect_null(bst$.__enclos_env__$private$handle) + rm(bst) + + # load the booster and make predictions...should be the same + expect_warning({ + bst2 <- readRDS.lgb.Booster(file = model_file) + }) + preds2 <- predict(bst2, X) + expect_identical(preds, preds2) +}) + test_that("boosters with linear models at leaves can be written to RDS and re-loaded successfully", { X <- matrix(rnorm(100L), ncol = 1L) labels <- 2L * X + runif(nrow(X), 0L, 0.1) @@ -1333,7 +1410,7 @@ test_that("boosters with linear models at leaves can be written to RDS and re-lo , nrounds = 10L , params = params ) - expect_true(.is_Booster(bst)) + expect_true(lgb.is.Booster(bst)) # save predictions, then write the model to a file and destroy it in R preds <- predict(bst, X) diff --git a/R-package/tests/testthat/test_multithreading.R b/R-package/tests/testthat/test_multithreading.R deleted file mode 100644 index e2f3169627a2..000000000000 --- a/R-package/tests/testthat/test_multithreading.R +++ /dev/null @@ -1,16 +0,0 @@ -test_that("getLGBMthreads() and setLGBMthreads() work as expected", { - # works with integer input - ret <- setLGBMthreads(2L) - expect_null(ret) - expect_equal(getLGBMthreads(), 2L) - - # works with float input - ret <- setLGBMthreads(1.0) - expect_null(ret) - expect_equal(getLGBMthreads(), 1L) - - # setting to any negative number sets max threads to -1 - ret <- setLGBMthreads(-312L) - expect_null(ret) - expect_equal(getLGBMthreads(), -1L) -}) diff --git a/R-package/tests/testthat/test_utils.R b/R-package/tests/testthat/test_utils.R index 898aed9b0915..4ab05e075ae3 100644 --- a/R-package/tests/testthat/test_utils.R +++ b/R-package/tests/testthat/test_utils.R @@ -1,12 +1,12 @@ -test_that(".params2str() works as expected for empty lists", { - out_str <- .params2str( +test_that("lgb.params2str() works as expected for empty lists", { + out_str <- lgb.params2str( params = list() ) expect_identical(class(out_str), "character") expect_equal(out_str, "") }) -test_that(".params2str() works as expected for a key in params with multiple different-length elements", { +test_that("lgb.params2str() works as expected for a key in params with multiple different-length elements", { metrics <- c("a", "ab", "abc", "abcdefg") params <- list( objective = "magic" @@ -14,7 +14,7 @@ test_that(".params2str() works as expected for a key in params with multiple dif , nrounds = 10L , learning_rate = 0.0000001 ) - out_str <- .params2str( + out_str <- lgb.params2str( params = params ) expect_identical(class(out_str), "character") @@ -24,8 +24,8 @@ test_that(".params2str() works as expected for a key in params with multiple dif ) }) -test_that(".params2str() passes through duplicated params", { - out_str <- .params2str( +test_that("lgb.params2str() passes through duplicated params", { + out_str <- lgb.params2str( params = list( objective = "regression" , bagging_fraction = 0.8 @@ -35,8 +35,8 @@ test_that(".params2str() passes through duplicated params", { expect_equal(out_str, "objective=regression bagging_fraction=0.8 bagging_fraction=0.5") }) -test_that(".check_eval works as expected with no metric", { - params <- .check_eval( +test_that("lgb.check.eval works as expected with no metric", { + params <- lgb.check.eval( params = list(device = "cpu") , eval = "binary_error" ) @@ -44,8 +44,8 @@ test_that(".check_eval works as expected with no metric", { expect_identical(params[["metric"]], list("binary_error")) }) -test_that(".check_eval adds eval to metric in params", { - params <- .check_eval( +test_that("lgb.check.eval adds eval to metric in params", { + params <- lgb.check.eval( params = list(metric = "auc") , eval = "binary_error" ) @@ -53,8 +53,8 @@ test_that(".check_eval adds eval to metric in params", { expect_identical(params[["metric"]], list("auc", "binary_error")) }) -test_that(".check_eval adds eval to metric in params if two evaluation names are provided", { - params <- .check_eval( +test_that("lgb.check.eval adds eval to metric in params if two evaluation names are provided", { + params <- lgb.check.eval( params = list(metric = "auc") , eval = c("binary_error", "binary_logloss") ) @@ -62,8 +62,8 @@ test_that(".check_eval adds eval to metric in params if two evaluation names are expect_identical(params[["metric"]], list("auc", "binary_error", "binary_logloss")) }) -test_that(".check_eval adds eval to metric in params if a list is provided", { - params <- .check_eval( +test_that("lgb.check.eval adds eval to metric in params if a list is provided", { + params <- lgb.check.eval( params = list(metric = "auc") , eval = list("binary_error", "binary_logloss") ) @@ -71,8 +71,8 @@ test_that(".check_eval adds eval to metric in params if a list is provided", { expect_identical(params[["metric"]], list("auc", "binary_error", "binary_logloss")) }) -test_that(".check_eval drops duplicate metrics and preserves order", { - params <- .check_eval( +test_that("lgb.check.eval drops duplicate metrics and preserves order", { + params <- lgb.check.eval( params = list(metric = "l1") , eval = list("l2", "rmse", "l1", "rmse") ) @@ -80,9 +80,9 @@ test_that(".check_eval drops duplicate metrics and preserves order", { expect_identical(params[["metric"]], list("l1", "l2", "rmse")) }) -test_that(".check_wrapper_param() uses passed-in keyword arg if no alias found in params", { +test_that("lgb.check.wrapper_param() uses passed-in keyword arg if no alias found in params", { kwarg_val <- sample(seq_len(100L), size = 1L) - params <- .check_wrapper_param( + params <- lgb.check.wrapper_param( main_param_name = "num_iterations" , params = list() , alternative_kwarg_value = kwarg_val @@ -90,10 +90,10 @@ test_that(".check_wrapper_param() uses passed-in keyword arg if no alias found i expect_equal(params[["num_iterations"]], kwarg_val) }) -test_that(".check_wrapper_param() prefers main parameter to alias and keyword arg", { +test_that("lgb.check.wrapper_param() prefers main parameter to alias and keyword arg", { num_iterations <- sample(seq_len(100L), size = 1L) kwarg_val <- sample(seq_len(100L), size = 1L) - params <- .check_wrapper_param( + params <- lgb.check.wrapper_param( main_param_name = "num_iterations" , params = list( num_iterations = num_iterations @@ -108,11 +108,11 @@ test_that(".check_wrapper_param() prefers main parameter to alias and keyword ar expect_identical(params, list(num_iterations = num_iterations)) }) -test_that(".check_wrapper_param() prefers alias to keyword arg", { +test_that("lgb.check.wrapper_param() prefers alias to keyword arg", { n_estimators <- sample(seq_len(100L), size = 1L) num_tree <- sample(seq_len(100L), size = 1L) kwarg_val <- sample(seq_len(100L), size = 1L) - params <- .check_wrapper_param( + params <- lgb.check.wrapper_param( main_param_name = "num_iterations" , params = list( num_tree = num_tree @@ -124,7 +124,7 @@ test_that(".check_wrapper_param() prefers alias to keyword arg", { expect_identical(params, list(num_iterations = num_tree)) # switching the order shouldn't switch which one is chosen - params2 <- .check_wrapper_param( + params2 <- lgb.check.wrapper_param( main_param_name = "num_iterations" , params = list( n_estimators = n_estimators @@ -136,14 +136,14 @@ test_that(".check_wrapper_param() prefers alias to keyword arg", { expect_identical(params2, list(num_iterations = num_tree)) }) -test_that(".equal_or_both_null produces expected results", { - expect_true(.equal_or_both_null(NULL, NULL)) - expect_false(.equal_or_both_null(1.0, NULL)) - expect_false(.equal_or_both_null(NULL, 1.0)) - expect_true(.equal_or_both_null(1.0, 1.0)) - expect_true(.equal_or_both_null(1.0, 1L)) - expect_false(.equal_or_both_null(NA, NULL)) - expect_false(.equal_or_both_null(NULL, NA)) - expect_false(.equal_or_both_null(10.0, 1L)) - expect_true(.equal_or_both_null(0L, 0L)) +test_that("lgb.equal.or.both.null produces expected results", { + expect_true(lgb.equal.or.both.null(NULL, NULL)) + expect_false(lgb.equal.or.both.null(1.0, NULL)) + expect_false(lgb.equal.or.both.null(NULL, 1.0)) + expect_true(lgb.equal.or.both.null(1.0, 1.0)) + expect_true(lgb.equal.or.both.null(1.0, 1L)) + expect_false(lgb.equal.or.both.null(NA, NULL)) + expect_false(lgb.equal.or.both.null(NULL, NA)) + expect_false(lgb.equal.or.both.null(10.0, 1L)) + expect_true(lgb.equal.or.both.null(0L, 0L)) }) diff --git a/R-package/vignettes/basic_walkthrough.Rmd b/R-package/vignettes/basic_walkthrough.Rmd index 01e2410d501e..d7aaf676f386 100644 --- a/R-package/vignettes/basic_walkthrough.Rmd +++ b/R-package/vignettes/basic_walkthrough.Rmd @@ -3,14 +3,10 @@ title: "Basic Walkthrough" description: > This vignette describes how to train a LightGBM model for binary classification. -output: - markdown::html_format: - options: - toc: true - number_sections: true +output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Basic Walkthrough} - %\VignetteEngine{knitr::knitr} + %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- @@ -27,16 +23,10 @@ knitr::opts_chunk$set( Welcome to the world of [LightGBM](https://lightgbm.readthedocs.io/en/latest/), a highly efficient gradient boosting implementation (Ke et al. 2017). -```{r} +```{r setup} library(lightgbm) ``` -```{r, include=FALSE} -# limit number of threads used, to be respectful of CRAN's resources when it checks this vignette -data.table::setDTthreads(1L) -setLGBMthreads(2L) -``` - This vignette will guide you through its basic usage. It will show how to build a simple binary classification model based on a subset of the `bank` dataset (Moro, Cortez, and Rita 2014). You will use the two input features "age" and "balance" to predict whether a client has subscribed a term deposit. ## The dataset diff --git a/README.md b/README.md index 3b3fe40790db..f6f4e8c570e0 100644 --- a/README.md +++ b/README.md @@ -41,6 +41,7 @@ Next you may want to read: - [**Features**](https://github.com/microsoft/LightGBM/blob/master/docs/Features.rst) and algorithms supported by LightGBM. - [**Parameters**](https://github.com/microsoft/LightGBM/blob/master/docs/Parameters.rst) is an exhaustive list of customization you can make. - [**Distributed Learning**](https://github.com/microsoft/LightGBM/blob/master/docs/Parallel-Learning-Guide.rst) and [**GPU Learning**](https://github.com/microsoft/LightGBM/blob/master/docs/GPU-Tutorial.rst) can speed up computation. +- [**Laurae++ interactive documentation**](https://sites.google.com/view/lauraepp/parameters) is a detailed guide for hyperparameters. - [**FLAML**](https://www.microsoft.com/en-us/research/project/fast-and-lightweight-automl-for-large-scale-data/articles/flaml-a-fast-and-lightweight-automl-library/) provides automated tuning for LightGBM ([code examples](https://microsoft.github.io/FLAML/docs/Examples/AutoML-for-LightGBM/)). - [**Optuna Hyperparameter Tuner**](https://medium.com/optuna/lightgbm-tuner-new-optuna-integration-for-hyperparameter-optimization-8b7095e99258) provides automated tuning for LightGBM hyperparameters ([code examples](https://github.com/optuna/optuna-examples/blob/main/lightgbm/lightgbm_tuner_simple.py)). - [**Understanding LightGBM Parameters (and How to Tune Them using Neptune)**](https://neptune.ai/blog/lightgbm-parameters-guide). @@ -63,8 +64,6 @@ External (Unofficial) Repositories Projects listed here offer alternative ways to use LightGBM. They are not maintained or officially endorsed by the `LightGBM` development team. -LightGBMLSS (An extension of LightGBM to probabilistic modelling from which prediction intervals and quantiles can be derived): https://github.com/StatMixedML/LightGBMLSS - FLAML (AutoML library for hyperparameter optimization): https://github.com/microsoft/FLAML Optuna (hyperparameter optimization framework): https://github.com/optuna/optuna diff --git a/VERSION.txt b/VERSION.txt index 67dae3929837..1f06da0058c9 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -1 +1 @@ -4.2.0.99 +4.1.0.99 diff --git a/build-cran-package.sh b/build-cran-package.sh index 9fa0c5877085..6558afcf77ad 100755 --- a/build-cran-package.sh +++ b/build-cran-package.sh @@ -132,7 +132,7 @@ cd "${TEMP_R_DIR}" using_windows_and_r3=$( Rscript -e 'cat(.Platform$OS.type == "windows" && R.version[["major"]] < 4)' ) - if test "${using_windows_and_r3}" = "TRUE"; then + if [[ ${using_windows_and_r3} == "TRUE" ]]; then LGB_CXX_STD="C++11" fi sed -i.bak -e "s/~~CXXSTD~~/${LGB_CXX_STD}/" DESCRIPTION @@ -227,7 +227,6 @@ if ${BUILD_VIGNETTES} ; then rm -f ./lightgbm/src/network/*.o rm -f ./lightgbm/src/objective/*.o rm -f ./lightgbm/src/treelearner/*.o - rm -f ./lightgbm/src/utils/*.o echo "re-tarring ${TARBALL_NAME}" tar \ diff --git a/docs/.linkcheckerrc b/docs/.linkcheckerrc index 003d8699a875..96fdcbd08157 100644 --- a/docs/.linkcheckerrc +++ b/docs/.linkcheckerrc @@ -11,7 +11,7 @@ ignore= http.*amd.com/.* https.*dl.acm.org/doi/.* https.*tandfonline.com/.* -ignorewarnings=http-redirected,http-robots-denied,https-certificate-error +ignorewarnings=http-robots-denied,https-certificate-error checkextern=1 [output] diff --git a/docs/Experiments.rst b/docs/Experiments.rst index 4440a2c0ccae..ede19cf3a2eb 100644 --- a/docs/Experiments.rst +++ b/docs/Experiments.rst @@ -18,19 +18,19 @@ Data We used 5 datasets to conduct our comparison experiments. Details of data are listed in the following table: -+-----------+-----------------------+---------------------------------------------------------------------------------+-------------+----------+----------------------------------------------+ -| Data | Task | Link | #Train\_Set | #Feature | Comments | -+===========+=======================+=================================================================================+=============+==========+==============================================+ -| Higgs | Binary classification | `link `__ | 10,500,000 | 28 | last 500,000 samples were used as test set | -+-----------+-----------------------+---------------------------------------------------------------------------------+-------------+----------+----------------------------------------------+ -| Yahoo LTR | Learning to rank | `link `__ | 473,134 | 700 | set1.train as train, set1.test as test | -+-----------+-----------------------+---------------------------------------------------------------------------------+-------------+----------+----------------------------------------------+ -| MS LTR | Learning to rank | `link `__ | 2,270,296 | 137 | {S1,S2,S3} as train set, {S5} as test set | -+-----------+-----------------------+---------------------------------------------------------------------------------+-------------+----------+----------------------------------------------+ -| Expo | Binary classification | `link `__ | 11,000,000 | 700 | last 1,000,000 samples were used as test set | -+-----------+-----------------------+---------------------------------------------------------------------------------+-------------+----------+----------------------------------------------+ -| Allstate | Binary classification | `link `__ | 13,184,290 | 4228 | last 1,000,000 samples were used as test set | -+-----------+-----------------------+---------------------------------------------------------------------------------+-------------+----------+----------------------------------------------+ ++-----------+-----------------------+------------------------------------------------------------------------+-------------+----------+----------------------------------------------+ +| Data | Task | Link | #Train\_Set | #Feature | Comments | ++===========+=======================+========================================================================+=============+==========+==============================================+ +| Higgs | Binary classification | `link `__ | 10,500,000 | 28 | last 500,000 samples were used as test set | ++-----------+-----------------------+------------------------------------------------------------------------+-------------+----------+----------------------------------------------+ +| Yahoo LTR | Learning to rank | `link `__ | 473,134 | 700 | set1.train as train, set1.test as test | ++-----------+-----------------------+------------------------------------------------------------------------+-------------+----------+----------------------------------------------+ +| MS LTR | Learning to rank | `link `__ | 2,270,296 | 137 | {S1,S2,S3} as train set, {S5} as test set | ++-----------+-----------------------+------------------------------------------------------------------------+-------------+----------+----------------------------------------------+ +| Expo | Binary classification | `link `__ | 11,000,000 | 700 | last 1,000,000 samples were used as test set | ++-----------+-----------------------+------------------------------------------------------------------------+-------------+----------+----------------------------------------------+ +| Allstate | Binary classification | `link `__ | 13,184,290 | 4228 | last 1,000,000 samples were used as test set | ++-----------+-----------------------+------------------------------------------------------------------------+-------------+----------+----------------------------------------------+ Environment ^^^^^^^^^^^ diff --git a/docs/FAQ.rst b/docs/FAQ.rst index 43999931ca07..3b06761114d5 100644 --- a/docs/FAQ.rst +++ b/docs/FAQ.rst @@ -11,15 +11,44 @@ LightGBM FAQ ------ -Please post questions, feature requests, and bug reports at https://github.com/microsoft/LightGBM/issues. +Critical Issues +=============== -This project is mostly maintained by volunteers, so please be patient. -If your request is time-sensitive or more than a month goes by without a response, please tag the maintainers below for help. +A **critical issue** could be a *crash*, *prediction error*, *nonsense output*, or something else requiring immediate attention. -- `@guolinke `__ **Guolin Ke** -- `@shiyu1994 `__ **Yu Shi** -- `@jameslamb `__ **James Lamb** -- `@jmoralez `__ **José Morales** +Please post such an issue in the `Microsoft/LightGBM repository `__. + +You may also ping a member of the core team according to the relevant area of expertise by mentioning them with the arabase (@) symbol: + +- `@guolinke `__ **Guolin Ke** (C++ code / R-package / Python-package) +- `@chivee `__ **Qiwei Ye** (C++ code / Python-package) +- `@shiyu1994 `__ **Yu Shi** (C++ code / Python-package) +- `@tongwu-msft` **Tong Wu** (C++ code / Python-package) +- `@hzy46 `__ **Zhiyuan He** (C++ code / Python-package) +- `@btrotta `__ **Belinda Trotta** (C++ code) +- `@Laurae2 `__ **Damien Soukhavong** (R-package) +- `@jameslamb `__ **James Lamb** (R-package / Dask-package) +- `@jmoralez `__ **José Morales** (Dask-package) +- `@wxchan `__ **Wenxuan Chen** (Python-package) +- `@henry0312 `__ **Tsukasa Omoto** (Python-package) +- `@StrikerRUS `__ **Nikita Titov** (Python-package) +- `@huanzhang12 `__ **Huan Zhang** (GPU support) + +Please include as much of the following information as possible when submitting a critical issue: + +- Is it reproducible on CLI (command line interface), R, and/or Python? + +- Is it specific to a wrapper? (R or Python?) + +- Is it specific to the compiler? (gcc or Clang version? MinGW or Visual Studio version?) + +- Is it specific to your Operating System? (Windows? Linux? macOS?) + +- Are you able to reproduce this issue with a simple case? + +- Does the issue persist after removing all optimization flags and compiling LightGBM in debug mode? + +When submitting issues, please keep in mind that this is largely a volunteer effort, and we may not be available 24/7 to provide support. -------------- @@ -33,7 +62,7 @@ General LightGBM Questions 1. Where do I find more details about LightGBM parameters? ---------------------------------------------------------- -Take a look at `Parameters <./Parameters.rst>`__. +Take a look at `Parameters <./Parameters.rst>`__ and the `Laurae++/Parameters `__ website. 2. On datasets with millions of features, training does not start (or starts after a very long time). ----------------------------------------------------------------------------------------------------- @@ -234,7 +263,7 @@ As of LightGBM v4.0.0, ``setinfo()`` has been replaced by a new method, ``set_fi 3. ``error in data.table::data.table()...argument 2 is NULL`` ------------------------------------------------------------- -If you are experiencing this error when running ``lightgbm``, you may be facing the same issue reported in `#2715 `_ and later in `#2989 `_. We have seen that in some situations, using ``data.table`` 1.11.x results in this error. To get around this, you can upgrade your version of ``data.table`` to at least version 1.12.0. +If you are experiencing this error when running ``lightgbm``, you may be facing the same issue reported in `#2715 `_ and later in `#2989 `_. We have seen that some in some situations, using ``data.table`` 1.11.x results in this error. To get around this, you can upgrade your version of ``data.table`` to at least version 1.12.0. ------ @@ -260,7 +289,7 @@ Python-package This error should be solved in latest version. If you still meet this error, try to remove ``lightgbm.egg-info`` folder in your Python-package and reinstall, -or check `this thread on stackoverflow `__. +or check `this thread on stackoverflow `__. 2. Error messages: ``Cannot ... before construct dataset``. ----------------------------------------------------------- diff --git a/docs/Features.rst b/docs/Features.rst index 89b56646588f..a7db86ec2935 100644 --- a/docs/Features.rst +++ b/docs/Features.rst @@ -291,7 +291,7 @@ References .. _On Grouping for Maximum Homogeneity: https://www.tandfonline.com/doi/abs/10.1080/01621459.1958.10501479 -.. _Optimization of collective communication operations in MPICH: https://web.cels.anl.gov/~thakur/papers/ijhpca-coll.pdf +.. _Optimization of collective communication operations in MPICH: https://www.mcs.anl.gov/~thakur/papers/ijhpca-coll.pdf .. _A Communication-Efficient Parallel Algorithm for Decision Tree: http://papers.nips.cc/paper/6381-a-communication-efficient-parallel-algorithm-for-decision-tree diff --git a/docs/GPU-Performance.rst b/docs/GPU-Performance.rst index 64cd78eb4202..ab7ff4137cf8 100644 --- a/docs/GPU-Performance.rst +++ b/docs/GPU-Performance.rst @@ -194,17 +194,17 @@ following article: Huan Zhang, Si Si and Cho-Jui Hsieh. `GPU Acceleration for Large-scale Tree Boosting`_. SysML Conference, 2018. -.. _link1: https://archive.ics.uci.edu/dataset/280/higgs +.. _link1: https://archive.ics.uci.edu/ml/datasets/HIGGS -.. _link2: https://www.csie.ntu.edu.tw/~cjlin/libsvmtools/datasets/binary.html +.. _link2: http://www.csie.ntu.edu.tw/~cjlin/libsvmtools/datasets/binary.html .. _link3: https://www.kaggle.com/c/bosch-production-line-performance/data .. _link4: https://webscope.sandbox.yahoo.com/catalog.php?datatype=c -.. _link5: https://www.microsoft.com/en-us/research/project/mslr/ +.. _link5: http://research.microsoft.com/en-us/projects/mslr/ -.. _link6: https://community.amstat.org/jointscsg-section/dataexpo/dataexpo2009 +.. _link6: http://stat-computing.org/dataexpo/2009/ .. _0bb4a82: https://github.com/microsoft/LightGBM/commit/0bb4a82 diff --git a/docs/GPU-Targets.rst b/docs/GPU-Targets.rst index ab024847d82d..9c3cac7c814a 100644 --- a/docs/GPU-Targets.rst +++ b/docs/GPU-Targets.rst @@ -4,7 +4,7 @@ GPU SDK Correspondence and Device Targeting Table GPU Targets Table ================= -OpenCL is a universal massively parallel programming framework that targets multiple backends (GPU, CPU, FPGA, etc). +OpenCL is a universal massively parallel programming framework that targets to multiple backends (GPU, CPU, FPGA, etc). Basically, to use a device from a vendor, you have to install drivers from that specific vendor. Intel's and AMD's OpenCL runtime also include x86 CPU target support. NVIDIA's OpenCL runtime only supports NVIDIA GPU (no CPU support). diff --git a/docs/GPU-Tutorial.rst b/docs/GPU-Tutorial.rst index ee1d3173e556..836ab1add378 100644 --- a/docs/GPU-Tutorial.rst +++ b/docs/GPU-Tutorial.rst @@ -33,7 +33,7 @@ After installing the drivers you need to restart the server. After about 30 seconds, the server should be up again. -If you are using an AMD GPU, you should download and install the `AMDGPU-Pro`_ driver and also install packages ``ocl-icd-libopencl1`` and ``ocl-icd-opencl-dev``. +If you are using an AMD GPU, you should download and install the `AMDGPU-Pro`_ driver and also install package ``ocl-icd-libopencl1`` and ``ocl-icd-opencl-dev``. Build LightGBM -------------- diff --git a/docs/GPU-Windows.rst b/docs/GPU-Windows.rst index c4c2ca818433..36e657e5801b 100644 --- a/docs/GPU-Windows.rst +++ b/docs/GPU-Windows.rst @@ -152,7 +152,7 @@ Download `Prebuilt Boost x86_64`_ or `Prebuilt Boost i686`_ and unpack them wit Boost Compilation ----------------- -Installing Boost requires downloading Boost and installing it. +Installing Boost requires to download Boost and to install it. It takes about 10 minutes to several hours depending on your CPU speed and network speed. We will assume an installation in ``C:\boost`` and a general installation (like in Unix variants: without versioning and without type tags). @@ -279,7 +279,7 @@ Installing CMake requires one download first and then a lot of configuration for :target: ./_static/images/screenshot-downloading-cmake.png :alt: A screenshot of the binary distributions of C Make for downloading on 64 bit Windows. -- Download `CMake`_ +- Download `CMake`_ (3.8 or higher) - Install CMake diff --git a/docs/Installation-Guide.rst b/docs/Installation-Guide.rst index 62383d9f924a..412f0ec5993a 100644 --- a/docs/Installation-Guide.rst +++ b/docs/Installation-Guide.rst @@ -69,7 +69,7 @@ The ``.exe`` file will be in ``LightGBM-master/windows/x64/Release`` folder. From Command Line ***************** -1. Install `Git for Windows`_, `CMake`_ and `VS Build Tools`_ (**VS Build Tools** is not needed if **Visual Studio** (2015 or newer) is already installed). +1. Install `Git for Windows`_, `CMake`_ (3.8 or higher) and `VS Build Tools`_ (**VS Build Tools** is not needed if **Visual Studio** (2015 or newer) is already installed). 2. Run the following commands: @@ -167,7 +167,7 @@ Install Using ``Homebrew`` Build from GitHub ***************** -1. Install `CMake`_ : +1. Install `CMake`_ (3.16 or higher): .. code:: sh @@ -193,7 +193,7 @@ Build from GitHub gcc ^^^ -1. Install `CMake`_ : +1. Install `CMake`_ (3.2 or higher): .. code:: sh @@ -266,7 +266,7 @@ The ``.exe`` file will be in ``LightGBM-master/windows/x64/Release`` folder. From Command Line ----------------- -1. Install `Git for Windows`_, `CMake`_ and `VS Build Tools`_ (**VS Build Tools** is not needed if **Visual Studio** (2015 or newer) is already installed). +1. Install `Git for Windows`_, `CMake`_ (3.8 or higher) and `VS Build Tools`_ (**VS Build Tools** is not needed if **Visual Studio** (2015 or newer) is already installed). 2. Run the following commands: @@ -331,7 +331,7 @@ Apple Clang Only **Apple Clang** version 8.1 or higher is supported. -1. Install `CMake`_ : +1. Install `CMake`_ (3.16 or higher): .. code:: sh @@ -351,7 +351,7 @@ Only **Apple Clang** version 8.1 or higher is supported. gcc *** -1. Install `CMake`_ : +1. Install `CMake`_ (3.2 or higher): .. code:: sh @@ -414,7 +414,7 @@ From Command Line 1. You need to install `MS MPI`_ first. Both ``msmpisdk.msi`` and ``msmpisetup.exe`` are needed. -2. Install `Git for Windows`_, `CMake`_ and `VS Build Tools`_ (**VS Build Tools** is not needed if **Visual Studio** (2015 or newer) is already installed). +2. Install `Git for Windows`_, `CMake`_ (3.8 or higher) and `VS Build Tools`_ (**VS Build Tools** is not needed if **Visual Studio** (2015 or newer) is already installed). 3. Run the following commands: @@ -465,7 +465,7 @@ Apple Clang Only **Apple Clang** version 8.1 or higher is supported. -1. Install `CMake`_ : +1. Install `CMake`_ (3.16 or higher): .. code:: sh @@ -497,7 +497,7 @@ Only **Apple Clang** version 8.1 or higher is supported. gcc *** -1. Install `CMake`_ : +1. Install `CMake`_ (3.2 or higher): .. code:: sh @@ -547,7 +547,7 @@ The following dependencies should be installed before compilation: The following Debian packages should provide necessary Boost libraries: ``libboost-dev``, ``libboost-system-dev``, ``libboost-filesystem-dev``. -- **CMake** +- **CMake** 3.2 or later. To build LightGBM GPU version, run the following commands: @@ -575,7 +575,7 @@ If you use **MinGW**, the build procedure is similar to the build on Linux. Refe Following procedure is for the **MSVC** (Microsoft Visual C++) build. -1. Install `Git for Windows`_, `CMake`_ and `VS Build Tools`_ (**VS Build Tools** is not needed if **Visual Studio** (2015 or newer) is installed). +1. Install `Git for Windows`_, `CMake`_ (3.8 or higher) and `VS Build Tools`_ (**VS Build Tools** is not needed if **Visual Studio** (2015 or newer) is installed). 2. Install **OpenCL** for Windows. The installation depends on the brand (NVIDIA, AMD, Intel) of your GPU card. @@ -635,9 +635,9 @@ On Linux a CUDA version of LightGBM can be built using **CUDA**, **CMake** and * The following dependencies should be installed before compilation: -- **CUDA** 11.0 or later libraries. Please refer to `this detailed guide`_. Pay great attention to the minimum required versions of host compilers listed in the table from that guide and use only recommended versions of compilers. +- **CUDA** 10.0 or later libraries. Please refer to `this detailed guide`_. Pay great attention to the minimum required versions of host compilers listed in the table from that guide and use only recommended versions of compilers. -- **CMake** +- **CMake** 3.16 or later. To build LightGBM CUDA version, run the following commands: @@ -700,7 +700,7 @@ On Windows a Java wrapper of LightGBM can be built using **Java**, **SWIG**, **C VS Build Tools ************** -1. Install `Git for Windows`_, `CMake`_ and `VS Build Tools`_ (**VS Build Tools** is not needed if **Visual Studio** (2015 or newer) is already installed). +1. Install `Git for Windows`_, `CMake`_ (3.8 or higher) and `VS Build Tools`_ (**VS Build Tools** is not needed if **Visual Studio** (2015 or newer) is already installed). 2. Install `SWIG`_ and **Java** (also make sure that ``JAVA_HOME`` is set properly). @@ -779,7 +779,7 @@ Apple Clang Only **Apple Clang** version 8.1 or higher is supported. -1. Install `CMake`_ : +1. Install `CMake`_ (3.16 or higher): .. code:: sh @@ -805,7 +805,7 @@ Only **Apple Clang** version 8.1 or higher is supported. gcc *** -1. Install `CMake`_ : +1. Install `CMake`_ (3.2 or higher): .. code:: sh @@ -839,7 +839,7 @@ Windows On Windows, C++ unit tests of LightGBM can be built using **CMake** and **VS Build Tools**. -1. Install `Git for Windows`_, `CMake`_ and `VS Build Tools`_ (**VS Build Tools** is not needed if **Visual Studio** (2015 or newer) is already installed). +1. Install `Git for Windows`_, `CMake`_ (3.8 or higher) and `VS Build Tools`_ (**VS Build Tools** is not needed if **Visual Studio** (2015 or newer) is already installed). 2. Run the following commands: @@ -884,7 +884,7 @@ Apple Clang Only **Apple Clang** version 8.1 or higher is supported. -1. Install `CMake`_ : +1. Install `CMake`_ (3.16 or higher): .. code:: sh @@ -904,7 +904,7 @@ Only **Apple Clang** version 8.1 or higher is supported. gcc *** -1. Install `CMake`_ : +1. Install `CMake`_ (3.2 or higher): .. code:: sh @@ -950,7 +950,7 @@ gcc .. _RDMA: https://en.wikipedia.org/wiki/Remote_direct_memory_access -.. _MS MPI: https://learn.microsoft.com/en-us/message-passing-interface/microsoft-mpi-release-notes +.. _MS MPI: https://docs.microsoft.com/en-us/message-passing-interface/microsoft-mpi-release-notes .. _Open MPI: https://www.open-mpi.org/ @@ -960,7 +960,7 @@ gcc .. _Boost Binaries: https://sourceforge.net/projects/boost/files/boost-binaries/ -.. _SWIG: https://www.swig.org/download.html +.. _SWIG: http://www.swig.org/download.html .. _this detailed guide: https://docs.nvidia.com/cuda/cuda-installation-guide-linux/index.html diff --git a/docs/Parallel-Learning-Guide.rst b/docs/Parallel-Learning-Guide.rst index cbc7b1012b98..e1857034e499 100644 --- a/docs/Parallel-Learning-Guide.rst +++ b/docs/Parallel-Learning-Guide.rst @@ -384,6 +384,8 @@ From the point forward, you can use any of the following methods to save the Boo Kubeflow ^^^^^^^^ +`Kubeflow Fairing`_ supports LightGBM distributed training. `These examples`_ show how to get started with LightGBM and Kubeflow Fairing in a hybrid cloud environment. + Kubeflow users can also use the `Kubeflow XGBoost Operator`_ for machine learning workflows with LightGBM. You can see `this example`_ for more details. Kubeflow integrations for LightGBM are not maintained by LightGBM's maintainers. @@ -518,7 +520,7 @@ See `the mars documentation`_ for usage examples. .. _the Dask DataFrame documentation: https://docs.dask.org/en/latest/dataframe.html -.. _the Dask prediction example: https://github.com/microsoft/LightGBM/blob/master/examples/python-guide/dask/prediction.py +.. _the Dask prediction example: https://github.com/microsoft/lightgbm/tree/master/examples/python-guide/dask/prediction.py .. _the Dask worker documentation: https://distributed.dask.org/en/stable/worker-memory.html @@ -526,6 +528,10 @@ See `the mars documentation`_ for usage examples. .. _these Dask examples: https://github.com/microsoft/lightgbm/tree/master/examples/python-guide/dask +.. _Kubeflow Fairing: https://www.kubeflow.org/docs/components/fairing/fairing-overview + +.. _These examples: https://github.com/kubeflow/fairing/tree/master/examples/lightgbm + .. _Kubeflow XGBoost Operator: https://github.com/kubeflow/xgboost-operator .. _this example: https://github.com/kubeflow/xgboost-operator/tree/master/config/samples/lightgbm-dist @@ -536,7 +542,7 @@ See `the mars documentation`_ for usage examples. .. _lightgbm_ray: https://github.com/ray-project/lightgbm_ray -.. _Ray: https://www.ray.io/ +.. _Ray: https://ray.io/ .. _the lightgbm_ray documentation: https://docs.ray.io/en/latest/tune/api_docs/integration.html#lightgbm-tune-integration-lightgbm diff --git a/docs/Parameters.rst b/docs/Parameters.rst index 341cdd487c71..86104ba5be55 100644 --- a/docs/Parameters.rst +++ b/docs/Parameters.rst @@ -14,6 +14,10 @@ This page contains descriptions of all parameters in LightGBM. - `Parameters Tuning <./Parameters-Tuning.rst>`__ +**External Links** + +- `Laurae++ Interactive Documentation`_ + Parameters Format ----------------- @@ -115,7 +119,7 @@ Core Parameters - ranking application - - ``lambdarank``, `lambdarank `__ objective. `label_gain <#label_gain>`__ can be used to set the gain (weight) of ``int`` label and all values in ``label`` must be smaller than number of elements in ``label_gain`` + - ``lambdarank``, `lambdarank `__ objective. `label_gain <#label_gain>`__ can be used to set the gain (weight) of ``int`` label and all values in ``label`` must be smaller than number of elements in ``label_gain`` - ``rank_xendcg``, `XE_NDCG_MART `__ ranking objective function, aliases: ``xendcg``, ``xe_ndcg``, ``xe_ndcg_mart``, ``xendcg_mart`` @@ -532,15 +536,15 @@ Learning Control Parameters - ``basic``, the most basic monotone constraints method. It does not slow the library at all, but over-constrains the predictions - - ``intermediate``, a `more advanced method `__, which may slow the library very slightly. However, this method is much less constraining than the basic method and should significantly improve the results + - ``intermediate``, a `more advanced method `__, which may slow the library very slightly. However, this method is much less constraining than the basic method and should significantly improve the results - - ``advanced``, an `even more advanced method `__, which may slow the library. However, this method is even less constraining than the intermediate method and should again significantly improve the results + - ``advanced``, an `even more advanced method `__, which may slow the library. However, this method is even less constraining than the intermediate method and should again significantly improve the results - ``monotone_penalty`` :raw-html:`🔗︎`, default = ``0.0``, type = double, aliases: ``monotone_splits_penalty``, ``ms_penalty``, ``mc_penalty``, constraints: ``monotone_penalty >= 0.0`` - used only if ``monotone_constraints`` is set - - `monotone penalty `__: a penalization parameter X forbids any monotone splits on the first X (rounded down) level(s) of the tree. The penalty applied to monotone splits on a given depth is a continuous, increasing function the penalization parameter + - `monotone penalty `__: a penalization parameter X forbids any monotone splits on the first X (rounded down) level(s) of the tree. The penalty applied to monotone splits on a given depth is a continuous, increasing function the penalization parameter - if ``0.0`` (the default), no penalization is applied @@ -560,7 +564,7 @@ Learning Control Parameters - **Note**: the forced split logic will be ignored, if the split makes gain worse - - see `this file `__ as an example + - see `this file `__ as an example - ``refit_decay_rate`` :raw-html:`🔗︎`, default = ``0.9``, type = double, constraints: ``0.0 <= refit_decay_rate <= 1.0`` @@ -766,7 +770,7 @@ Dataset Parameters - ``enable_bundle`` :raw-html:`🔗︎`, default = ``true``, type = bool, aliases: ``is_enable_bundle``, ``bundle`` - - set this to ``false`` to disable Exclusive Feature Bundling (EFB), which is described in `LightGBM: A Highly Efficient Gradient Boosting Decision Tree `__ + - set this to ``false`` to disable Exclusive Feature Bundling (EFB), which is described in `LightGBM: A Highly Efficient Gradient Boosting Decision Tree `__ - **Note**: disabling this may cause the slow training speed for sparse datasets @@ -890,7 +894,7 @@ Dataset Parameters - ``.json`` file should contain an array of objects, each containing the word ``feature`` (integer feature index) and ``bin_upper_bound`` (array of thresholds for binning) - - see `this file `__ as an example + - see `this file `__ as an example - ``save_binary`` :raw-html:`🔗︎`, default = ``false``, type = bool, aliases: ``is_save_binary``, ``is_save_binary_file`` @@ -957,7 +961,7 @@ Predict Parameters - produces ``#features + 1`` values where the last value is the expected value of the model output over the training data - - **Note**: if you want to get more explanation for your model's predictions using SHAP values like SHAP interaction values, you can install `shap package `__ + - **Note**: if you want to get more explanation for your model's predictions using SHAP values like SHAP interaction values, you can install `shap package `__ - **Note**: unlike the shap package, with ``predict_contrib`` we return a matrix with an extra column, where the last column is the expected value @@ -1376,3 +1380,5 @@ If the name of data file is ``train.txt``, the query file should be named as ``t In this case, LightGBM will load the query file automatically if it exists. Also, you can include query/group id column in your data file. Please refer to the ``group_column`` `parameter <#group_column>`__ in above. + +.. _Laurae++ Interactive Documentation: https://sites.google.com/view/lauraepp/parameters diff --git a/docs/Quick-Start.rst b/docs/Quick-Start.rst index 4a372db6736e..04e64beb1281 100644 --- a/docs/Quick-Start.rst +++ b/docs/Quick-Start.rst @@ -50,7 +50,7 @@ The parameters format is ``key1=value1 key2=value2 ...``. Parameters can be set both in config file and command line. If one parameter appears in both command line and config file, LightGBM will use the parameter from the command line. -The most important parameters which new users should take a look at are located into `Core Parameters <./Parameters.rst#core-parameters>`__ +The most important parameters which new users should take a look to are located into `Core Parameters <./Parameters.rst#core-parameters>`__ and the top of `Learning Control Parameters <./Parameters.rst#learning-control-parameters>`__ sections of the full detailed list of `LightGBM's parameters <./Parameters.rst>`__. @@ -85,4 +85,4 @@ Examples .. _LibSVM: https://www.csie.ntu.edu.tw/~cjlin/libsvm/ -.. _Expo data: https://community.amstat.org/jointscsg-section/dataexpo/dataexpo2009 +.. _Expo data: http://stat-computing.org/dataexpo/2009/ diff --git a/docs/env.yml b/docs/env.yml index 351ce1f0fae0..69bcd92fbdc3 100644 --- a/docs/env.yml +++ b/docs/env.yml @@ -9,9 +9,9 @@ dependencies: - r-data.table=1.14.2 - r-jsonlite=1.7.2 - r-knitr=1.37 - - r-markdown - r-matrix=1.4_0 - r-pkgdown=1.6.1 + - r-rmarkdown=2.11 - r-roxygen2=7.2.1 - scikit-learn - sphinx diff --git a/docs/gcc-Tips.rst b/docs/gcc-Tips.rst index 938aee407f7d..ad5981855d20 100644 --- a/docs/gcc-Tips.rst +++ b/docs/gcc-Tips.rst @@ -25,6 +25,8 @@ You can find more details on the experimentation below: - `Laurae's Benchmark Master Data (Interactive) `__ +- `Kaggle Paris Meetup #12 Slides `__ + The image below compares the runtime for training with different compiler options to a baseline using LightGBM compiled with ``-O2 --mtune=core2``. All three options are faster than that baseline. The best performance was achieved with ``-O3 --mtune=native``. .. image:: ./_static/images/gcc-comparison-2.png diff --git a/include/LightGBM/arrow.h b/include/LightGBM/arrow.h deleted file mode 100644 index 767da12a9809..000000000000 --- a/include/LightGBM/arrow.h +++ /dev/null @@ -1,259 +0,0 @@ -/*! - * Copyright (c) 2023 Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See LICENSE file in the project root for license information. - * - * Author: Oliver Borchert - */ - -#ifndef LIGHTGBM_ARROW_H_ -#define LIGHTGBM_ARROW_H_ - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -/* -------------------------------------- C DATA INTERFACE ------------------------------------- */ -// The C data interface is taken from -// https://arrow.apache.org/docs/format/CDataInterface.html#structure-definitions -// and is available under Apache License 2.0 (https://www.apache.org/licenses/LICENSE-2.0). - -#ifdef __cplusplus -extern "C" { -#endif - -#define ARROW_FLAG_DICTIONARY_ORDERED 1 -#define ARROW_FLAG_NULLABLE 2 -#define ARROW_FLAG_MAP_KEYS_SORTED 4 - -struct ArrowSchema { - // Array type description - const char* format; - const char* name; - const char* metadata; - int64_t flags; - int64_t n_children; - struct ArrowSchema** children; - struct ArrowSchema* dictionary; - - // Release callback - void (*release)(struct ArrowSchema*); - // Opaque producer-specific data - void* private_data; -}; - -struct ArrowArray { - // Array data description - int64_t length; - int64_t null_count; - int64_t offset; - int64_t n_buffers; - int64_t n_children; - const void** buffers; - struct ArrowArray** children; - struct ArrowArray* dictionary; - - // Release callback - void (*release)(struct ArrowArray*); - // Opaque producer-specific data - void* private_data; -}; - -#ifdef __cplusplus -} -#endif - -/* --------------------------------------------------------------------------------------------- */ -/* CHUNKED ARRAY */ -/* --------------------------------------------------------------------------------------------- */ - -namespace LightGBM { - -/** - * @brief Arrow array-like container for a list of Arrow arrays. - */ -class ArrowChunkedArray { - /* List of length `n` for `n` chunks containing the individual Arrow arrays. */ - std::vector chunks_; - /* Schema for all chunks. */ - const ArrowSchema* schema_; - /* List of length `n + 1` for `n` chunks containing the offsets for each chunk. */ - std::vector chunk_offsets_; - - inline void construct_chunk_offsets() { - chunk_offsets_.reserve(chunks_.size() + 1); - chunk_offsets_.emplace_back(0); - for (size_t k = 0; k < chunks_.size(); ++k) { - chunk_offsets_.emplace_back(chunks_[k]->length + chunk_offsets_.back()); - } - } - - public: - /** - * @brief Construct a new Arrow Chunked Array object. - * - * @param chunks A list with the chunks. - * @param schema The schema for all chunks. - */ - inline ArrowChunkedArray(std::vector chunks, const ArrowSchema* schema) { - chunks_ = chunks; - schema_ = schema; - construct_chunk_offsets(); - } - - /** - * @brief Construct a new Arrow Chunked Array object. - * - * @param n_chunks The number of chunks. - * @param chunks A C-style array containing the chunks. - * @param schema The schema for all chunks. - */ - inline ArrowChunkedArray(int64_t n_chunks, - const struct ArrowArray* chunks, - const struct ArrowSchema* schema) { - chunks_.reserve(n_chunks); - for (auto k = 0; k < n_chunks; ++k) { - if (chunks[k].length == 0) continue; - chunks_.push_back(&chunks[k]); - } - schema_ = schema; - construct_chunk_offsets(); - } - - /** - * @brief Get the length of the chunked array. - * This method returns the cumulative length of all chunks. - * Complexity: O(1) - * - * @return int64_t The number of elements in the chunked array. - */ - inline int64_t get_length() const { return chunk_offsets_.back(); } - - /* ----------------------------------------- ITERATOR ---------------------------------------- */ - template - class Iterator { - using getter_fn = std::function; - - /* Reference to the chunked array that this iterator iterates over. */ - const ArrowChunkedArray& array_; - /* Function to fetch the value at a certain index from a single chunk. */ - getter_fn get_; - /* The chunk the iterator currently points to. */ - int64_t ptr_chunk_; - /* The index inside the current chunk that the iterator points to. */ - int64_t ptr_offset_; - - public: - using iterator_category = std::random_access_iterator_tag; - using difference_type = int64_t; - using value_type = T; - using pointer = value_type*; - using reference = value_type&; - - /** - * @brief Construct a new Iterator object. - * - * @param array Reference to the chunked array to iterator over. - * @param get Function to fetch the value at a certain index from a single chunk. - * @param ptr_chunk The index of the chunk to whose first index the iterator points to. - */ - Iterator(const ArrowChunkedArray& array, getter_fn get, int64_t ptr_chunk); - - T operator*() const; - template - T operator[](I idx) const; - - Iterator& operator++(); - Iterator& operator--(); - Iterator& operator+=(int64_t c); - - template - friend bool operator==(const Iterator& a, const Iterator& b); - template - friend bool operator!=(const Iterator& a, const Iterator& b); - template - friend int64_t operator-(const Iterator& a, const Iterator& b); - }; - - /** - * @brief Obtain an iterator to the beginning of the chunked array. - * - * @tparam T The value type of the iterator. May be any primitive type. - * @return Iterator The iterator. - */ - template - inline Iterator begin() const; - - /** - * @brief Obtain an iterator to the beginning of the chunked array. - * - * @tparam T The value type of the iterator. May be any primitive type. - * @return Iterator The iterator. - */ - template - inline Iterator end() const; - - template - friend int64_t operator-(const Iterator& a, const Iterator& b); -}; - -/** - * @brief Arrow container for a list of chunked arrays. - */ -class ArrowTable { - std::vector columns_; - - public: - /** - * @brief Construct a new Arrow Table object. - * - * @param n_chunks The number of chunks. - * @param chunks A C-style array containing the chunks. - * @param schema The schema for all chunks. - */ - inline ArrowTable(int64_t n_chunks, const ArrowArray* chunks, const ArrowSchema* schema) { - columns_.reserve(schema->n_children); - for (int64_t j = 0; j < schema->n_children; ++j) { - std::vector children_chunks; - children_chunks.reserve(n_chunks); - for (int64_t k = 0; k < n_chunks; ++k) { - if (chunks[k].length == 0) continue; - children_chunks.push_back(chunks[k].children[j]); - } - columns_.emplace_back(children_chunks, schema->children[j]); - } - } - - /** - * @brief Get the number of rows in the table. - * - * @return int64_t The number of rows. - */ - inline int64_t get_num_rows() const { return columns_.front().get_length(); } - - /** - * @brief Get the number of columns of this table. - * - * @return int64_t The column count. - */ - inline int64_t get_num_columns() const { return columns_.size(); } - - /** - * @brief Get the column at a particular index. - * - * @param idx The index of the column, must me in the range `[0, num_columns)`. - * @return const ArrowChunkedArray& The chunked array for the child at the provided index. - */ - inline const ArrowChunkedArray& get_column(size_t idx) const { return this->columns_[idx]; } -}; - -} // namespace LightGBM - -#include "arrow.tpp" - -#endif /* LIGHTGBM_ARROW_H_ */ diff --git a/include/LightGBM/arrow.tpp b/include/LightGBM/arrow.tpp deleted file mode 100644 index 8d1ce4f4c0c1..000000000000 --- a/include/LightGBM/arrow.tpp +++ /dev/null @@ -1,190 +0,0 @@ -#include - -#ifndef ARROW_TPP_ -#define ARROW_TPP_ - -namespace LightGBM { - -/** - * @brief Obtain a function to access an index from an Arrow array. - * - * @tparam T The return type of the function, must be a primitive type. - * @param dtype The Arrow format string describing the datatype of the Arrow array. - * @return std::function The index accessor function. - */ -template -std::function get_index_accessor(const char* dtype); - -/* ---------------------------------- ITERATOR INITIALIZATION ---------------------------------- */ - -template -inline ArrowChunkedArray::Iterator ArrowChunkedArray::begin() const { - return ArrowChunkedArray::Iterator(*this, get_index_accessor(schema_->format), 0); -} - -template -inline ArrowChunkedArray::Iterator ArrowChunkedArray::end() const { - return ArrowChunkedArray::Iterator(*this, get_index_accessor(schema_->format), - chunk_offsets_.size() - 1); -} - -/* ---------------------------------- ITERATOR IMPLEMENTATION ---------------------------------- */ - -template -ArrowChunkedArray::Iterator::Iterator(const ArrowChunkedArray& array, - getter_fn get, - int64_t ptr_chunk) - : array_(array), get_(get), ptr_chunk_(ptr_chunk) { - this->ptr_offset_ = 0; -} - -template -T ArrowChunkedArray::Iterator::operator*() const { - auto chunk = array_.chunks_[ptr_chunk_]; - return static_cast(get_(chunk, ptr_offset_)); -} - -template -template -T ArrowChunkedArray::Iterator::operator[](I idx) const { - auto it = std::lower_bound(array_.chunk_offsets_.begin(), array_.chunk_offsets_.end(), idx, - [](int64_t a, int64_t b) { return a <= b; }); - - auto chunk_idx = std::distance(array_.chunk_offsets_.begin() + 1, it); - auto chunk = array_.chunks_[chunk_idx]; - - auto ptr_offset = static_cast(idx) - array_.chunk_offsets_[chunk_idx]; - return static_cast(get_(chunk, ptr_offset)); -} - -template -ArrowChunkedArray::Iterator& ArrowChunkedArray::Iterator::operator++() { - if (ptr_offset_ + 1 >= array_.chunks_[ptr_chunk_]->length) { - ptr_offset_ = 0; - ptr_chunk_++; - } else { - ptr_offset_++; - } - return *this; -} - -template -ArrowChunkedArray::Iterator& ArrowChunkedArray::Iterator::operator--() { - if (ptr_offset_ == 0) { - ptr_chunk_--; - ptr_offset_ = array_.chunks_[ptr_chunk_]->length - 1; - } else { - ptr_chunk_--; - } - return *this; -} - -template -ArrowChunkedArray::Iterator& ArrowChunkedArray::Iterator::operator+=(int64_t c) { - while (ptr_offset_ + c >= array_.chunks_[ptr_chunk_]->length) { - c -= array_.chunks_[ptr_chunk_]->length - ptr_offset_; - ptr_offset_ = 0; - ptr_chunk_++; - } - ptr_offset_ += c; - return *this; -} - -template -bool operator==(const ArrowChunkedArray::Iterator& a, const ArrowChunkedArray::Iterator& b) { - return a.ptr_chunk_ == b.ptr_chunk_ && a.ptr_offset_ == b.ptr_offset_; -} - -template -bool operator!=(const ArrowChunkedArray::Iterator& a, const ArrowChunkedArray::Iterator& b) { - return a.ptr_chunk_ != b.ptr_chunk_ || a.ptr_offset_ != b.ptr_offset_; -} - -template -int64_t operator-(const ArrowChunkedArray::Iterator& a, - const ArrowChunkedArray::Iterator& b) { - auto full_offset_a = a.array_.chunk_offsets_[a.ptr_chunk_] + a.ptr_offset_; - auto full_offset_b = b.array_.chunk_offsets_[b.ptr_chunk_] + b.ptr_offset_; - return full_offset_a - full_offset_b; -} - -/* --------------------------------------- INDEX ACCESSOR -------------------------------------- */ - -/** - * @brief The value of "no value" for a primitive type. - * - * @tparam T The type for which the missing value is defined. - * @return T The missing value. - */ -template -inline T arrow_primitive_missing_value() { - return 0; -} - -template <> -inline double arrow_primitive_missing_value() { - return std::numeric_limits::quiet_NaN(); -} - -template <> -inline float arrow_primitive_missing_value() { - return std::numeric_limits::quiet_NaN(); -} - -template -struct ArrayIndexAccessor { - V operator()(const ArrowArray* array, size_t idx) { - auto buffer_idx = idx + array->offset; - - // For primitive types, buffer at idx 0 provides validity, buffer at idx 1 data, see: - // https://arrow.apache.org/docs/format/Columnar.html#buffer-listing-for-each-layout - auto validity = static_cast(array->buffers[0]); - - // Take return value from data buffer conditional on the validity of the index: - // - The structure of validity bitmasks is taken from here: - // https://arrow.apache.org/docs/format/Columnar.html#validity-bitmaps - // - If the bitmask is NULL, all indices are valid - if (validity == nullptr || (validity[buffer_idx / 8] & (1 << (buffer_idx % 8)))) { - // In case the index is valid, we take it from the data buffer - auto data = static_cast(array->buffers[1]); - return static_cast(data[buffer_idx]); - } - - // In case the index is not valid, we return a default value - return arrow_primitive_missing_value(); - } -}; - -template -std::function get_index_accessor(const char* dtype) { - // Mapping obtained from: - // https://arrow.apache.org/docs/format/CDataInterface.html#data-type-description-format-strings - switch (dtype[0]) { - case 'c': - return ArrayIndexAccessor(); - case 'C': - return ArrayIndexAccessor(); - case 's': - return ArrayIndexAccessor(); - case 'S': - return ArrayIndexAccessor(); - case 'i': - return ArrayIndexAccessor(); - case 'I': - return ArrayIndexAccessor(); - case 'l': - return ArrayIndexAccessor(); - case 'L': - return ArrayIndexAccessor(); - case 'f': - return ArrayIndexAccessor(); - case 'g': - return ArrayIndexAccessor(); - default: - throw std::invalid_argument("unsupported Arrow datatype"); - } -} - -} // namespace LightGBM - -#endif diff --git a/include/LightGBM/bin.h b/include/LightGBM/bin.h index de1bb6eb94ed..ffb8f2844843 100644 --- a/include/LightGBM/bin.h +++ b/include/LightGBM/bin.h @@ -63,7 +63,8 @@ inline static void Int32HistogramSumReducer(const char* src, char* dst, int type const int64_t* src_ptr = reinterpret_cast(src); int64_t* dst_ptr = reinterpret_cast(dst); const comm_size_t steps = (len + (type_size * 2) - 1) / (type_size * 2); - #pragma omp parallel for schedule(static) num_threads(OMP_NUM_THREADS()) + const int num_threads = OMP_NUM_THREADS(); + #pragma omp parallel for schedule(static) num_threads(num_threads) for (comm_size_t i = 0; i < steps; ++i) { dst_ptr[i] += src_ptr[i]; } @@ -73,7 +74,8 @@ inline static void Int16HistogramSumReducer(const char* src, char* dst, int type const int32_t* src_ptr = reinterpret_cast(src); int32_t* dst_ptr = reinterpret_cast(dst); const comm_size_t steps = (len + (type_size * 2) - 1) / (type_size * 2); - #pragma omp parallel for schedule(static) num_threads(OMP_NUM_THREADS()) + const int num_threads = OMP_NUM_THREADS(); + #pragma omp parallel for schedule(static) num_threads(num_threads) for (comm_size_t i = 0; i < steps; ++i) { dst_ptr[i] += src_ptr[i]; } diff --git a/include/LightGBM/c_api.h b/include/LightGBM/c_api.h index b43f096c31ee..bba46a02a492 100644 --- a/include/LightGBM/c_api.h +++ b/include/LightGBM/c_api.h @@ -13,7 +13,6 @@ #ifndef LIGHTGBM_C_API_H_ #define LIGHTGBM_C_API_H_ -#include #include #ifdef __cplusplus @@ -438,23 +437,6 @@ LIGHTGBM_C_EXPORT int LGBM_DatasetCreateFromMats(int32_t nmat, const DatasetHandle reference, DatasetHandle* out); -/*! - * \brief Create dataset from Arrow. - * \param n_chunks The number of Arrow arrays passed to this function - * \param chunks Pointer to the list of Arrow arrays - * \param schema Pointer to the schema of all Arrow arrays - * \param parameters Additional parameters - * \param reference Used to align bin mapper with other dataset, nullptr means isn't used - * \param[out] out Created dataset - * \return 0 when succeed, -1 when failure happens - */ -LIGHTGBM_C_EXPORT int LGBM_DatasetCreateFromArrow(int64_t n_chunks, - const ArrowArray* chunks, - const ArrowSchema* schema, - const char* parameters, - const DatasetHandle reference, - DatasetHandle *out); - /*! * \brief Create subset of a data. * \param handle Handle of full dataset @@ -555,25 +537,6 @@ LIGHTGBM_C_EXPORT int LGBM_DatasetSetField(DatasetHandle handle, int num_element, int type); -/*! - * \brief Set vector to a content in info. - * \note - * - \a group converts input datatype into ``int32``; - * - \a label and \a weight convert input datatype into ``float32``; - * - \a init_score converts input datatype into ``float64``. - * \param handle Handle of dataset - * \param field_name Field name, can be \a label, \a weight, \a init_score, \a group - * \param n_chunks The number of Arrow arrays passed to this function - * \param chunks Pointer to the list of Arrow arrays - * \param schema Pointer to the schema of all Arrow arrays - * \return 0 when succeed, -1 when failure happens - */ -LIGHTGBM_C_EXPORT int LGBM_DatasetSetFieldFromArrow(DatasetHandle handle, - const char* field_name, - int64_t n_chunks, - const ArrowArray* chunks, - const ArrowSchema* schema); - /*! * \brief Get info vector from dataset. * \param handle Handle of dataset @@ -1417,40 +1380,6 @@ LIGHTGBM_C_EXPORT int LGBM_BoosterPredictForMats(BoosterHandle handle, int64_t* out_len, double* out_result); -/*! - * \brief Make prediction for a new dataset. - * \note - * You should pre-allocate memory for ``out_result``: - * - for normal and raw score, its length is equal to ``num_class * num_data``; - * - for leaf index, its length is equal to ``num_class * num_data * num_iteration``; - * - for feature contributions, its length is equal to ``num_class * num_data * (num_feature + 1)``. - * \param handle Handle of booster - * \param n_chunks The number of Arrow arrays passed to this function - * \param chunks Pointer to the list of Arrow arrays - * \param schema Pointer to the schema of all Arrow arrays - * \param predict_type What should be predicted - * - ``C_API_PREDICT_NORMAL``: normal prediction, with transform (if needed); - * - ``C_API_PREDICT_RAW_SCORE``: raw score; - * - ``C_API_PREDICT_LEAF_INDEX``: leaf index; - * - ``C_API_PREDICT_CONTRIB``: feature contributions (SHAP values) - * \param start_iteration Start index of the iteration to predict - * \param num_iteration Number of iteration for prediction, <= 0 means no limit - * \param parameter Other parameters for prediction, e.g. early stopping for prediction - * \param[out] out_len Length of output result - * \param[out] out_result Pointer to array with predictions - * \return 0 when succeed, -1 when failure happens - */ -LIGHTGBM_C_EXPORT int LGBM_BoosterPredictForArrow(BoosterHandle handle, - int64_t n_chunks, - const ArrowArray* chunks, - const ArrowSchema* schema, - int predict_type, - int start_iteration, - int num_iteration, - const char* parameter, - int64_t* out_len, - double* out_result); - /*! * \brief Save model into file. * \param handle Handle of booster @@ -1595,20 +1524,6 @@ LIGHTGBM_C_EXPORT int LGBM_NetworkInitWithFunctions(int num_machines, void* reduce_scatter_ext_fun, void* allgather_ext_fun); -/*! - * \brief Set maximum number of threads used by LightGBM routines in this process. - * \param num_threads maximum number of threads used by LightGBM. -1 means defaulting to omp_get_num_threads(). - * \return 0 when succeed, -1 when failure happens - */ -LIGHTGBM_C_EXPORT int LGBM_SetMaxThreads(int num_threads); - -/*! - * \brief Get current maximum number of threads used by LightGBM routines in this process. - * \param[out] out current maximum number of threads used by LightGBM. -1 means defaulting to omp_get_num_threads(). - * \return 0 when succeed, -1 when failure happens - */ -LIGHTGBM_C_EXPORT int LGBM_GetMaxThreads(int* out); - #if !defined(__cplusplus) && (!defined(__STDC__) || (__STDC_VERSION__ < 199901L)) /*! \brief Inline specifier no-op in C using standards before C99. */ #define INLINE_FUNCTION diff --git a/include/LightGBM/config.h b/include/LightGBM/config.h index 6500cb77272d..6d61bc764924 100644 --- a/include/LightGBM/config.h +++ b/include/LightGBM/config.h @@ -150,7 +150,7 @@ struct Config { // descl2 = ``cross_entropy_lambda``, alternative parameterization of cross-entropy, aliases: ``xentlambda`` // descl2 = label is anything in interval [0, 1] // desc = ranking application - // descl2 = ``lambdarank``, `lambdarank `__ objective. `label_gain <#label_gain>`__ can be used to set the gain (weight) of ``int`` label and all values in ``label`` must be smaller than number of elements in ``label_gain`` + // descl2 = ``lambdarank``, `lambdarank `__ objective. `label_gain <#label_gain>`__ can be used to set the gain (weight) of ``int`` label and all values in ``label`` must be smaller than number of elements in ``label_gain`` // descl2 = ``rank_xendcg``, `XE_NDCG_MART `__ ranking objective function, aliases: ``xendcg``, ``xe_ndcg``, ``xe_ndcg_mart``, ``xendcg_mart`` // descl2 = ``rank_xendcg`` is faster than and achieves the similar performance as ``lambdarank`` // descl2 = label should be ``int`` type, and larger number represents the higher relevance (e.g. 0:bad, 1:fair, 2:good, 3:perfect) @@ -501,14 +501,14 @@ struct Config { // desc = used only if ``monotone_constraints`` is set // desc = monotone constraints method // descl2 = ``basic``, the most basic monotone constraints method. It does not slow the library at all, but over-constrains the predictions - // descl2 = ``intermediate``, a `more advanced method `__, which may slow the library very slightly. However, this method is much less constraining than the basic method and should significantly improve the results - // descl2 = ``advanced``, an `even more advanced method `__, which may slow the library. However, this method is even less constraining than the intermediate method and should again significantly improve the results + // descl2 = ``intermediate``, a `more advanced method `__, which may slow the library very slightly. However, this method is much less constraining than the basic method and should significantly improve the results + // descl2 = ``advanced``, an `even more advanced method `__, which may slow the library. However, this method is even less constraining than the intermediate method and should again significantly improve the results std::string monotone_constraints_method = "basic"; // alias = monotone_splits_penalty, ms_penalty, mc_penalty // check = >=0.0 // desc = used only if ``monotone_constraints`` is set - // desc = `monotone penalty `__: a penalization parameter X forbids any monotone splits on the first X (rounded down) level(s) of the tree. The penalty applied to monotone splits on a given depth is a continuous, increasing function the penalization parameter + // desc = `monotone penalty `__: a penalization parameter X forbids any monotone splits on the first X (rounded down) level(s) of the tree. The penalty applied to monotone splits on a given depth is a continuous, increasing function the penalization parameter // desc = if ``0.0`` (the default), no penalization is applied double monotone_penalty = 0.0; @@ -524,7 +524,7 @@ struct Config { // desc = ``.json`` file can be arbitrarily nested, and each split contains ``feature``, ``threshold`` fields, as well as ``left`` and ``right`` fields representing subsplits // desc = categorical splits are forced in a one-hot fashion, with ``left`` representing the split containing the feature value and ``right`` representing other values // desc = **Note**: the forced split logic will be ignored, if the split makes gain worse - // desc = see `this file `__ as an example + // desc = see `this file `__ as an example std::string forcedsplits_filename = ""; // check = >=0.0 @@ -683,7 +683,7 @@ struct Config { bool is_enable_sparse = true; // alias = is_enable_bundle, bundle - // desc = set this to ``false`` to disable Exclusive Feature Bundling (EFB), which is described in `LightGBM: A Highly Efficient Gradient Boosting Decision Tree `__ + // desc = set this to ``false`` to disable Exclusive Feature Bundling (EFB), which is described in `LightGBM: A Highly Efficient Gradient Boosting Decision Tree `__ // desc = **Note**: disabling this may cause the slow training speed for sparse datasets bool enable_bundle = true; @@ -770,7 +770,7 @@ struct Config { // desc = path to a ``.json`` file that specifies bin upper bounds for some or all features // desc = ``.json`` file should contain an array of objects, each containing the word ``feature`` (integer feature index) and ``bin_upper_bound`` (array of thresholds for binning) - // desc = see `this file `__ as an example + // desc = see `this file `__ as an example std::string forcedbins_filename = ""; // [no-save] @@ -826,7 +826,7 @@ struct Config { // desc = used only in ``prediction`` task // desc = set this to ``true`` to estimate `SHAP values `__, which represent how each feature contributes to each prediction // desc = produces ``#features + 1`` values where the last value is the expected value of the model output over the training data - // desc = **Note**: if you want to get more explanation for your model's predictions using SHAP values like SHAP interaction values, you can install `shap package `__ + // desc = **Note**: if you want to get more explanation for your model's predictions using SHAP values like SHAP interaction values, you can install `shap package `__ // desc = **Note**: unlike the shap package, with ``predict_contrib`` we return a matrix with an extra column, where the last column is the expected value // desc = **Note**: this feature is not implemented for linear trees bool predict_contrib = false; diff --git a/include/LightGBM/cuda/cuda_algorithms.hpp b/include/LightGBM/cuda/cuda_algorithms.hpp index f79fc57e4f42..ab3328bb5561 100644 --- a/include/LightGBM/cuda/cuda_algorithms.hpp +++ b/include/LightGBM/cuda/cuda_algorithms.hpp @@ -13,7 +13,7 @@ #include #include -#include +#include #include #include diff --git a/include/LightGBM/cuda/cuda_column_data.hpp b/include/LightGBM/cuda/cuda_column_data.hpp index 314a178859c6..5b2301ac8de3 100644 --- a/include/LightGBM/cuda/cuda_column_data.hpp +++ b/include/LightGBM/cuda/cuda_column_data.hpp @@ -9,7 +9,7 @@ #define LIGHTGBM_CUDA_CUDA_COLUMN_DATA_HPP_ #include -#include +#include #include #include diff --git a/include/LightGBM/cuda/cuda_metadata.hpp b/include/LightGBM/cuda/cuda_metadata.hpp index 5882ce7e05c9..bc7339a84bf7 100644 --- a/include/LightGBM/cuda/cuda_metadata.hpp +++ b/include/LightGBM/cuda/cuda_metadata.hpp @@ -8,7 +8,7 @@ #ifndef LIGHTGBM_CUDA_CUDA_METADATA_HPP_ #define LIGHTGBM_CUDA_CUDA_METADATA_HPP_ -#include +#include #include #include diff --git a/include/LightGBM/cuda/cuda_metric.hpp b/include/LightGBM/cuda/cuda_metric.hpp index 2540b0c1a835..9186ceea160b 100644 --- a/include/LightGBM/cuda/cuda_metric.hpp +++ b/include/LightGBM/cuda/cuda_metric.hpp @@ -9,7 +9,7 @@ #ifdef USE_CUDA -#include +#include #include namespace LightGBM { diff --git a/include/LightGBM/cuda/cuda_objective_function.hpp b/include/LightGBM/cuda/cuda_objective_function.hpp index 465ed334156c..fae8aa7ec643 100644 --- a/include/LightGBM/cuda/cuda_objective_function.hpp +++ b/include/LightGBM/cuda/cuda_objective_function.hpp @@ -9,7 +9,7 @@ #ifdef USE_CUDA -#include +#include #include #include diff --git a/include/LightGBM/cuda/cuda_row_data.hpp b/include/LightGBM/cuda/cuda_row_data.hpp index 1d4cb2f73b1e..0386db0dc300 100644 --- a/include/LightGBM/cuda/cuda_row_data.hpp +++ b/include/LightGBM/cuda/cuda_row_data.hpp @@ -10,7 +10,7 @@ #include #include -#include +#include #include #include #include diff --git a/include/LightGBM/cuda/cuda_split_info.hpp b/include/LightGBM/cuda/cuda_split_info.hpp index f01ce2b02a02..46b35ca37a59 100644 --- a/include/LightGBM/cuda/cuda_split_info.hpp +++ b/include/LightGBM/cuda/cuda_split_info.hpp @@ -24,14 +24,12 @@ class CUDASplitInfo { double left_sum_gradients; double left_sum_hessians; - int64_t left_sum_of_gradients_hessians; data_size_t left_count; double left_gain; double left_value; double right_sum_gradients; double right_sum_hessians; - int64_t right_sum_of_gradients_hessians; data_size_t right_count; double right_gain; double right_value; diff --git a/include/LightGBM/cuda/cuda_utils.hu b/include/LightGBM/cuda/cuda_utils.h similarity index 91% rename from include/LightGBM/cuda/cuda_utils.hu rename to include/LightGBM/cuda/cuda_utils.h index 4bd84aeb264d..953bf9f12e88 100644 --- a/include/LightGBM/cuda/cuda_utils.hu +++ b/include/LightGBM/cuda/cuda_utils.h @@ -7,21 +7,15 @@ #define LIGHTGBM_CUDA_CUDA_UTILS_H_ #ifdef USE_CUDA - #include #include #include - #include - -#include #include #include namespace LightGBM { -typedef unsigned long long atomic_add_long_t; - #define CUDASUCCESS_OR_FATAL(ans) { gpuAssert((ans), __FILE__, __LINE__); } inline void gpuAssert(cudaError_t code, const char *file, int line, bool abort = true) { if (code != cudaSuccess) { @@ -131,19 +125,13 @@ class CUDAVector { T* new_data = nullptr; AllocateCUDAMemory(&new_data, size, __FILE__, __LINE__); if (size_ > 0 && data_ != nullptr) { - const size_t size_for_old_content = std::min(size_, size); - CopyFromCUDADeviceToCUDADevice(new_data, data_, size_for_old_content, __FILE__, __LINE__); + CopyFromCUDADeviceToCUDADevice(new_data, data_, size, __FILE__, __LINE__); } DeallocateCUDAMemory(&data_, __FILE__, __LINE__); data_ = new_data; size_ = size; } - void InitFromHostVector(const std::vector& host_vector) { - Resize(host_vector.size()); - CopyFromHostToCUDADevice(data_, host_vector.data(), host_vector.size(), __FILE__, __LINE__); - } - void Clear() { if (size_ > 0 && data_ != nullptr) { DeallocateCUDAMemory(&data_, __FILE__, __LINE__); @@ -183,10 +171,6 @@ class CUDAVector { return data_; } - void SetValue(int value) { - SetCUDAMemory(data_, value, size_, __FILE__, __LINE__); - } - const T* RawDataReadOnly() const { return data_; } diff --git a/include/LightGBM/dataset.h b/include/LightGBM/dataset.h index 220a1f9f009c..e7baa42dc2e6 100644 --- a/include/LightGBM/dataset.h +++ b/include/LightGBM/dataset.h @@ -5,7 +5,6 @@ #ifndef LIGHTGBM_DATASET_H_ #define LIGHTGBM_DATASET_H_ -#include #include #include #include @@ -110,13 +109,10 @@ class Metadata { const std::vector& used_data_indices); void SetLabel(const label_t* label, data_size_t len); - void SetLabel(const ArrowChunkedArray& array); void SetWeights(const label_t* weights, data_size_t len); - void SetWeights(const ArrowChunkedArray& array); void SetQuery(const data_size_t* query, data_size_t len); - void SetQuery(const ArrowChunkedArray& array); void SetPosition(const data_size_t* position, data_size_t len); @@ -125,7 +121,6 @@ class Metadata { * \param init_score Initial scores, this class will manage memory for init_score. */ void SetInitScore(const double* init_score, data_size_t len); - void SetInitScore(const ArrowChunkedArray& array); /*! @@ -338,24 +333,12 @@ class Metadata { void CalculateQueryBoundaries(); /*! \brief Insert labels at the given index */ void InsertLabels(const label_t* labels, data_size_t start_index, data_size_t len); - /*! \brief Set labels from pointers to the first element and the end of an iterator. */ - template - void SetLabelsFromIterator(It first, It last); /*! \brief Insert weights at the given index */ void InsertWeights(const label_t* weights, data_size_t start_index, data_size_t len); - /*! \brief Set weights from pointers to the first element and the end of an iterator. */ - template - void SetWeightsFromIterator(It first, It last); /*! \brief Insert initial scores at the given index */ void InsertInitScores(const double* init_scores, data_size_t start_index, data_size_t len, data_size_t source_size); - /*! \brief Set init scores from pointers to the first element and the end of an iterator. */ - template - void SetInitScoresFromIterator(It first, It last); /*! \brief Insert queries at the given index */ void InsertQueries(const data_size_t* queries, data_size_t start_index, data_size_t len); - /*! \brief Set queries from pointers to the first element and the end of an iterator. */ - template - void SetQueriesFromIterator(It first, It last); /*! \brief Filename of current data */ std::string data_filename_; /*! \brief Number of data */ @@ -562,26 +545,21 @@ class Dataset { } } - inline void PushOneValue(int tid, data_size_t row_idx, size_t col_idx, double value) { - if (this->is_finish_load_) - return; - auto feature_idx = this->used_feature_map_[col_idx]; - if (feature_idx >= 0) { - auto group = this->feature2group_[feature_idx]; - auto sub_feature = this->feature2subfeature_[feature_idx]; - this->feature_groups_[group]->PushData(tid, sub_feature, row_idx, value); - if (this->has_raw_) { - auto feat_ind = numeric_feature_map_[feature_idx]; - if (feat_ind >= 0) { - raw_data_[feat_ind][row_idx] = static_cast(value); - } - } - } - } - inline void PushOneRow(int tid, data_size_t row_idx, const std::vector& feature_values) { + if (is_finish_load_) { return; } for (size_t i = 0; i < feature_values.size() && i < static_cast(num_total_features_); ++i) { - this->PushOneValue(tid, row_idx, i, feature_values[i]); + int feature_idx = used_feature_map_[i]; + if (feature_idx >= 0) { + const int group = feature2group_[feature_idx]; + const int sub_feature = feature2subfeature_[feature_idx]; + feature_groups_[group]->PushData(tid, sub_feature, row_idx, feature_values[i]); + if (has_raw_) { + int feat_ind = numeric_feature_map_[feature_idx]; + if (feat_ind >= 0) { + raw_data_[feat_ind][row_idx] = static_cast(feature_values[i]); + } + } + } } } @@ -671,8 +649,6 @@ class Dataset { LIGHTGBM_EXPORT void FinishLoad(); - bool SetFieldFromArrow(const char* field_name, const ArrowChunkedArray& ca); - LIGHTGBM_EXPORT bool SetFloatField(const char* field_name, const float* field_data, data_size_t num_element); LIGHTGBM_EXPORT bool SetDoubleField(const char* field_name, const double* field_data, data_size_t num_element); diff --git a/include/LightGBM/feature_group.h b/include/LightGBM/feature_group.h index f13a5fff966f..003df70afad7 100644 --- a/include/LightGBM/feature_group.h +++ b/include/LightGBM/feature_group.h @@ -361,7 +361,7 @@ class FeatureGroup { inline void FinishLoad() { if (is_multi_val_) { OMP_INIT_EX(); -#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(guided) +#pragma omp parallel for schedule(guided) for (int i = 0; i < num_feature_; ++i) { OMP_LOOP_EX_BEGIN(); multi_bin_data_[i]->FinishLoad(); diff --git a/include/LightGBM/sample_strategy.h b/include/LightGBM/sample_strategy.h index 4ea5cfc5f436..51d3cbc16f23 100644 --- a/include/LightGBM/sample_strategy.h +++ b/include/LightGBM/sample_strategy.h @@ -6,7 +6,7 @@ #ifndef LIGHTGBM_SAMPLE_STRATEGY_H_ #define LIGHTGBM_SAMPLE_STRATEGY_H_ -#include +#include #include #include #include diff --git a/include/LightGBM/tree.h b/include/LightGBM/tree.h index 0c4a41f46a87..13b3c41a2309 100644 --- a/include/LightGBM/tree.h +++ b/include/LightGBM/tree.h @@ -185,7 +185,7 @@ class Tree { * \param rate The factor of shrinkage */ virtual inline void Shrinkage(double rate) { -#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static, 1024) if (num_leaves_ >= 2048) +#pragma omp parallel for schedule(static, 1024) if (num_leaves_ >= 2048) for (int i = 0; i < num_leaves_ - 1; ++i) { leaf_value_[i] = MaybeRoundToZero(leaf_value_[i] * rate); internal_value_[i] = MaybeRoundToZero(internal_value_[i] * rate); @@ -210,7 +210,7 @@ class Tree { inline double shrinkage() const { return shrinkage_; } virtual inline void AddBias(double val) { -#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static, 1024) if (num_leaves_ >= 2048) +#pragma omp parallel for schedule(static, 1024) if (num_leaves_ >= 2048) for (int i = 0; i < num_leaves_ - 1; ++i) { leaf_value_[i] = MaybeRoundToZero(leaf_value_[i] + val); internal_value_[i] = MaybeRoundToZero(internal_value_[i] + val); @@ -218,7 +218,7 @@ class Tree { leaf_value_[num_leaves_ - 1] = MaybeRoundToZero(leaf_value_[num_leaves_ - 1] + val); if (is_linear_) { -#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static, 1024) if (num_leaves_ >= 2048) +#pragma omp parallel for schedule(static, 1024) if (num_leaves_ >= 2048) for (int i = 0; i < num_leaves_ - 1; ++i) { leaf_const_[i] = MaybeRoundToZero(leaf_const_[i] + val); } diff --git a/include/LightGBM/utils/common.h b/include/LightGBM/utils/common.h index f1b5a10b5a69..4e82734858f3 100644 --- a/include/LightGBM/utils/common.h +++ b/include/LightGBM/utils/common.h @@ -691,7 +691,7 @@ static void ParallelSort(_RanIt _First, _RanIt _Last, _Pr _Pred, _VTRanIt*) { size_t inner_size = (len + num_threads - 1) / num_threads; inner_size = std::max(inner_size, kMinInnerLen); num_threads = static_cast((len + inner_size - 1) / inner_size); -#pragma omp parallel for num_threads(num_threads) schedule(static, 1) +#pragma omp parallel for schedule(static, 1) for (int i = 0; i < num_threads; ++i) { size_t left = inner_size*i; size_t right = left + inner_size; @@ -707,7 +707,7 @@ static void ParallelSort(_RanIt _First, _RanIt _Last, _Pr _Pred, _VTRanIt*) { // Recursive merge while (s < len) { int loop_size = static_cast((len + s * 2 - 1) / (s * 2)); - #pragma omp parallel for num_threads(num_threads) schedule(static, 1) + #pragma omp parallel for schedule(static, 1) for (int i = 0; i < loop_size; ++i) { size_t left = i * 2 * s; size_t mid = left + s; diff --git a/include/LightGBM/utils/openmp_wrapper.h b/include/LightGBM/utils/openmp_wrapper.h index b9a8ea2982fc..a337fc353b75 100644 --- a/include/LightGBM/utils/openmp_wrapper.h +++ b/include/LightGBM/utils/openmp_wrapper.h @@ -5,15 +5,6 @@ #ifndef LIGHTGBM_OPENMP_WRAPPER_H_ #define LIGHTGBM_OPENMP_WRAPPER_H_ -#include - -// this can only be changed by LGBM_SetMaxThreads() -LIGHTGBM_EXTERN_C int LGBM_MAX_NUM_THREADS; - -// this is modified by OMP_SET_NUM_THREADS(), for example -// by passing num_thread through params -LIGHTGBM_EXTERN_C int LGBM_DEFAULT_NUM_THREADS; - #ifdef _OPENMP #include @@ -26,25 +17,22 @@ LIGHTGBM_EXTERN_C int LGBM_DEFAULT_NUM_THREADS; #include #include -/* - Get number of threads to use in OpenMP parallel regions. - - By default, this will return the result of omp_get_max_threads(), - which is OpenMP-implementation dependent but generally can be controlled - by environment variable OMP_NUM_THREADS. - - ref: - - https://www.openmp.org/spec-html/5.0/openmpsu112.html - - https://gcc.gnu.org/onlinedocs/libgomp/omp_005fget_005fmax_005fthreads.html -*/ -LIGHTGBM_EXTERN_C int OMP_NUM_THREADS(); - -/* - Update the default number of threads that'll be used in OpenMP parallel - regions for LightGBM routines where the number of threads aren't directly - supplied. -*/ -LIGHTGBM_EXTERN_C void OMP_SET_NUM_THREADS(int num_threads); +inline int OMP_NUM_THREADS() { + int ret = 1; +#pragma omp parallel +#pragma omp master + { ret = omp_get_num_threads(); } + return ret; +} + +inline void OMP_SET_NUM_THREADS(int num_threads) { + static const int default_omp_num_threads = OMP_NUM_THREADS(); + if (num_threads > 0) { + omp_set_num_threads(num_threads); + } else { + omp_set_num_threads(default_omp_num_threads); + } +} class ThreadExceptionHelper { public: @@ -114,7 +102,10 @@ class ThreadExceptionHelper { /** Fall here if no OPENMP support, so just simulate a single thread running. All #pragma omp should be ignored by the compiler **/ + inline void omp_set_num_threads(int) __GOMP_NOTHROW {} // NOLINT (no cast done here) inline void OMP_SET_NUM_THREADS(int) __GOMP_NOTHROW {} + inline int omp_get_num_threads() __GOMP_NOTHROW {return 1;} + inline int omp_get_max_threads() __GOMP_NOTHROW {return 1;} inline int omp_get_thread_num() __GOMP_NOTHROW {return 0;} inline int OMP_NUM_THREADS() __GOMP_NOTHROW { return 1; } #ifdef __cplusplus diff --git a/include/LightGBM/utils/threading.h b/include/LightGBM/utils/threading.h index 41362ee5db49..a093f87c1c8b 100644 --- a/include/LightGBM/utils/threading.h +++ b/include/LightGBM/utils/threading.h @@ -73,7 +73,7 @@ class Threading { INDEX_T num_inner = end - start; BlockInfo(num_inner, min_block_size, &n_block, &num_inner); OMP_INIT_EX(); -#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static, 1) +#pragma omp parallel for schedule(static, 1) for (int i = 0; i < n_block; ++i) { OMP_LOOP_EX_BEGIN(); INDEX_T inner_start = start + num_inner * i; diff --git a/python-package/lightgbm/basic.py b/python-package/lightgbm/basic.py index 19a53c6bb70f..dac3224a54b2 100644 --- a/python-package/lightgbm/basic.py +++ b/python-package/lightgbm/basic.py @@ -18,21 +18,12 @@ import numpy as np import scipy.sparse -from .compat import (PANDAS_INSTALLED, PYARROW_INSTALLED, arrow_cffi, arrow_is_floating, arrow_is_integer, concat, - dt_DataTable, pa_Array, pa_chunked_array, pa_ChunkedArray, pa_compute, pa_Table, - pd_CategoricalDtype, pd_DataFrame, pd_Series) +from .compat import PANDAS_INSTALLED, concat, dt_DataTable, pd_CategoricalDtype, pd_DataFrame, pd_Series from .libpath import find_lib_path if TYPE_CHECKING: from typing import Literal - # typing.TypeGuard was only introduced in Python 3.10 - try: - from typing import TypeGuard - except ImportError: - from typing_extensions import TypeGuard - - __all__ = [ 'Booster', 'Dataset', @@ -70,9 +61,7 @@ List[float], List[int], np.ndarray, - pd_Series, - pa_Array, - pa_ChunkedArray, + pd_Series ] _LGBM_PositionType = Union[ np.ndarray, @@ -84,9 +73,6 @@ np.ndarray, pd_Series, pd_DataFrame, - pa_Table, - pa_Array, - pa_ChunkedArray, ] _LGBM_TrainDataType = Union[ str, @@ -97,17 +83,14 @@ scipy.sparse.spmatrix, "Sequence", List["Sequence"], - List[np.ndarray], - pa_Table + List[np.ndarray] ] _LGBM_LabelType = Union[ List[float], List[int], np.ndarray, pd_Series, - pd_DataFrame, - pa_Array, - pa_ChunkedArray, + pd_DataFrame ] _LGBM_PredictDataType = Union[ str, @@ -115,16 +98,13 @@ np.ndarray, pd_DataFrame, dt_DataTable, - scipy.sparse.spmatrix, - pa_Table, + scipy.sparse.spmatrix ] _LGBM_WeightType = Union[ List[float], List[int], np.ndarray, - pd_Series, - pa_Array, - pa_ChunkedArray, + pd_Series ] ZERO_THRESHOLD = 1e-35 @@ -299,20 +279,6 @@ def _is_1d_list(data: Any) -> bool: return isinstance(data, list) and (not data or _is_numeric(data[0])) -def _is_list_of_numpy_arrays(data: Any) -> "TypeGuard[List[np.ndarray]]": - return ( - isinstance(data, list) - and all(isinstance(x, np.ndarray) for x in data) - ) - - -def _is_list_of_sequences(data: Any) -> "TypeGuard[List[Sequence]]": - return ( - isinstance(data, list) - and all(isinstance(x, Sequence) for x in data) - ) - - def _is_1d_collection(data: Any) -> bool: """Check whether data is a 1-D collection.""" return ( @@ -364,68 +330,6 @@ def _is_2d_collection(data: Any) -> bool: ) -def _is_pyarrow_array(data: Any) -> bool: - """Check whether data is a PyArrow array.""" - return isinstance(data, (pa_Array, pa_ChunkedArray)) - - -def _is_pyarrow_table(data: Any) -> bool: - """Check whether data is a PyArrow table.""" - return isinstance(data, pa_Table) - - -class _ArrowCArray: - """Simple wrapper around the C representation of an Arrow type.""" - - n_chunks: int - chunks: arrow_cffi.CData - schema: arrow_cffi.CData - - def __init__(self, n_chunks: int, chunks: arrow_cffi.CData, schema: arrow_cffi.CData): - self.n_chunks = n_chunks - self.chunks = chunks - self.schema = schema - - @property - def chunks_ptr(self) -> int: - """Returns the address of the pointer to the list of chunks making up the array.""" - return int(arrow_cffi.cast("uintptr_t", arrow_cffi.addressof(self.chunks[0]))) - - @property - def schema_ptr(self) -> int: - """Returns the address of the pointer to the schema of the array.""" - return int(arrow_cffi.cast("uintptr_t", self.schema)) - - -def _export_arrow_to_c(data: pa_Table) -> _ArrowCArray: - """Export an Arrow type to its C representation.""" - # Obtain objects to export - if isinstance(data, pa_Array): - export_objects = [data] - elif isinstance(data, pa_ChunkedArray): - export_objects = data.chunks - elif isinstance(data, pa_Table): - export_objects = data.to_batches() - else: - raise ValueError(f"data of type '{type(data)}' cannot be exported to Arrow") - - # Prepare export - chunks = arrow_cffi.new("struct ArrowArray[]", len(export_objects)) - schema = arrow_cffi.new("struct ArrowSchema*") - - # Export all objects - for i, obj in enumerate(export_objects): - chunk_ptr = int(arrow_cffi.cast("uintptr_t", arrow_cffi.addressof(chunks[i]))) - if i == 0: - schema_ptr = int(arrow_cffi.cast("uintptr_t", schema)) - obj._export_to_c(chunk_ptr, schema_ptr) - else: - obj._export_to_c(chunk_ptr) - - return _ArrowCArray(len(chunks), chunks, schema) - - - def _data_to_2d_numpy( data: Any, dtype: "np.typing.DTypeLike", @@ -443,7 +347,7 @@ def _data_to_2d_numpy( "It should be list of lists, numpy 2-D array or pandas DataFrame") -def _cfloat32_array_to_numpy(*, cptr: "ctypes._Pointer", length: int) -> np.ndarray: +def _cfloat32_array_to_numpy(cptr: "ctypes._Pointer", length: int) -> np.ndarray: """Convert a ctypes float pointer array to a numpy array.""" if isinstance(cptr, ctypes.POINTER(ctypes.c_float)): return np.ctypeslib.as_array(cptr, shape=(length,)).copy() @@ -451,7 +355,7 @@ def _cfloat32_array_to_numpy(*, cptr: "ctypes._Pointer", length: int) -> np.ndar raise RuntimeError('Expected float pointer') -def _cfloat64_array_to_numpy(*, cptr: "ctypes._Pointer", length: int) -> np.ndarray: +def _cfloat64_array_to_numpy(cptr: "ctypes._Pointer", length: int) -> np.ndarray: """Convert a ctypes double pointer array to a numpy array.""" if isinstance(cptr, ctypes.POINTER(ctypes.c_double)): return np.ctypeslib.as_array(cptr, shape=(length,)).copy() @@ -459,7 +363,7 @@ def _cfloat64_array_to_numpy(*, cptr: "ctypes._Pointer", length: int) -> np.ndar raise RuntimeError('Expected double pointer') -def _cint32_array_to_numpy(*, cptr: "ctypes._Pointer", length: int) -> np.ndarray: +def _cint32_array_to_numpy(cptr: "ctypes._Pointer", length: int) -> np.ndarray: """Convert a ctypes int pointer array to a numpy array.""" if isinstance(cptr, ctypes.POINTER(ctypes.c_int32)): return np.ctypeslib.as_array(cptr, shape=(length,)).copy() @@ -467,7 +371,7 @@ def _cint32_array_to_numpy(*, cptr: "ctypes._Pointer", length: int) -> np.ndarra raise RuntimeError('Expected int32 pointer') -def _cint64_array_to_numpy(*, cptr: "ctypes._Pointer", length: int) -> np.ndarray: +def _cint64_array_to_numpy(cptr: "ctypes._Pointer", length: int) -> np.ndarray: """Convert a ctypes int pointer array to a numpy array.""" if isinstance(cptr, ctypes.POINTER(ctypes.c_int64)): return np.ctypeslib.as_array(cptr, shape=(length,)).copy() @@ -554,7 +458,7 @@ def _get_all_param_aliases() -> Dict[str, List[str]]: buffer_len = 1 << 20 tmp_out_len = ctypes.c_int64(0) string_buffer = ctypes.create_string_buffer(buffer_len) - ptr_string_buffer = ctypes.c_char_p(ctypes.addressof(string_buffer)) + ptr_string_buffer = ctypes.c_char_p(*[ctypes.addressof(string_buffer)]) _safe_call(_LIB.LGBM_DumpParamAliases( ctypes.c_int64(buffer_len), ctypes.byref(tmp_out_len), @@ -563,7 +467,7 @@ def _get_all_param_aliases() -> Dict[str, List[str]]: # if buffer length is not long enough, re-allocate a buffer if actual_len > buffer_len: string_buffer = ctypes.create_string_buffer(actual_len) - ptr_string_buffer = ctypes.c_char_p(ctypes.addressof(string_buffer)) + ptr_string_buffer = ctypes.c_char_p(*[ctypes.addressof(string_buffer)]) _safe_call(_LIB.LGBM_DumpParamAliases( ctypes.c_int64(actual_len), ctypes.byref(tmp_out_len), @@ -764,43 +668,22 @@ def _check_for_bad_pandas_dtypes(pandas_dtypes_series: pd_Series) -> None: f'Fields with bad pandas dtypes: {", ".join(bad_pandas_dtypes)}') -def _pandas_to_numpy( - data: pd_DataFrame, - target_dtype: "np.typing.DTypeLike" -) -> np.ndarray: - _check_for_bad_pandas_dtypes(data.dtypes) - try: - # most common case (no nullable dtypes) - return data.to_numpy(dtype=target_dtype, copy=False) - except TypeError: - # 1.0 <= pd version < 1.1 and nullable dtypes, least common case - # raises error because array is casted to type(pd.NA) and there's no na_value argument - return data.astype(target_dtype, copy=False).values - except ValueError: - # data has nullable dtypes, but we can specify na_value argument and copy will be made - return data.to_numpy(dtype=target_dtype, na_value=np.nan) - - def _data_from_pandas( data: pd_DataFrame, feature_name: _LGBM_FeatureNameConfiguration, categorical_feature: _LGBM_CategoricalFeatureConfiguration, pandas_categorical: Optional[List[List]] -) -> Tuple[np.ndarray, List[str], Union[List[str], List[int]], List[List]]: +) -> Tuple[np.ndarray, List[str], List[str], List[List]]: if len(data.shape) != 2 or data.shape[0] < 1: raise ValueError('Input data must be 2 dimensional and non empty.') - # take shallow copy in case we modify categorical columns - # whole column modifications don't change the original df - data = data.copy(deep=False) - # determine feature names if feature_name == 'auto': feature_name = [str(col) for col in data.columns] # determine categorical features cat_cols = [col for col, dtype in zip(data.columns, data.dtypes) if isinstance(dtype, pd_CategoricalDtype)] - cat_cols_not_ordered: List[str] = [col for col in cat_cols if not data[col].cat.ordered] + cat_cols_not_ordered = [col for col in cat_cols if not data[col].cat.ordered] if pandas_categorical is None: # train dataset pandas_categorical = [list(data[col].cat.categories) for col in cat_cols] else: @@ -810,23 +693,29 @@ def _data_from_pandas( if list(data[col].cat.categories) != list(category): data[col] = data[col].cat.set_categories(category) if len(cat_cols): # cat_cols is list + data = data.copy(deep=False) # not alter origin DataFrame data[cat_cols] = data[cat_cols].apply(lambda x: x.cat.codes).replace({-1: np.nan}) - - # use cat cols from DataFrame - if categorical_feature == 'auto': + if categorical_feature == 'auto': # use cat cols from DataFrame categorical_feature = cat_cols_not_ordered + else: # use cat cols specified by user + categorical_feature = list(categorical_feature) # type: ignore[assignment] + # get numpy representation of the data + _check_for_bad_pandas_dtypes(data.dtypes) df_dtypes = [dtype.type for dtype in data.dtypes] - # so that the target dtype considers floats - df_dtypes.append(np.float32) + df_dtypes.append(np.float32) # so that the target dtype considers floats target_dtype = np.result_type(*df_dtypes) - - return ( - _pandas_to_numpy(data, target_dtype=target_dtype), - feature_name, - categorical_feature, - pandas_categorical - ) + try: + # most common case (no nullable dtypes) + data = data.to_numpy(dtype=target_dtype, copy=False) + except TypeError: + # 1.0 <= pd version < 1.1 and nullable dtypes, least common case + # raises error because array is casted to type(pd.NA) and there's no na_value argument + data = data.astype(target_dtype, copy=False).values + except ValueError: + # data has nullable dtypes, but we can specify na_value argument and copy will be made + data = data.to_numpy(dtype=target_dtype, na_value=np.nan) + return data, feature_name, categorical_feature, pandas_categorical def _dump_pandas_categorical( @@ -1070,7 +959,7 @@ def predict( Parameters ---------- - data : str, pathlib.Path, numpy array, pandas DataFrame, pyarrow Table, H2O DataTable's Frame or scipy.sparse + data : str, pathlib.Path, numpy array, pandas DataFrame, H2O DataTable's Frame or scipy.sparse Data source for prediction. If str or pathlib.Path, it represents the path to a text file (CSV, TSV, or LibSVM). start_iteration : int, optional (default=0) @@ -1162,13 +1051,6 @@ def predict( num_iteration=num_iteration, predict_type=predict_type ) - elif _is_pyarrow_table(data): - preds, nrow = self.__pred_for_pyarrow_table( - table=data, - start_iteration=start_iteration, - num_iteration=num_iteration, - predict_type=predict_type - ) elif isinstance(data, list): try: data = np.array(data) @@ -1326,18 +1208,18 @@ def __create_sparse_native( data_indices_len = out_shape[0] indptr_len = out_shape[1] if indptr_type == _C_API_DTYPE_INT32: - out_indptr = _cint32_array_to_numpy(cptr=out_ptr_indptr, length=indptr_len) + out_indptr = _cint32_array_to_numpy(out_ptr_indptr, indptr_len) elif indptr_type == _C_API_DTYPE_INT64: - out_indptr = _cint64_array_to_numpy(cptr=out_ptr_indptr, length=indptr_len) + out_indptr = _cint64_array_to_numpy(out_ptr_indptr, indptr_len) else: raise TypeError("Expected int32 or int64 type for indptr") if data_type == _C_API_DTYPE_FLOAT32: - out_data = _cfloat32_array_to_numpy(cptr=out_ptr_data, length=data_indices_len) + out_data = _cfloat32_array_to_numpy(out_ptr_data, data_indices_len) elif data_type == _C_API_DTYPE_FLOAT64: - out_data = _cfloat64_array_to_numpy(cptr=out_ptr_data, length=data_indices_len) + out_data = _cfloat64_array_to_numpy(out_ptr_data, data_indices_len) else: raise TypeError("Expected float32 or float64 type for data") - out_indices = _cint32_array_to_numpy(cptr=out_ptr_indices, length=data_indices_len) + out_indices = _cint32_array_to_numpy(out_ptr_indices, data_indices_len) # break up indptr based on number of rows (note more than one matrix in multiclass case) per_class_indptr_shape = cs.indptr.shape[0] # for CSC there is extra column added @@ -1622,48 +1504,6 @@ def __pred_for_csc( if n_preds != out_num_preds.value: raise ValueError("Wrong length for predict results") return preds, nrow - - def __pred_for_pyarrow_table( - self, - table: pa_Table, - start_iteration: int, - num_iteration: int, - predict_type: int - ) -> Tuple[np.ndarray, int]: - """Predict for a PyArrow table.""" - if not PYARROW_INSTALLED: - raise LightGBMError("Cannot predict from Arrow without `pyarrow` installed.") - - # Check that the input is valid: we only handle numbers (for now) - if not all(arrow_is_integer(t) or arrow_is_floating(t) for t in table.schema.types): - raise ValueError("Arrow table may only have integer or floating point datatypes") - - # Prepare prediction output array - n_preds = self.__get_num_preds( - start_iteration=start_iteration, - num_iteration=num_iteration, - nrow=table.num_rows, - predict_type=predict_type - ) - preds = np.empty(n_preds, dtype=np.float64) - out_num_preds = ctypes.c_int64(0) - - # Export Arrow table to C and run prediction - c_array = _export_arrow_to_c(table) - _safe_call(_LIB.LGBM_BoosterPredictForArrow( - self._handle, - ctypes.c_int64(c_array.n_chunks), - ctypes.c_void_p(c_array.chunks_ptr), - ctypes.c_void_p(c_array.schema_ptr), - ctypes.c_int(predict_type), - ctypes.c_int(start_iteration), - ctypes.c_int(num_iteration), - _c_str(self.pred_parameter), - ctypes.byref(out_num_preds), - preds.ctypes.data_as(ctypes.POINTER(ctypes.c_double)))) - if n_preds != out_num_preds.value: - raise ValueError("Wrong length for predict results") - return preds, table.num_rows def current_iteration(self) -> int: """Get the index of the current iteration. @@ -1701,26 +1541,26 @@ def __init__( Parameters ---------- - data : str, pathlib.Path, numpy array, pandas DataFrame, H2O DataTable's Frame, scipy.sparse, Sequence, list of Sequence, list of numpy array or pyarrow Table + data : str, pathlib.Path, numpy array, pandas DataFrame, H2O DataTable's Frame, scipy.sparse, Sequence, list of Sequence or list of numpy array Data source of Dataset. If str or pathlib.Path, it represents the path to a text file (CSV, TSV, or LibSVM) or a LightGBM Dataset binary file. - label : list, numpy 1-D array, pandas Series / one-column DataFrame, pyarrow Array, pyarrow ChunkedArray or None, optional (default=None) + label : list, numpy 1-D array, pandas Series / one-column DataFrame or None, optional (default=None) Label of the data. reference : Dataset or None, optional (default=None) If this is Dataset for validation, training data should be used as reference. - weight : list, numpy 1-D array, pandas Series, pyarrow Array, pyarrow ChunkedArray or None, optional (default=None) + weight : list, numpy 1-D array, pandas Series or None, optional (default=None) Weight for each instance. Weights should be non-negative. - group : list, numpy 1-D array, pandas Series, pyarrow Array, pyarrow ChunkedArray or None, optional (default=None) + group : list, numpy 1-D array, pandas Series or None, optional (default=None) Group/query data. Only used in the learning-to-rank task. sum(group) = n_samples. For example, if you have a 100-document dataset with ``group = [10, 20, 40, 10, 10, 10]``, that means that you have 6 groups, where the first 10 records are in the first group, records 11-30 are in the second group, records 31-70 are in the third group, etc. - init_score : list, list of lists (for multi-class task), numpy array, pandas Series, pandas DataFrame (for multi-class task), pyarrow Array, pyarrow ChunkedArray, pyarrow Table (for multi-class task) or None, optional (default=None) + init_score : list, list of lists (for multi-class task), numpy array, pandas Series, pandas DataFrame (for multi-class task), or None, optional (default=None) Init score for Dataset. feature_name : list of str, or 'auto', optional (default="auto") Feature names. - If 'auto' and data is pandas DataFrame or pyarrow Table, data columns names are used. + If 'auto' and data is pandas DataFrame, data columns names are used. categorical_feature : list of str or int, or 'auto', optional (default="auto") Categorical features. If list of int, interpreted as indices. @@ -2077,13 +1917,10 @@ def _lazy_init( self.__init_from_csc(data, params_str, ref_dataset) elif isinstance(data, np.ndarray): self.__init_from_np2d(data, params_str, ref_dataset) - elif _is_pyarrow_table(data): - self.__init_from_pyarrow_table(data, params_str, ref_dataset) - feature_name = data.column_names elif isinstance(data, list) and len(data) > 0: - if _is_list_of_numpy_arrays(data): + if all(isinstance(x, np.ndarray) for x in data): self.__init_from_list_np2d(data, params_str, ref_dataset) - elif _is_list_of_sequences(data): + elif all(isinstance(x, Sequence) for x in data): self.__init_from_seqs(data, ref_dataset) else: raise TypeError('Data list can only be of ndarray or Sequence') @@ -2340,36 +2177,10 @@ def __init_from_csc( ctypes.byref(self._handle))) return self - def __init_from_pyarrow_table( - self, - table: pa_Table, - params_str: str, - ref_dataset: Optional[_DatasetHandle] - ) -> "Dataset": - """Initialize data from a PyArrow table.""" - if not PYARROW_INSTALLED: - raise LightGBMError("Cannot init dataframe from Arrow without `pyarrow` installed.") - - # Check that the input is valid: we only handle numbers (for now) - if not all(arrow_is_integer(t) or arrow_is_floating(t) for t in table.schema.types): - raise ValueError("Arrow table may only have integer or floating point datatypes") - - # Export Arrow table to C - c_array = _export_arrow_to_c(table) - self._handle = ctypes.c_void_p() - _safe_call(_LIB.LGBM_DatasetCreateFromArrow( - ctypes.c_int64(c_array.n_chunks), - ctypes.c_void_p(c_array.chunks_ptr), - ctypes.c_void_p(c_array.schema_ptr), - _c_str(params_str), - ref_dataset, - ctypes.byref(self._handle))) - return self - @staticmethod def _compare_params_for_warning( - params: Dict[str, Any], - other_params: Dict[str, Any], + params: Optional[Dict[str, Any]], + other_params: Optional[Dict[str, Any]], ignore_keys: Set[str] ) -> bool: """Compare two dictionaries with params ignoring some keys. @@ -2378,9 +2189,9 @@ def _compare_params_for_warning( Parameters ---------- - params : dict + params : dict or None One dictionary with parameters to compare. - other_params : dict + other_params : dict or None Another dictionary with parameters to compare. ignore_keys : set Keys that should be ignored during comparing two dictionaries. @@ -2390,6 +2201,10 @@ def _compare_params_for_warning( compare_result : bool Returns whether two dictionaries with params are equal. """ + if params is None: + params = {} + if other_params is None: + other_params = {} for k in other_params: if k not in ignore_keys: if k not in params or params[k] != other_params[k]: @@ -2486,17 +2301,17 @@ def create_valid( data : str, pathlib.Path, numpy array, pandas DataFrame, H2O DataTable's Frame, scipy.sparse, Sequence, list of Sequence or list of numpy array Data source of Dataset. If str or pathlib.Path, it represents the path to a text file (CSV, TSV, or LibSVM) or a LightGBM Dataset binary file. - label : list, numpy 1-D array, pandas Series / one-column DataFrame, pyarrow Array, pyarrow ChunkedArray or None, optional (default=None) + label : list, numpy 1-D array, pandas Series / one-column DataFrame or None, optional (default=None) Label of the data. - weight : list, numpy 1-D array, pandas Series, pyarrow Array, pyarrow ChunkedArray or None, optional (default=None) + weight : list, numpy 1-D array, pandas Series or None, optional (default=None) Weight for each instance. Weights should be non-negative. - group : list, numpy 1-D array, pandas Series, pyarrow Array, pyarrow ChunkedArray or None, optional (default=None) + group : list, numpy 1-D array, pandas Series or None, optional (default=None) Group/query data. Only used in the learning-to-rank task. sum(group) = n_samples. For example, if you have a 100-document dataset with ``group = [10, 20, 40, 10, 10, 10]``, that means that you have 6 groups, where the first 10 records are in the first group, records 11-30 are in the second group, records 31-70 are in the third group, etc. - init_score : list, list of lists (for multi-class task), numpy array, pandas Series, pandas DataFrame (for multi-class task), pyarrow Array, pyarrow ChunkedArray, pyarrow Table (for multi-class task) or None, optional (default=None) + init_score : list, list of lists (for multi-class task), numpy array, pandas Series, pandas DataFrame (for multi-class task), or None, optional (default=None) Init score for Dataset. params : dict or None, optional (default=None) Other parameters for validation Dataset. @@ -2603,7 +2418,7 @@ def _reverse_update_params(self) -> "Dataset": def set_field( self, field_name: str, - data: Optional[Union[List[List[float]], List[List[int]], List[float], List[int], np.ndarray, pd_Series, pd_DataFrame, pa_Table, pa_Array, pa_ChunkedArray]] + data: Optional[Union[List[List[float]], List[List[int]], List[float], List[int], np.ndarray, pd_Series, pd_DataFrame]] ) -> "Dataset": """Set property into the Dataset. @@ -2611,7 +2426,7 @@ def set_field( ---------- field_name : str The field name of the information. - data : list, list of lists (for multi-class task), numpy array, pandas Series, pandas DataFrame (for multi-class task), pyarrow Array, pyarrow ChunkedArray or None + data : list, list of lists (for multi-class task), numpy array, pandas Series, pandas DataFrame (for multi-class task), or None The data to be set. Returns @@ -2630,29 +2445,6 @@ def set_field( ctypes.c_int(0), ctypes.c_int(_FIELD_TYPE_MAPPER[field_name]))) return self - - # If the data is a arrow data, we can just pass it to C - if _is_pyarrow_array(data) or _is_pyarrow_table(data): - # If a table is being passed, we concatenate the columns. This is only valid for - # 'init_score'. - if _is_pyarrow_table(data): - if field_name != "init_score": - raise ValueError(f"pyarrow tables are not supported for field '{field_name}'") - data = pa_chunked_array([ - chunk for array in data.columns for chunk in array.chunks # type: ignore - ]) - - c_array = _export_arrow_to_c(data) - _safe_call(_LIB.LGBM_DatasetSetFieldFromArrow( - self._handle, - _c_str(field_name), - ctypes.c_int64(c_array.n_chunks), - ctypes.c_void_p(c_array.chunks_ptr), - ctypes.c_void_p(c_array.schema_ptr), - )) - self.version += 1 - return self - dtype: "np.typing.DTypeLike" if field_name == 'init_score': dtype = np.float64 @@ -2691,12 +2483,6 @@ def set_field( def get_field(self, field_name: str) -> Optional[np.ndarray]: """Get property from the Dataset. - Can only be run on a constructed Dataset. - - Unlike ``get_group()``, ``get_init_score()``, ``get_label()``, ``get_position()``, and ``get_weight()``, - this method ignores any raw data passed into ``lgb.Dataset()`` on the Python side, and will only read - data from the constructed C++ ``Dataset`` object. - Parameters ---------- field_name : str @@ -2723,20 +2509,11 @@ def get_field(self, field_name: str) -> Optional[np.ndarray]: if tmp_out_len.value == 0: return None if out_type.value == _C_API_DTYPE_INT32: - arr = _cint32_array_to_numpy( - cptr=ctypes.cast(ret, ctypes.POINTER(ctypes.c_int32)), - length=tmp_out_len.value - ) + arr = _cint32_array_to_numpy(ctypes.cast(ret, ctypes.POINTER(ctypes.c_int32)), tmp_out_len.value) elif out_type.value == _C_API_DTYPE_FLOAT32: - arr = _cfloat32_array_to_numpy( - cptr=ctypes.cast(ret, ctypes.POINTER(ctypes.c_float)), - length=tmp_out_len.value - ) + arr = _cfloat32_array_to_numpy(ctypes.cast(ret, ctypes.POINTER(ctypes.c_float)), tmp_out_len.value) elif out_type.value == _C_API_DTYPE_FLOAT64: - arr = _cfloat64_array_to_numpy( - cptr=ctypes.cast(ret, ctypes.POINTER(ctypes.c_double)), - length=tmp_out_len.value - ) + arr = _cfloat64_array_to_numpy(ctypes.cast(ret, ctypes.POINTER(ctypes.c_double)), tmp_out_len.value) else: raise TypeError("Unknown type") if field_name == 'init_score': @@ -2869,7 +2646,7 @@ def set_label(self, label: Optional[_LGBM_LabelType]) -> "Dataset": Parameters ---------- - label : list, numpy 1-D array, pandas Series / one-column DataFrame, pyarrow Array, pyarrow ChunkedArray or None + label : list, numpy 1-D array, pandas Series / one-column DataFrame or None The label information to be set into Dataset. Returns @@ -2882,9 +2659,18 @@ def set_label(self, label: Optional[_LGBM_LabelType]) -> "Dataset": if isinstance(label, pd_DataFrame): if len(label.columns) > 1: raise ValueError('DataFrame for label cannot have multiple columns') - label_array = np.ravel(_pandas_to_numpy(label, target_dtype=np.float32)) - elif _is_pyarrow_array(label): - label_array = label + _check_for_bad_pandas_dtypes(label.dtypes) + try: + # most common case (no nullable dtypes) + label = label.to_numpy(dtype=np.float32, copy=False) + except TypeError: + # 1.0 <= pd version < 1.1 and nullable dtypes, least common case + # raises error because array is casted to type(pd.NA) and there's no na_value argument + label = label.astype(np.float32, copy=False).values + except ValueError: + # data has nullable dtypes, but we can specify na_value argument and copy will be made + label = label.to_numpy(dtype=np.float32, na_value=np.nan) + label_array = np.ravel(label) else: label_array = _list_to_1d_numpy(label, dtype=np.float32, name='label') self.set_field('label', label_array) @@ -2899,7 +2685,7 @@ def set_weight( Parameters ---------- - weight : list, numpy 1-D array, pandas Series, pyarrow Array, pyarrow ChunkedArray or None + weight : list, numpy 1-D array, pandas Series or None Weight to be set for each data point. Weights should be non-negative. Returns @@ -2907,19 +2693,11 @@ def set_weight( self : Dataset Dataset with set weight. """ - # Check if the weight contains values other than one - if weight is not None: - if _is_pyarrow_array(weight): - if pa_compute.all(pa_compute.equal(weight, 1)).as_py(): - weight = None - elif np.all(weight == 1): - weight = None + if weight is not None and np.all(weight == 1): + weight = None self.weight = weight - - # Set field if self._handle is not None and weight is not None: - if not _is_pyarrow_array(weight): - weight = _list_to_1d_numpy(weight, dtype=np.float32, name='weight') + weight = _list_to_1d_numpy(weight, dtype=np.float32, name='weight') self.set_field('weight', weight) self.weight = self.get_field('weight') # original values can be modified at cpp side return self @@ -2932,7 +2710,7 @@ def set_init_score( Parameters ---------- - init_score : list, list of lists (for multi-class task), numpy array, pandas Series, pandas DataFrame (for multi-class task), pyarrow Array, pyarrow ChunkedArray, pyarrow Table (for multi-class task) or None + init_score : list, list of lists (for multi-class task), numpy array, pandas Series, pandas DataFrame (for multi-class task), or None Init score for Booster. Returns @@ -2954,7 +2732,7 @@ def set_group( Parameters ---------- - group : list, numpy 1-D array, pandas Series, pyarrow Array, pyarrow ChunkedArray or None + group : list, numpy 1-D array, pandas Series or None Group/query data. Only used in the learning-to-rank task. sum(group) = n_samples. @@ -2968,13 +2746,8 @@ def set_group( """ self.group = group if self._handle is not None and group is not None: - if not _is_pyarrow_array(group): - group = _list_to_1d_numpy(group, dtype=np.int32, name='group') + group = _list_to_1d_numpy(group, dtype=np.int32, name='group') self.set_field('group', group) - # original values can be modified at cpp side - constructed_group = self.get_field('group') - if constructed_group is not None: - self.group = np.diff(constructed_group) return self def set_position( @@ -3014,7 +2787,7 @@ def get_feature_name(self) -> List[str]: reserved_string_buffer_size = 255 required_string_buffer_size = ctypes.c_size_t(0) string_buffers = [ctypes.create_string_buffer(reserved_string_buffer_size) for _ in range(num_feature)] - ptr_string_buffers = (ctypes.c_char_p * num_feature)(*map(ctypes.addressof, string_buffers)) # type: ignore[misc] + ptr_string_buffers = (ctypes.c_char_p * num_feature)(*map(ctypes.addressof, string_buffers)) _safe_call(_LIB.LGBM_DatasetGetFeatureNames( self._handle, ctypes.c_int(num_feature), @@ -3028,7 +2801,7 @@ def get_feature_name(self) -> List[str]: # if buffer length is not long enough, reallocate buffers if reserved_string_buffer_size < actual_string_buffer_size: string_buffers = [ctypes.create_string_buffer(actual_string_buffer_size) for _ in range(num_feature)] - ptr_string_buffers = (ctypes.c_char_p * num_feature)(*map(ctypes.addressof, string_buffers)) # type: ignore[misc] + ptr_string_buffers = (ctypes.c_char_p * num_feature)(*map(ctypes.addressof, string_buffers)) _safe_call(_LIB.LGBM_DatasetGetFeatureNames( self._handle, ctypes.c_int(num_feature), @@ -3038,40 +2811,37 @@ def get_feature_name(self) -> List[str]: ptr_string_buffers)) return [string_buffers[i].value.decode('utf-8') for i in range(num_feature)] - def get_label(self) -> Optional[_LGBM_LabelType]: + def get_label(self) -> Optional[np.ndarray]: """Get the label of the Dataset. Returns ------- - label : list, numpy 1-D array, pandas Series / one-column DataFrame or None + label : numpy array or None The label information from the Dataset. - For a constructed ``Dataset``, this will only return a numpy array. """ if self.label is None: self.label = self.get_field('label') return self.label - def get_weight(self) -> Optional[_LGBM_WeightType]: + def get_weight(self) -> Optional[np.ndarray]: """Get the weight of the Dataset. Returns ------- - weight : list, numpy 1-D array, pandas Series or None + weight : numpy array or None Weight for each data point from the Dataset. Weights should be non-negative. - For a constructed ``Dataset``, this will only return ``None`` or a numpy array. """ if self.weight is None: self.weight = self.get_field('weight') return self.weight - def get_init_score(self) -> Optional[_LGBM_InitScoreType]: + def get_init_score(self) -> Optional[np.ndarray]: """Get the initial score of the Dataset. Returns ------- - init_score : list, list of lists (for multi-class task), numpy array, pandas Series, pandas DataFrame (for multi-class task), or None + init_score : numpy array or None Init score of Booster. - For a constructed ``Dataset``, this will only return ``None`` or a numpy array. """ if self.init_score is None: self.init_score = self.get_field('init_score') @@ -3098,7 +2868,7 @@ def get_data(self) -> Optional[_LGBM_TrainDataType]: self.data = self.data[self.used_indices, :] elif isinstance(self.data, Sequence): self.data = self.data[self.used_indices] - elif _is_list_of_sequences(self.data) and len(self.data) > 0: + elif isinstance(self.data, list) and len(self.data) > 0 and all(isinstance(x, Sequence) for x in self.data): self.data = np.array(list(self._yield_row_from_seqlist(self.data, self.used_indices))) else: _log_warning(f"Cannot subset {type(self.data).__name__} type of raw data.\n" @@ -3109,18 +2879,17 @@ def get_data(self) -> Optional[_LGBM_TrainDataType]: "set free_raw_data=False when construct Dataset to avoid this.") return self.data - def get_group(self) -> Optional[_LGBM_GroupType]: + def get_group(self) -> Optional[np.ndarray]: """Get the group of the Dataset. Returns ------- - group : list, numpy 1-D array, pandas Series or None + group : numpy array or None Group/query data. Only used in the learning-to-rank task. sum(group) = n_samples. For example, if you have a 100-document dataset with ``group = [10, 20, 40, 10, 10, 10]``, that means that you have 6 groups, where the first 10 records are in the first group, records 11-30 are in the second group, records 31-70 are in the third group, etc. - For a constructed ``Dataset``, this will only return ``None`` or a numpy array. """ if self.group is None: self.group = self.get_field('group') @@ -3129,14 +2898,13 @@ def get_group(self) -> Optional[_LGBM_GroupType]: self.group = np.diff(self.group) return self.group - def get_position(self) -> Optional[_LGBM_PositionType]: + def get_position(self) -> Optional[np.ndarray]: """Get the position of the Dataset. Returns ------- - position : numpy 1-D array, pandas Series or None + position : numpy 1-D array or None Position of items used in unbiased learning-to-rank task. - For a constructed ``Dataset``, this will only return ``None`` or a numpy array. """ if self.position is None: self.position = self.get_field('position') @@ -3524,7 +3292,7 @@ def _get_loaded_param(self) -> Dict[str, Any]: buffer_len = 1 << 20 tmp_out_len = ctypes.c_int64(0) string_buffer = ctypes.create_string_buffer(buffer_len) - ptr_string_buffer = ctypes.c_char_p(ctypes.addressof(string_buffer)) + ptr_string_buffer = ctypes.c_char_p(*[ctypes.addressof(string_buffer)]) _safe_call(_LIB.LGBM_BoosterGetLoadedParam( self._handle, ctypes.c_int64(buffer_len), @@ -3534,7 +3302,7 @@ def _get_loaded_param(self) -> Dict[str, Any]: # if buffer length is not long enough, re-allocate a buffer if actual_len > buffer_len: string_buffer = ctypes.create_string_buffer(actual_len) - ptr_string_buffer = ctypes.c_char_p(ctypes.addressof(string_buffer)) + ptr_string_buffer = ctypes.c_char_p(*[ctypes.addressof(string_buffer)]) _safe_call(_LIB.LGBM_BoosterGetLoadedParam( self._handle, ctypes.c_int64(actual_len), @@ -4287,7 +4055,7 @@ def model_to_string( buffer_len = 1 << 20 tmp_out_len = ctypes.c_int64(0) string_buffer = ctypes.create_string_buffer(buffer_len) - ptr_string_buffer = ctypes.c_char_p(ctypes.addressof(string_buffer)) + ptr_string_buffer = ctypes.c_char_p(*[ctypes.addressof(string_buffer)]) _safe_call(_LIB.LGBM_BoosterSaveModelToString( self._handle, ctypes.c_int(start_iteration), @@ -4300,7 +4068,7 @@ def model_to_string( # if buffer length is not long enough, re-allocate a buffer if actual_len > buffer_len: string_buffer = ctypes.create_string_buffer(actual_len) - ptr_string_buffer = ctypes.c_char_p(ctypes.addressof(string_buffer)) + ptr_string_buffer = ctypes.c_char_p(*[ctypes.addressof(string_buffer)]) _safe_call(_LIB.LGBM_BoosterSaveModelToString( self._handle, ctypes.c_int(start_iteration), @@ -4355,7 +4123,7 @@ def dump_model( buffer_len = 1 << 20 tmp_out_len = ctypes.c_int64(0) string_buffer = ctypes.create_string_buffer(buffer_len) - ptr_string_buffer = ctypes.c_char_p(ctypes.addressof(string_buffer)) + ptr_string_buffer = ctypes.c_char_p(*[ctypes.addressof(string_buffer)]) _safe_call(_LIB.LGBM_BoosterDumpModel( self._handle, ctypes.c_int(start_iteration), @@ -4368,7 +4136,7 @@ def dump_model( # if buffer length is not long enough, reallocate a buffer if actual_len > buffer_len: string_buffer = ctypes.create_string_buffer(actual_len) - ptr_string_buffer = ctypes.c_char_p(ctypes.addressof(string_buffer)) + ptr_string_buffer = ctypes.c_char_p(*[ctypes.addressof(string_buffer)]) _safe_call(_LIB.LGBM_BoosterDumpModel( self._handle, ctypes.c_int(start_iteration), @@ -4398,7 +4166,7 @@ def predict( Parameters ---------- - data : str, pathlib.Path, numpy array, pandas DataFrame, pyarrow Table, H2O DataTable's Frame or scipy.sparse + data : str, pathlib.Path, numpy array, pandas DataFrame, H2O DataTable's Frame or scipy.sparse Data source for prediction. If str or pathlib.Path, it represents the path to a text file (CSV, TSV, or LibSVM). start_iteration : int, optional (default=0) @@ -4482,7 +4250,7 @@ def refit( data : str, pathlib.Path, numpy array, pandas DataFrame, H2O DataTable's Frame, scipy.sparse, Sequence, list of Sequence or list of numpy array Data source for refit. If str or pathlib.Path, it represents the path to a text file (CSV, TSV, or LibSVM). - label : list, numpy 1-D array, pandas Series / one-column DataFrame, pyarrow Array or pyarrow ChunkedArray + label : list, numpy 1-D array or pandas Series / one-column DataFrame Label for refit. decay_rate : float, optional (default=0.9) Decay rate of refit, @@ -4492,12 +4260,12 @@ def refit( .. versionadded:: 4.0.0 - weight : list, numpy 1-D array, pandas Series, pyarrow Array, pyarrow ChunkedArray or None, optional (default=None) + weight : list, numpy 1-D array, pandas Series or None, optional (default=None) Weight for each ``data`` instance. Weights should be non-negative. .. versionadded:: 4.0.0 - group : list, numpy 1-D array, pandas Series, pyarrow Array, pyarrow ChunkedArray or None, optional (default=None) + group : list, numpy 1-D array, pandas Series or None, optional (default=None) Group/query size for ``data``. Only used in the learning-to-rank task. sum(group) = n_samples. @@ -4506,7 +4274,7 @@ def refit( .. versionadded:: 4.0.0 - init_score : list, list of lists (for multi-class task), numpy array, pandas Series, pandas DataFrame (for multi-class task), pyarrow Array, pyarrow ChunkedArray, pyarrow Table (for multi-class task) or None, optional (default=None) + init_score : list, list of lists (for multi-class task), numpy array, pandas Series, pandas DataFrame (for multi-class task), or None, optional (default=None) Init score for ``data``. .. versionadded:: 4.0.0 @@ -4694,7 +4462,7 @@ def feature_name(self) -> List[str]: reserved_string_buffer_size = 255 required_string_buffer_size = ctypes.c_size_t(0) string_buffers = [ctypes.create_string_buffer(reserved_string_buffer_size) for _ in range(num_feature)] - ptr_string_buffers = (ctypes.c_char_p * num_feature)(*map(ctypes.addressof, string_buffers)) # type: ignore[misc] + ptr_string_buffers = (ctypes.c_char_p * num_feature)(*map(ctypes.addressof, string_buffers)) _safe_call(_LIB.LGBM_BoosterGetFeatureNames( self._handle, ctypes.c_int(num_feature), @@ -4708,7 +4476,7 @@ def feature_name(self) -> List[str]: # if buffer length is not long enough, reallocate buffers if reserved_string_buffer_size < actual_string_buffer_size: string_buffers = [ctypes.create_string_buffer(actual_string_buffer_size) for _ in range(num_feature)] - ptr_string_buffers = (ctypes.c_char_p * num_feature)(*map(ctypes.addressof, string_buffers)) # type: ignore[misc] + ptr_string_buffers = (ctypes.c_char_p * num_feature)(*map(ctypes.addressof, string_buffers)) _safe_call(_LIB.LGBM_BoosterGetFeatureNames( self._handle, ctypes.c_int(num_feature), @@ -4918,7 +4686,7 @@ def __get_eval_info(self) -> None: string_buffers = [ ctypes.create_string_buffer(reserved_string_buffer_size) for _ in range(self.__num_inner_eval) ] - ptr_string_buffers = (ctypes.c_char_p * self.__num_inner_eval)(*map(ctypes.addressof, string_buffers)) # type: ignore[misc] + ptr_string_buffers = (ctypes.c_char_p * self.__num_inner_eval)(*map(ctypes.addressof, string_buffers)) _safe_call(_LIB.LGBM_BoosterGetEvalNames( self._handle, ctypes.c_int(self.__num_inner_eval), @@ -4934,7 +4702,7 @@ def __get_eval_info(self) -> None: string_buffers = [ ctypes.create_string_buffer(actual_string_buffer_size) for _ in range(self.__num_inner_eval) ] - ptr_string_buffers = (ctypes.c_char_p * self.__num_inner_eval)(*map(ctypes.addressof, string_buffers)) # type: ignore[misc] + ptr_string_buffers = (ctypes.c_char_p * self.__num_inner_eval)(*map(ctypes.addressof, string_buffers)) _safe_call(_LIB.LGBM_BoosterGetEvalNames( self._handle, ctypes.c_int(self.__num_inner_eval), diff --git a/python-package/lightgbm/callback.py b/python-package/lightgbm/callback.py index b68bb63c7f41..2f77ee740c75 100644 --- a/python-package/lightgbm/callback.py +++ b/python-package/lightgbm/callback.py @@ -229,12 +229,7 @@ def __call__(self, env: CallbackEnv) -> None: if new_param != env.params.get(key, None): new_parameters[key] = new_param if new_parameters: - if isinstance(env.model, Booster): - env.model.reset_parameter(new_parameters) - else: - # CVBooster holds a list of Booster objects, each needs to be updated - for booster in env.model.boosters: - booster.reset_parameter(new_parameters) + env.model.reset_parameter(new_parameters) env.params.update(new_parameters) @@ -272,10 +267,6 @@ def __init__( verbose: bool = True, min_delta: Union[float, List[float]] = 0.0 ) -> None: - - if not isinstance(stopping_rounds, int) or stopping_rounds <= 0: - raise ValueError(f"stopping_rounds should be an integer and greater than 0. got: {stopping_rounds}") - self.order = 30 self.before_iteration = False @@ -300,45 +291,32 @@ def _gt_delta(self, curr_score: float, best_score: float, delta: float) -> bool: def _lt_delta(self, curr_score: float, best_score: float, delta: float) -> bool: return curr_score < best_score - delta - def _is_train_set(self, ds_name: str, eval_name: str, env: CallbackEnv) -> bool: - """Check, by name, if a given Dataset is the training data.""" - # for lgb.cv() with eval_train_metric=True, evaluation is also done on the training set - # and those metrics are considered for early stopping - if ds_name == "cv_agg" and eval_name == "train": - return True - - # for lgb.train(), it's possible to pass the training data via valid_sets with any eval_name - if isinstance(env.model, Booster) and ds_name == env.model._train_data_name: - return True - - return False + def _is_train_set(self, ds_name: str, eval_name: str, train_name: str) -> bool: + return (ds_name == "cv_agg" and eval_name == "train") or ds_name == train_name def _init(self, env: CallbackEnv) -> None: if env.evaluation_result_list is None or env.evaluation_result_list == []: raise ValueError( "For early stopping, at least one dataset and eval metric is required for evaluation" ) - is_dart = any(env.params.get(alias, "") == 'dart' for alias in _ConfigAliases.get("boosting")) - if is_dart: - self.enabled = False - _log_warning('Early stopping is not available in dart mode') + only_train_set = ( + len(env.evaluation_result_list) == 1 + and self._is_train_set( + ds_name=env.evaluation_result_list[0][0], + eval_name=env.evaluation_result_list[0][1].split(" ")[0], + train_name=env.model._train_data_name) + ) + self.enabled = not is_dart and not only_train_set + if not self.enabled: + if is_dart: + _log_warning('Early stopping is not available in dart mode') + elif only_train_set: + _log_warning('Only training set found, disabling early stopping.') return - # validation sets are guaranteed to not be identical to the training data in cv() - if isinstance(env.model, Booster): - only_train_set = ( - len(env.evaluation_result_list) == 1 - and self._is_train_set( - ds_name=env.evaluation_result_list[0][0], - eval_name=env.evaluation_result_list[0][1].split(" ")[0], - env=env - ) - ) - if only_train_set: - self.enabled = False - _log_warning('Only training set found, disabling early stopping.') - return + if self.stopping_rounds <= 0: + raise ValueError("stopping_rounds should be greater than zero.") if self.verbose: _log_info(f"Training until validation scores don't improve for {self.stopping_rounds} rounds") @@ -417,11 +395,7 @@ def __call__(self, env: CallbackEnv) -> None: eval_name_splitted = env.evaluation_result_list[i][1].split(" ") if self.first_metric_only and self.first_metric != eval_name_splitted[-1]: continue # use only the first metric for early stopping - if self._is_train_set( - ds_name=env.evaluation_result_list[i][0], - eval_name=eval_name_splitted[0], - env=env - ): + if self._is_train_set(env.evaluation_result_list[i][0], eval_name_splitted[0], env.model._train_data_name): continue # train data for lgb.cv or sklearn wrapper (underlying lgb.train) elif env.iteration - self.best_iter[i] >= self.stopping_rounds: if self.verbose: diff --git a/python-package/lightgbm/compat.py b/python-package/lightgbm/compat.py index bd1b29a1e802..0a55ccd1e421 100644 --- a/python-package/lightgbm/compat.py +++ b/python-package/lightgbm/compat.py @@ -36,16 +36,6 @@ def __init__(self, *args, **kwargs): concat = None -"""numpy""" -try: - from numpy.random import Generator as np_random_Generator -except ImportError: - class np_random_Generator: # type: ignore - """Dummy class for np.random.Generator.""" - - def __init__(self, *args, **kwargs): - pass - """matplotlib""" try: import matplotlib # noqa: F401 @@ -195,59 +185,6 @@ class dask_Series: # type: ignore def __init__(self, *args, **kwargs): pass -"""pyarrow""" -try: - import pyarrow.compute as pa_compute - from pyarrow import Array as pa_Array - from pyarrow import ChunkedArray as pa_ChunkedArray - from pyarrow import Table as pa_Table - from pyarrow import chunked_array as pa_chunked_array - from pyarrow.cffi import ffi as arrow_cffi - from pyarrow.types import is_floating as arrow_is_floating - from pyarrow.types import is_integer as arrow_is_integer - PYARROW_INSTALLED = True -except ImportError: - PYARROW_INSTALLED = False - - class pa_Array: # type: ignore - """Dummy class for pa.Array.""" - - def __init__(self, *args, **kwargs): - pass - - class pa_ChunkedArray: # type: ignore - """Dummy class for pa.ChunkedArray.""" - - def __init__(self, *args, **kwargs): - pass - - class pa_Table: # type: ignore - """Dummy class for pa.Table.""" - - def __init__(self, *args, **kwargs): - pass - - class arrow_cffi: # type: ignore - """Dummy class for pyarrow.cffi.ffi.""" - - CData = None - addressof = None - cast = None - new = None - - def __init__(self, *args, **kwargs): - pass - - class pa_compute: # type: ignore - """Dummy class for pyarrow.compute.""" - - all = None - equal = None - - pa_chunked_array = None - arrow_is_integer = None - arrow_is_floating = None - """cpu_count()""" try: from joblib import cpu_count diff --git a/python-package/lightgbm/dask.py b/python-package/lightgbm/dask.py index 094d49223952..ce5f7137b20c 100644 --- a/python-package/lightgbm/dask.py +++ b/python-package/lightgbm/dask.py @@ -1142,7 +1142,7 @@ def __init__( colsample_bytree: float = 1., reg_alpha: float = 0., reg_lambda: float = 0., - random_state: Optional[Union[int, np.random.RandomState, 'np.random.Generator']] = None, + random_state: Optional[Union[int, np.random.RandomState]] = None, n_jobs: Optional[int] = None, importance_type: str = 'split', validation_fraction: Optional[float] = 0.1, @@ -1348,7 +1348,7 @@ def __init__( colsample_bytree: float = 1., reg_alpha: float = 0., reg_lambda: float = 0., - random_state: Optional[Union[int, np.random.RandomState, 'np.random.Generator']] = None, + random_state: Optional[Union[int, np.random.RandomState]] = None, n_jobs: Optional[int] = None, importance_type: str = 'split', validation_fraction: Optional[float] = 0.1, @@ -1519,7 +1519,7 @@ def __init__( colsample_bytree: float = 1., reg_alpha: float = 0., reg_lambda: float = 0., - random_state: Optional[Union[int, np.random.RandomState, 'np.random.Generator']] = None, + random_state: Optional[Union[int, np.random.RandomState]] = None, n_jobs: Optional[int] = None, importance_type: str = 'split', validation_fraction: Optional[float] = 0.1, diff --git a/python-package/lightgbm/libpath.py b/python-package/lightgbm/libpath.py index 21222228b0c2..c096a6f1b5e2 100644 --- a/python-package/lightgbm/libpath.py +++ b/python-package/lightgbm/libpath.py @@ -16,7 +16,8 @@ def find_lib_path() -> List[str]: List of all found library paths to LightGBM. """ curr_path = Path(__file__).absolute() - dll_path = [curr_path.parents[1], + dll_path = [curr_path, + curr_path.parents[1], curr_path.parents[0] / 'bin', curr_path.parents[0] / 'lib'] if system() in ('Windows', 'Microsoft'): diff --git a/python-package/lightgbm/sklearn.py b/python-package/lightgbm/sklearn.py index 64be9e55e8af..f1a132637aa1 100644 --- a/python-package/lightgbm/sklearn.py +++ b/python-package/lightgbm/sklearn.py @@ -15,8 +15,8 @@ from .compat import (SKLEARN_INSTALLED, LGBMNotFittedError, _LGBMAssertAllFinite, _LGBMCheckArray, _LGBMCheckClassificationTargets, _LGBMCheckSampleWeight, _LGBMCheckXY, _LGBMClassifierBase, _LGBMComputeSampleWeight, _LGBMCpuCount, _LGBMLabelEncoder, _LGBMModelBase, _LGBMRegressorBase, - dt_DataTable, np_random_Generator, pd_DataFrame) -from .engine import train + dt_DataTable, pd_DataFrame) +from .engine import _make_n_folds, train __all__ = [ 'LGBMClassifier', @@ -86,36 +86,6 @@ _LGBM_ScikitValidSet = Tuple[_LGBM_ScikitMatrixLike, _LGBM_LabelType] -def _get_group_from_constructed_dataset(dataset: Dataset) -> Optional[np.ndarray]: - group = dataset.get_group() - error_msg = ( - "Estimators in lightgbm.sklearn should only retrieve query groups from a constructed Dataset. " - "If you're seeing this message, it's a bug in lightgbm. Please report it at https://github.com/microsoft/LightGBM/issues." - ) - assert (group is None or isinstance(group, np.ndarray)), error_msg - return group - - -def _get_label_from_constructed_dataset(dataset: Dataset) -> np.ndarray: - label = dataset.get_label() - error_msg = ( - "Estimators in lightgbm.sklearn should only retrieve labels from a constructed Dataset. " - "If you're seeing this message, it's a bug in lightgbm. Please report it at https://github.com/microsoft/LightGBM/issues." - ) - assert isinstance(label, np.ndarray), error_msg - return label - - -def _get_weight_from_constructed_dataset(dataset: Dataset) -> Optional[np.ndarray]: - weight = dataset.get_weight() - error_msg = ( - "Estimators in lightgbm.sklearn should only retrieve weights from a constructed Dataset. " - "If you're seeing this message, it's a bug in lightgbm. Please report it at https://github.com/microsoft/LightGBM/issues." - ) - assert (weight is None or isinstance(weight, np.ndarray)), error_msg - return weight - - class _ObjectiveFunctionWrapper: """Proxy class for objective function.""" @@ -181,22 +151,17 @@ def __call__(self, preds: np.ndarray, dataset: Dataset) -> Tuple[np.ndarray, np. The value of the second order derivative (Hessian) of the loss with respect to the elements of preds for each sample point. """ - labels = _get_label_from_constructed_dataset(dataset) + labels = dataset.get_label() argc = len(signature(self.func).parameters) if argc == 2: grad, hess = self.func(labels, preds) # type: ignore[call-arg] - return grad, hess - - weight = _get_weight_from_constructed_dataset(dataset) - if argc == 3: - grad, hess = self.func(labels, preds, weight) # type: ignore[call-arg] - return grad, hess - - if argc == 4: - group = _get_group_from_constructed_dataset(dataset) - return self.func(labels, preds, weight, group) # type: ignore[call-arg] - - raise TypeError(f"Self-defined objective function should have 2, 3 or 4 arguments, got {argc}") + elif argc == 3: + grad, hess = self.func(labels, preds, dataset.get_weight()) # type: ignore[call-arg] + elif argc == 4: + grad, hess = self.func(labels, preds, dataset.get_weight(), dataset.get_group()) # type: ignore [call-arg] + else: + raise TypeError(f"Self-defined objective function should have 2, 3 or 4 arguments, got {argc}") + return grad, hess class _EvalFunctionWrapper: @@ -264,20 +229,16 @@ def __call__( is_higher_better : bool Is eval result higher better, e.g. AUC is ``is_higher_better``. """ - labels = _get_label_from_constructed_dataset(dataset) + labels = dataset.get_label() argc = len(signature(self.func).parameters) if argc == 2: return self.func(labels, preds) # type: ignore[call-arg] - - weight = _get_weight_from_constructed_dataset(dataset) - if argc == 3: - return self.func(labels, preds, weight) # type: ignore[call-arg] - - if argc == 4: - group = _get_group_from_constructed_dataset(dataset) - return self.func(labels, preds, weight, group) # type: ignore[call-arg] - - raise TypeError(f"Self-defined eval function should have 2, 3 or 4 arguments, got {argc}") + elif argc == 3: + return self.func(labels, preds, dataset.get_weight()) # type: ignore[call-arg] + elif argc == 4: + return self.func(labels, preds, dataset.get_weight(), dataset.get_group()) # type: ignore[call-arg] + else: + raise TypeError(f"Self-defined eval function should have 2, 3 or 4 arguments, got {argc}") # documentation templates for LGBMModel methods are shared between the classes in @@ -448,7 +409,7 @@ def __init__( colsample_bytree: float = 1., reg_alpha: float = 0., reg_lambda: float = 0., - random_state: Optional[Union[int, np.random.RandomState, 'np.random.Generator']] = None, + random_state: Optional[Union[int, np.random.RandomState]] = None, n_jobs: Optional[int] = None, importance_type: str = 'split', validation_fraction: Optional[float] = 0.1, @@ -510,7 +471,7 @@ def __init__( random_state : int, RandomState object or None, optional (default=None) Random number seed. If int, this number is used to seed the C++ code. - If RandomState or Generator object (numpy), a random integer is picked based on its state to seed the C++ code. + If RandomState object (numpy), a random integer is picked based on its state to seed the C++ code. If None, default seeds in C++ code are used. n_jobs : int or None, optional (default=None) Number of parallel threads to use for training (can be changed at prediction time by @@ -717,10 +678,6 @@ def _process_params(self, stage: str) -> Dict[str, Any]: if isinstance(params['random_state'], np.random.RandomState): params['random_state'] = params['random_state'].randint(np.iinfo(np.int32).max) - elif isinstance(params['random_state'], np_random_Generator): - params['random_state'] = int( - params['random_state'].integers(np.iinfo(np.int32).max) - ) params = _choose_param_value( main_param_name="early_stopping_round", diff --git a/python-package/pyproject.toml b/python-package/pyproject.toml index d5586aa3857d..6e43dc242d1b 100644 --- a/python-package/pyproject.toml +++ b/python-package/pyproject.toml @@ -30,13 +30,9 @@ maintainers = [ name = "lightgbm" readme = "README.rst" requires-python = ">=3.6" -version = "4.2.0.99" +version = "4.1.0.99" [project.optional-dependencies] -arrow = [ - "cffi>=1.15.1", - "pyarrow>=6.0.1" -] dask = [ "dask[array,dataframe,distributed]>=2.0.0", "pandas>=0.24.0" @@ -63,7 +59,7 @@ build-backend = "scikit_build_core.build" # based on https://github.com/scikit-build/scikit-build-core#configuration [tool.scikit-build] -cmake.minimum-version = "3.18" +cmake.minimum-version = "3.15" ninja.minimum-version = "1.11" ninja.make-fallback = true cmake.args = [ diff --git a/src/application/application.cpp b/src/application/application.cpp index 0bb9eca13bf2..3e51136afc96 100644 --- a/src/application/application.cpp +++ b/src/application/application.cpp @@ -227,7 +227,7 @@ void Application::Predict() { TextReader result_reader(config_.output_result.c_str(), false); result_reader.ReadAllLines(); std::vector> pred_leaf(result_reader.Lines().size()); - #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) + #pragma omp parallel for schedule(static) for (int i = 0; i < static_cast(result_reader.Lines().size()); ++i) { pred_leaf[i] = Common::StringToArray(result_reader.Lines()[i], '\t'); // Free memory diff --git a/src/application/predictor.hpp b/src/application/predictor.hpp index 18eb01e30179..d1a8aca4d041 100644 --- a/src/application/predictor.hpp +++ b/src/application/predictor.hpp @@ -233,7 +233,7 @@ class Predictor { std::vector> oneline_features; std::vector result_to_write(lines.size()); OMP_INIT_EX(); - #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) firstprivate(oneline_features) + #pragma omp parallel for schedule(static) firstprivate(oneline_features) for (data_size_t i = 0; i < static_cast(lines.size()); ++i) { OMP_LOOP_EX_BEGIN(); oneline_features.clear(); diff --git a/src/boosting/cuda/cuda_score_updater.hpp b/src/boosting/cuda/cuda_score_updater.hpp index cb79b43b9f36..ec728777e66c 100644 --- a/src/boosting/cuda/cuda_score_updater.hpp +++ b/src/boosting/cuda/cuda_score_updater.hpp @@ -8,7 +8,7 @@ #ifdef USE_CUDA -#include +#include #include "../score_updater.hpp" diff --git a/src/boosting/gbdt.cpp b/src/boosting/gbdt.cpp index b75adab6d971..1f0a5405bf49 100644 --- a/src/boosting/gbdt.cpp +++ b/src/boosting/gbdt.cpp @@ -255,7 +255,7 @@ void GBDT::RefitTree(const std::vector>& tree_leaf_prediction) std::vector leaf_pred(num_data_); if (linear_tree_) { std::vector max_leaves_by_thread = std::vector(OMP_NUM_THREADS(), 0); - #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) + #pragma omp parallel for schedule(static) for (int i = 0; i < static_cast(tree_leaf_prediction.size()); ++i) { int tid = omp_get_thread_num(); for (size_t j = 0; j < tree_leaf_prediction[i].size(); ++j) { @@ -270,7 +270,7 @@ void GBDT::RefitTree(const std::vector>& tree_leaf_prediction) Boosting(); for (int tree_id = 0; tree_id < num_tree_per_iteration_; ++tree_id) { int model_index = iter * num_tree_per_iteration_ + tree_id; - #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) + #pragma omp parallel for schedule(static) for (int i = 0; i < num_data_; ++i) { leaf_pred[i] = tree_leaf_prediction[i][model_index]; CHECK_LT(leaf_pred[i], models_[model_index]->num_leaves()); @@ -348,7 +348,7 @@ bool GBDT::TrainOneIter(const score_t* gradients, const score_t* hessians) { if (data_sample_strategy_->IsHessianChange()) { // need to copy customized gradients when using GOSS int64_t total_size = static_cast(num_data_) * num_tree_per_iteration_; - #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) + #pragma omp parallel for schedule(static) for (int64_t i = 0; i < total_size; ++i) { gradients_[i] = gradients[i]; hessians_[i] = hessians[i]; @@ -669,7 +669,7 @@ void GBDT::GetPredictAt(int data_idx, double* out_result, int64_t* out_len) { } #endif // USE_CUDA if (objective_function_ != nullptr) { - #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) + #pragma omp parallel for schedule(static) for (data_size_t i = 0; i < num_data; ++i) { std::vector tree_pred(num_tree_per_iteration_); for (int j = 0; j < num_tree_per_iteration_; ++j) { @@ -682,7 +682,7 @@ void GBDT::GetPredictAt(int data_idx, double* out_result, int64_t* out_len) { } } } else { - #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) + #pragma omp parallel for schedule(static) for (data_size_t i = 0; i < num_data; ++i) { for (int j = 0; j < num_tree_per_iteration_; ++j) { out_result[j * num_data + i] = static_cast(raw_scores[j * num_data + i]); diff --git a/src/boosting/gbdt.h b/src/boosting/gbdt.h index 28ebee446fad..e38b26be3e14 100644 --- a/src/boosting/gbdt.h +++ b/src/boosting/gbdt.h @@ -179,20 +179,15 @@ class GBDT : public GBDTBase { const auto pair = Common::Split(line.c_str(), ":"); if (pair[1] == " ]") continue; - const auto param = pair[0].substr(1); - const auto value_str = pair[1].substr(1, pair[1].size() - 2); - auto iter = param_types.find(param); - if (iter == param_types.end()) { - Log::Warning("Ignoring unrecognized parameter '%s' found in model string.", param.c_str()); - continue; - } - std::string param_type = iter->second; if (first) { first = false; str_buf << "\""; } else { str_buf << ",\""; } + const auto param = pair[0].substr(1); + const auto value_str = pair[1].substr(1, pair[1].size() - 2); + const auto param_type = param_types.at(param); str_buf << param << "\": "; if (param_type == "string") { str_buf << "\"" << value_str << "\""; @@ -434,7 +429,7 @@ class GBDT : public GBDTBase { } start_iteration_for_pred_ = start_iteration; if (is_pred_contrib) { - #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) + #pragma omp parallel for schedule(static) for (int i = 0; i < static_cast(models_.size()); ++i) { models_[i]->RecomputeMaxDepth(); } diff --git a/src/boosting/gbdt_model_text.cpp b/src/boosting/gbdt_model_text.cpp index 27be5afe066e..73c3ea98d3f6 100644 --- a/src/boosting/gbdt_model_text.cpp +++ b/src/boosting/gbdt_model_text.cpp @@ -354,7 +354,7 @@ std::string GBDT::SaveModelToString(int start_iteration, int num_iteration, int std::vector tree_strs(num_used_model - start_model); std::vector tree_sizes(num_used_model - start_model); // output tree models - #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) + #pragma omp parallel for schedule(static) for (int i = start_model; i < num_used_model; ++i) { const int idx = i - start_model; tree_strs[idx] = "Tree=" + std::to_string(idx) + '\n'; @@ -552,7 +552,7 @@ bool GBDT::LoadModelFromString(const char* buffer, size_t len) { models_.emplace_back(nullptr); } OMP_INIT_EX(); - #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) + #pragma omp parallel for schedule(static) for (int i = 0; i < num_trees; ++i) { OMP_LOOP_EX_BEGIN(); auto cur_p = p + tree_boundries[i]; diff --git a/src/boosting/rf.hpp b/src/boosting/rf.hpp index e6101dc30a39..88ece154e432 100644 --- a/src/boosting/rf.hpp +++ b/src/boosting/rf.hpp @@ -97,7 +97,7 @@ class RF : public GBDT { } size_t total_size = static_cast(num_data_) * num_tree_per_iteration_; std::vector tmp_scores(total_size, 0.0f); - #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) + #pragma omp parallel for schedule(static) for (int j = 0; j < num_tree_per_iteration_; ++j) { size_t offset = static_cast(j)* num_data_; for (data_size_t i = 0; i < num_data_; ++i) { diff --git a/src/boosting/score_updater.hpp b/src/boosting/score_updater.hpp index 2333b36ec029..6e475455df7a 100644 --- a/src/boosting/score_updater.hpp +++ b/src/boosting/score_updater.hpp @@ -39,7 +39,7 @@ class ScoreUpdater { Log::Fatal("Number of class for initial score error"); } has_init_score_ = true; -#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static, 512) if (total_size >= 1024) +#pragma omp parallel for schedule(static, 512) if (total_size >= 1024) for (int64_t i = 0; i < total_size; ++i) { score_[i] = init_score[i]; } @@ -54,7 +54,7 @@ class ScoreUpdater { virtual inline void AddScore(double val, int cur_tree_id) { Common::FunctionTimer fun_timer("ScoreUpdater::AddScore", global_timer); const size_t offset = static_cast(num_data_) * cur_tree_id; -#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static, 512) if (num_data_ >= 1024) +#pragma omp parallel for schedule(static, 512) if (num_data_ >= 1024) for (int i = 0; i < num_data_; ++i) { score_[offset + i] += val; } @@ -62,7 +62,7 @@ class ScoreUpdater { virtual inline void MultiplyScore(double val, int cur_tree_id) { const size_t offset = static_cast(num_data_) * cur_tree_id; -#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static, 512) if (num_data_ >= 1024) +#pragma omp parallel for schedule(static, 512) if (num_data_ >= 1024) for (int i = 0; i < num_data_; ++i) { score_[offset + i] *= val; } diff --git a/src/c_api.cpp b/src/c_api.cpp index 67b18003588a..8c4eee96b4c9 100644 --- a/src/c_api.cpp +++ b/src/c_api.cpp @@ -4,7 +4,6 @@ */ #include -#include #include #include #include @@ -438,7 +437,7 @@ class Booster { int64_t num_pred_in_one_row = boosting_->NumPredictOneRow(start_iteration, num_iteration, is_predict_leaf, predict_contrib); auto pred_fun = predictor.GetPredictFunction(); OMP_INIT_EX(); - #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) + #pragma omp parallel for schedule(static) for (int i = 0; i < nrow; ++i) { OMP_LOOP_EX_BEGIN(); auto one_row = get_row_fun(i); @@ -460,7 +459,7 @@ class Booster { auto pred_sparse_fun = predictor.GetPredictSparseFunction(); std::vector>>& agg = *agg_ptr; OMP_INIT_EX(); - #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) + #pragma omp parallel for schedule(static) for (int64_t i = 0; i < nrow; ++i) { OMP_LOOP_EX_BEGIN(); auto one_row = get_row_fun(i); @@ -552,7 +551,7 @@ class Booster { indptr_index++; int64_t matrix_start_index = m * static_cast(agg.size()); OMP_INIT_EX(); - #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) + #pragma omp parallel for schedule(static) for (int64_t i = 0; i < static_cast(agg.size()); ++i) { OMP_LOOP_EX_BEGIN(); auto row_vector = agg[i]; @@ -664,7 +663,7 @@ class Booster { } // Note: we parallelize across matrices instead of rows because of the column_counts[m][col_idx] increment inside the loop OMP_INIT_EX(); - #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) + #pragma omp parallel for schedule(static) for (int m = 0; m < num_matrices; ++m) { OMP_LOOP_EX_BEGIN(); for (int64_t i = 0; i < static_cast(agg.size()); ++i) { @@ -833,8 +832,6 @@ class Booster { // explicitly declare symbols from LightGBM namespace using LightGBM::AllgatherFunction; -using LightGBM::ArrowChunkedArray; -using LightGBM::ArrowTable; using LightGBM::Booster; using LightGBM::Common::CheckElementsIntervalClosed; using LightGBM::Common::RemoveQuotationSymbol; @@ -1077,7 +1074,7 @@ int LGBM_DatasetPushRows(DatasetHandle dataset, p_dataset->ResizeRaw(p_dataset->num_numeric_features() + nrow); } OMP_INIT_EX(); - #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) + #pragma omp parallel for schedule(static) for (int i = 0; i < nrow; ++i) { OMP_LOOP_EX_BEGIN(); const int tid = omp_get_thread_num(); @@ -1119,7 +1116,7 @@ int LGBM_DatasetPushRowsWithMetadata(DatasetHandle dataset, const int max_omp_threads = p_dataset->omp_max_threads() > 0 ? p_dataset->omp_max_threads() : OMP_NUM_THREADS(); OMP_INIT_EX(); -#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) +#pragma omp parallel for schedule(static) for (int i = 0; i < nrow; ++i) { OMP_LOOP_EX_BEGIN(); // convert internal thread id to be unique based on external thread id @@ -1156,7 +1153,7 @@ int LGBM_DatasetPushRowsByCSR(DatasetHandle dataset, p_dataset->ResizeRaw(p_dataset->num_numeric_features() + nrow); } OMP_INIT_EX(); - #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) + #pragma omp parallel for schedule(static) for (int i = 0; i < nrow; ++i) { OMP_LOOP_EX_BEGIN(); const int tid = omp_get_thread_num(); @@ -1202,7 +1199,7 @@ int LGBM_DatasetPushRowsByCSRWithMetadata(DatasetHandle dataset, const int max_omp_threads = p_dataset->omp_max_threads() > 0 ? p_dataset->omp_max_threads() : OMP_NUM_THREADS(); OMP_INIT_EX(); -#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) +#pragma omp parallel for schedule(static) for (int i = 0; i < nrow; ++i) { OMP_LOOP_EX_BEGIN(); // convert internal thread id to be unique based on external thread id @@ -1322,7 +1319,7 @@ int LGBM_DatasetCreateFromMats(int32_t nmat, int32_t start_row = 0; for (int j = 0; j < nmat; ++j) { OMP_INIT_EX(); - #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) + #pragma omp parallel for schedule(static) for (int i = 0; i < nrow[j]; ++i) { OMP_LOOP_EX_BEGIN(); const int tid = omp_get_thread_num(); @@ -1397,7 +1394,7 @@ int LGBM_DatasetCreateFromCSR(const void* indptr, } } OMP_INIT_EX(); - #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) + #pragma omp parallel for schedule(static) for (int i = 0; i < nindptr - 1; ++i) { OMP_LOOP_EX_BEGIN(); const int tid = omp_get_thread_num(); @@ -1468,7 +1465,7 @@ int LGBM_DatasetCreateFromCSRFunc(void* get_row_funptr, OMP_INIT_EX(); std::vector> thread_buffer; - #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) private(thread_buffer) + #pragma omp parallel for schedule(static) private(thread_buffer) for (int i = 0; i < num_rows; ++i) { OMP_LOOP_EX_BEGIN(); { @@ -1509,7 +1506,7 @@ int LGBM_DatasetCreateFromCSC(const void* col_ptr, std::vector> sample_values(ncol_ptr - 1); std::vector> sample_idx(ncol_ptr - 1); OMP_INIT_EX(); - #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) + #pragma omp parallel for schedule(static) for (int i = 0; i < static_cast(sample_values.size()); ++i) { OMP_LOOP_EX_BEGIN(); CSC_RowIterator col_it(col_ptr, col_ptr_type, indices, data, data_type, ncol_ptr, nelem, i); @@ -1537,7 +1534,7 @@ int LGBM_DatasetCreateFromCSC(const void* col_ptr, reinterpret_cast(reference)); } OMP_INIT_EX(); - #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) + #pragma omp parallel for schedule(static) for (int i = 0; i < ncol_ptr - 1; ++i) { OMP_LOOP_EX_BEGIN(); const int tid = omp_get_thread_num(); @@ -1570,98 +1567,6 @@ int LGBM_DatasetCreateFromCSC(const void* col_ptr, API_END(); } -int LGBM_DatasetCreateFromArrow(int64_t n_chunks, - const ArrowArray* chunks, - const ArrowSchema* schema, - const char* parameters, - const DatasetHandle reference, - DatasetHandle *out) { - API_BEGIN(); - - auto param = Config::Str2Map(parameters); - Config config; - config.Set(param); - OMP_SET_NUM_THREADS(config.num_threads); - - std::unique_ptr ret; - - // Prepare the Arrow data - ArrowTable table(n_chunks, chunks, schema); - - // Initialize the dataset - if (reference == nullptr) { - // If there is no reference dataset, we first sample indices - auto sample_indices = CreateSampleIndices(static_cast(table.get_num_rows()), config); - auto sample_count = static_cast(sample_indices.size()); - std::vector> sample_values(table.get_num_columns()); - std::vector> sample_idx(table.get_num_columns()); - - // Then, we obtain sample values by parallelizing across columns - OMP_INIT_EX(); - #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) - for (int64_t j = 0; j < table.get_num_columns(); ++j) { - OMP_LOOP_EX_BEGIN(); - - // Values need to be copied from the record batches. - sample_values[j].reserve(sample_indices.size()); - sample_idx[j].reserve(sample_indices.size()); - - // The chunks are iterated over in the inner loop as columns can be treated independently. - int last_idx = 0; - int i = 0; - auto it = table.get_column(j).begin(); - for (auto idx : sample_indices) { - std::advance(it, idx - last_idx); - auto v = *it; - if (std::fabs(v) > kZeroThreshold || std::isnan(v)) { - sample_values[j].emplace_back(v); - sample_idx[j].emplace_back(i); - } - last_idx = idx; - i++; - } - OMP_LOOP_EX_END(); - } - OMP_THROW_EX(); - - // Finally, we initialize a loader from the sampled values - DatasetLoader loader(config, nullptr, 1, nullptr); - ret.reset(loader.ConstructFromSampleData(Vector2Ptr(&sample_values).data(), - Vector2Ptr(&sample_idx).data(), - table.get_num_columns(), - VectorSize(sample_values).data(), - sample_count, - table.get_num_rows(), - table.get_num_rows())); - } else { - ret.reset(new Dataset(static_cast(table.get_num_rows()))); - ret->CreateValid(reinterpret_cast(reference)); - if (ret->has_raw()) { - ret->ResizeRaw(static_cast(table.get_num_rows())); - } - } - - // After sampling and properly initializing all bins, we can add our data to the dataset. Here, - // we parallelize across rows. - OMP_INIT_EX(); - #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) - for (int64_t j = 0; j < table.get_num_columns(); ++j) { - OMP_LOOP_EX_BEGIN(); - const int tid = omp_get_thread_num(); - data_size_t idx = 0; - auto column = table.get_column(j); - for (auto it = column.begin(), end = column.end(); it != end; ++it) { - ret->PushOneValue(tid, idx++, j, *it); - } - OMP_LOOP_EX_END(); - } - OMP_THROW_EX(); - - ret->FinishLoad(); - *out = ret.release(); - API_END(); -} - int LGBM_DatasetGetSubset( const DatasetHandle handle, const int32_t* used_row_indices, @@ -1781,21 +1686,6 @@ int LGBM_DatasetSetField(DatasetHandle handle, API_END(); } -int LGBM_DatasetSetFieldFromArrow(DatasetHandle handle, - const char* field_name, - int64_t n_chunks, - const ArrowArray* chunks, - const ArrowSchema* schema) { - API_BEGIN(); - auto dataset = reinterpret_cast(handle); - ArrowChunkedArray ca(n_chunks, chunks, schema); - auto is_success = dataset->SetFieldFromArrow(field_name, ca); - if (!is_success) { - Log::Fatal("Input field is not supported"); - } - API_END(); -} - int LGBM_DatasetGetField(DatasetHandle handle, const char* field_name, int* out_len, @@ -2568,57 +2458,6 @@ int LGBM_BoosterPredictForMats(BoosterHandle handle, API_END(); } -int LGBM_BoosterPredictForArrow(BoosterHandle handle, - int64_t n_chunks, - const ArrowArray* chunks, - const ArrowSchema* schema, - int predict_type, - int start_iteration, - int num_iteration, - const char* parameter, - int64_t* out_len, - double* out_result) { - API_BEGIN(); - - // Apply the configuration - auto param = Config::Str2Map(parameter); - Config config; - config.Set(param); - OMP_SET_NUM_THREADS(config.num_threads); - - // Set up chunked array and iterators for all columns - ArrowTable table(n_chunks, chunks, schema); - std::vector> its; - its.reserve(table.get_num_columns()); - for (int64_t j = 0; j < table.get_num_columns(); ++j) { - its.emplace_back(table.get_column(j).begin()); - } - - // Build row function - auto num_columns = table.get_num_columns(); - auto row_fn = [num_columns, &its] (int row_idx) { - std::vector> result; - result.reserve(num_columns); - for (int64_t j = 0; j < num_columns; ++j) { - result.emplace_back(static_cast(j), its[j][row_idx]); - } - return result; - }; - - // Run prediction - Booster* ref_booster = reinterpret_cast(handle); - ref_booster->Predict(start_iteration, - num_iteration, - predict_type, - static_cast(table.get_num_rows()), - static_cast(table.get_num_columns()), - row_fn, - config, - out_result, - out_len); - API_END(); -} - int LGBM_BoosterSaveModel(BoosterHandle handle, int start_iteration, int num_iteration, @@ -2750,23 +2589,6 @@ int LGBM_NetworkInitWithFunctions(int num_machines, int rank, API_END(); } -int LGBM_SetMaxThreads(int num_threads) { - API_BEGIN(); - if (num_threads <= 0) { - LGBM_MAX_NUM_THREADS = -1; - } else { - LGBM_MAX_NUM_THREADS = num_threads; - } - API_END(); -} - -int LGBM_GetMaxThreads(int* out) { - API_BEGIN(); - *out = LGBM_MAX_NUM_THREADS; - API_END(); -} - - // ---- start of some help functions diff --git a/src/cuda/cuda_utils.cpp b/src/cuda/cuda_utils.cpp index b601f9395268..a7d0df697e24 100644 --- a/src/cuda/cuda_utils.cpp +++ b/src/cuda/cuda_utils.cpp @@ -5,7 +5,7 @@ #ifdef USE_CUDA -#include +#include namespace LightGBM { diff --git a/src/io/config.cpp b/src/io/config.cpp index e25bb6d4fd70..e8578046960a 100644 --- a/src/io/config.cpp +++ b/src/io/config.cpp @@ -389,6 +389,10 @@ void Config::CheckParamConflict() { if (deterministic) { Log::Warning("Although \"deterministic\" is set, the results ran by GPU may be non-deterministic."); } + if (use_quantized_grad) { + Log::Warning("Quantized training is not supported by CUDA tree learner. Switch to full precision training."); + use_quantized_grad = false; + } } // linear tree learner must be serial type and run on CPU device if (linear_tree) { diff --git a/src/io/dataset.cpp b/src/io/dataset.cpp index 058d7bd328ad..cd692afb031a 100644 --- a/src/io/dataset.cpp +++ b/src/io/dataset.cpp @@ -536,7 +536,7 @@ MultiValBin* Dataset::GetMultiBinFromSparseFeatures(const std::vector& std::vector most_freq_bins; double sum_sparse_rate = 0; for (int i = 0; i < num_feature; ++i) { -#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static, 1) +#pragma omp parallel for schedule(static, 1) for (int tid = 0; tid < num_threads; ++tid) { iters[tid].emplace_back( feature_groups_[multi_group_id]->SubFeatureIterator(i)); @@ -584,7 +584,7 @@ MultiValBin* Dataset::GetMultiBinFromAllFeatures(const std::vector& of for (int fid = 0; fid < feature_groups_[gid]->num_feature_; ++fid) { const auto& bin_mapper = feature_groups_[gid]->bin_mappers_[fid]; most_freq_bins.push_back(bin_mapper->GetMostFreqBin()); -#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static, 1) +#pragma omp parallel for schedule(static, 1) for (int tid = 0; tid < num_threads; ++tid) { iters[tid].emplace_back( feature_groups_[gid]->SubFeatureIterator(fid)); @@ -823,7 +823,7 @@ void Dataset::ReSize(data_size_t num_data) { if (num_data_ != num_data) { num_data_ = num_data; OMP_INIT_EX(); -#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) +#pragma omp parallel for schedule(static) for (int group = 0; group < num_groups_; ++group) { OMP_LOOP_EX_BEGIN(); feature_groups_[group]->ReSize(num_data_); @@ -856,7 +856,7 @@ void Dataset::CopySubrow(const Dataset* fullset, int num_copy_tasks = static_cast(group_ids.size()); OMP_INIT_EX(); - #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(dynamic) + #pragma omp parallel for schedule(dynamic) for (int task_id = 0; task_id < num_copy_tasks; ++task_id) { OMP_LOOP_EX_BEGIN(); int group = group_ids[task_id]; @@ -875,7 +875,7 @@ void Dataset::CopySubrow(const Dataset* fullset, num_numeric_features_ = fullset->num_numeric_features_; if (has_raw_) { ResizeRaw(num_used_indices); -#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) +#pragma omp parallel for schedule(static) for (int i = 0; i < num_used_indices; ++i) { for (int j = 0; j < num_numeric_features_; ++j) { raw_data_[j][i] = fullset->raw_data_[j][used_indices[i]]; @@ -897,23 +897,6 @@ void Dataset::CopySubrow(const Dataset* fullset, #endif // USE_CUDA } -bool Dataset::SetFieldFromArrow(const char* field_name, const ArrowChunkedArray &ca) { - std::string name(field_name); - name = Common::Trim(name); - if (name == std::string("label") || name == std::string("target")) { - metadata_.SetLabel(ca); - } else if (name == std::string("weight") || name == std::string("weights")) { - metadata_.SetWeights(ca); - } else if (name == std::string("init_score")) { - metadata_.SetInitScore(ca); - } else if (name == std::string("query") || name == std::string("group")) { - metadata_.SetQuery(ca); - } else { - return false; - } - return true; -} - bool Dataset::SetFloatField(const char* field_name, const float* field_data, data_size_t num_element) { std::string name(field_name); @@ -1299,7 +1282,7 @@ void Dataset::ConstructHistogramsInner( int16_t* ordered_gradients_and_hessians = reinterpret_cast(ordered_gradients); const int16_t* gradients_and_hessians = reinterpret_cast(gradients); if (USE_INDICES) { - #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static, 512) if (num_data >= 1024) + #pragma omp parallel for schedule(static, 512) if (num_data >= 1024) for (data_size_t i = 0; i < num_data; ++i) { ordered_gradients_and_hessians[i] = gradients_and_hessians[data_indices[i]]; } @@ -1309,7 +1292,7 @@ void Dataset::ConstructHistogramsInner( } else { if (USE_INDICES) { if (USE_HESSIAN) { - #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static, 512) if (num_data >= 1024) + #pragma omp parallel for schedule(static, 512) if (num_data >= 1024) for (data_size_t i = 0; i < num_data; ++i) { ordered_gradients[i] = gradients[data_indices[i]]; ordered_hessians[i] = hessians[data_indices[i]]; @@ -1317,7 +1300,7 @@ void Dataset::ConstructHistogramsInner( ptr_ordered_grad = ordered_gradients; ptr_ordered_hess = ordered_hessians; } else { - #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static, 512) if (num_data >= 1024) + #pragma omp parallel for schedule(static, 512) if (num_data >= 1024) for (data_size_t i = 0; i < num_data; ++i) { ordered_gradients[i] = gradients[data_indices[i]]; } diff --git a/src/io/dataset_loader.cpp b/src/io/dataset_loader.cpp index 84bf3907a43c..354936cfb01a 100644 --- a/src/io/dataset_loader.cpp +++ b/src/io/dataset_loader.cpp @@ -625,7 +625,7 @@ Dataset* DatasetLoader::ConstructFromSampleData(double** sample_values, if (Network::num_machines() == 1) { // if only one machine, find bin locally OMP_INIT_EX(); - #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(guided) + #pragma omp parallel for schedule(guided) for (int i = 0; i < num_col; ++i) { OMP_LOOP_EX_BEGIN(); if (ignore_features_.count(i) > 0) { @@ -674,7 +674,7 @@ Dataset* DatasetLoader::ConstructFromSampleData(double** sample_values, } len[num_machines - 1] = num_total_features - start[num_machines - 1]; OMP_INIT_EX(); - #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(guided) + #pragma omp parallel for schedule(guided) for (int i = 0; i < len[rank]; ++i) { OMP_LOOP_EX_BEGIN(); if (ignore_features_.count(start[rank] + i) > 0) { @@ -1136,7 +1136,7 @@ void DatasetLoader::ConstructBinMappersFromTextData(int rank, int num_machines, if (num_machines == 1) { // if only one machine, find bin locally OMP_INIT_EX(); - #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(guided) + #pragma omp parallel for schedule(guided) for (int i = 0; i < static_cast(sample_values.size()); ++i) { OMP_LOOP_EX_BEGIN(); if (ignore_features_.count(i) > 0) { @@ -1177,7 +1177,7 @@ void DatasetLoader::ConstructBinMappersFromTextData(int rank, int num_machines, } len[num_machines - 1] = dataset->num_total_features_ - start[num_machines - 1]; OMP_INIT_EX(); - #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(guided) + #pragma omp parallel for schedule(guided) for (int i = 0; i < len[rank]; ++i) { OMP_LOOP_EX_BEGIN(); if (ignore_features_.count(start[rank] + i) > 0) { @@ -1268,7 +1268,7 @@ void DatasetLoader::ExtractFeaturesFromMemory(std::vector* text_dat if (!predict_fun_) { OMP_INIT_EX(); // if doesn't need to prediction with initial model - #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) private(oneline_features) firstprivate(tmp_label, feature_row) + #pragma omp parallel for schedule(static) private(oneline_features) firstprivate(tmp_label, feature_row) for (data_size_t i = 0; i < dataset->num_data_; ++i) { OMP_LOOP_EX_BEGIN(); const int tid = omp_get_thread_num(); @@ -1319,7 +1319,7 @@ void DatasetLoader::ExtractFeaturesFromMemory(std::vector* text_dat OMP_INIT_EX(); // if need to prediction with initial model std::vector init_score(static_cast(dataset->num_data_) * num_class_); - #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) private(oneline_features) firstprivate(tmp_label, feature_row) + #pragma omp parallel for schedule(static) private(oneline_features) firstprivate(tmp_label, feature_row) for (data_size_t i = 0; i < dataset->num_data_; ++i) { OMP_LOOP_EX_BEGIN(); const int tid = omp_get_thread_num(); @@ -1394,7 +1394,7 @@ void DatasetLoader::ExtractFeaturesFromFile(const char* filename, const Parser* double tmp_label = 0.0f; std::vector feature_row(dataset->num_features_); OMP_INIT_EX(); - #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) private(oneline_features) firstprivate(tmp_label, feature_row) + #pragma omp parallel for schedule(static) private(oneline_features) firstprivate(tmp_label, feature_row) for (data_size_t i = 0; i < static_cast(lines.size()); ++i) { OMP_LOOP_EX_BEGIN(); const int tid = omp_get_thread_num(); diff --git a/src/io/metadata.cpp b/src/io/metadata.cpp index 55440649f55e..1fc47c46787f 100644 --- a/src/io/metadata.cpp +++ b/src/io/metadata.cpp @@ -101,7 +101,7 @@ void Metadata::Init(const Metadata& fullset, const data_size_t* used_indices, da num_data_ = num_used_indices; label_ = std::vector(num_used_indices); -#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static, 512) if (num_used_indices >= 1024) +#pragma omp parallel for schedule(static, 512) if (num_used_indices >= 1024) for (data_size_t i = 0; i < num_used_indices; ++i) { label_[i] = fullset.label_[used_indices[i]]; } @@ -109,7 +109,7 @@ void Metadata::Init(const Metadata& fullset, const data_size_t* used_indices, da if (!fullset.weights_.empty()) { weights_ = std::vector(num_used_indices); num_weights_ = num_used_indices; -#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static, 512) if (num_used_indices >= 1024) +#pragma omp parallel for schedule(static, 512) if (num_used_indices >= 1024) for (data_size_t i = 0; i < num_used_indices; ++i) { weights_[i] = fullset.weights_[used_indices[i]]; } @@ -121,7 +121,7 @@ void Metadata::Init(const Metadata& fullset, const data_size_t* used_indices, da int num_class = static_cast(fullset.num_init_score_ / fullset.num_data_); init_score_ = std::vector(static_cast(num_used_indices) * num_class); num_init_score_ = static_cast(num_used_indices) * num_class; - #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) + #pragma omp parallel for schedule(static) for (int k = 0; k < num_class; ++k) { const size_t offset_dest = static_cast(k) * num_data_; const size_t offset_src = static_cast(k) * fullset.num_data_; @@ -173,7 +173,7 @@ void Metadata::PartitionLabel(const std::vector& used_indices) { auto old_label = label_; num_data_ = static_cast(used_indices.size()); label_ = std::vector(num_data_); -#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static, 512) if (num_data_ >= 1024) +#pragma omp parallel for schedule(static, 512) if (num_data_ >= 1024) for (data_size_t i = 0; i < num_data_; ++i) { label_[i] = old_label[used_indices[i]]; } @@ -255,7 +255,7 @@ void Metadata::CheckOrPartition(data_size_t num_all_data, const std::vector(num_data_); -#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static, 512) +#pragma omp parallel for schedule(static, 512) for (int i = 0; i < static_cast(used_data_indices.size()); ++i) { weights_[i] = old_weights[used_data_indices[i]]; } @@ -274,7 +274,7 @@ void Metadata::CheckOrPartition(data_size_t num_all_data, const std::vector(num_data_); - #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static, 512) + #pragma omp parallel for schedule(static, 512) for (int i = 0; i < static_cast(used_data_indices.size()); ++i) { positions_[i] = old_positions[used_data_indices[i]]; } @@ -335,7 +335,7 @@ void Metadata::CheckOrPartition(data_size_t num_all_data, const std::vector(num_init_score_ / num_all_data); num_init_score_ = static_cast(num_data_) * num_class; init_score_ = std::vector(num_init_score_); -#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) +#pragma omp parallel for schedule(static) for (int k = 0; k < num_class; ++k) { const size_t offset_dest = static_cast(k) * num_data_; const size_t offset_src = static_cast(k) * num_all_data; @@ -355,44 +355,32 @@ void Metadata::CheckOrPartition(data_size_t num_all_data, const std::vector -void Metadata::SetInitScoresFromIterator(It first, It last) { +void Metadata::SetInitScore(const double* init_score, data_size_t len) { std::lock_guard lock(mutex_); - // Clear init scores on empty input - if (last - first == 0) { + // save to nullptr + if (init_score == nullptr || len == 0) { init_score_.clear(); num_init_score_ = 0; return; } - if (((last - first) % num_data_) != 0) { + if ((len % num_data_) != 0) { Log::Fatal("Initial score size doesn't match data size"); } - if (init_score_.empty()) { - init_score_.resize(last - first); - } - num_init_score_ = last - first; + if (init_score_.empty()) { init_score_.resize(len); } + num_init_score_ = len; - #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static, 512) if (num_init_score_ >= 1024) + #pragma omp parallel for schedule(static, 512) if (num_init_score_ >= 1024) for (int64_t i = 0; i < num_init_score_; ++i) { - init_score_[i] = Common::AvoidInf(first[i]); + init_score_[i] = Common::AvoidInf(init_score[i]); } init_score_load_from_file_ = false; - #ifdef USE_CUDA if (cuda_metadata_ != nullptr) { - cuda_metadata_->SetInitScore(init_score_.data(), init_score_.size()); + cuda_metadata_->SetInitScore(init_score_.data(), len); } #endif // USE_CUDA } -void Metadata::SetInitScore(const double* init_score, data_size_t len) { - SetInitScoresFromIterator(init_score, init_score + len); -} - -void Metadata::SetInitScore(const ArrowChunkedArray& array) { - SetInitScoresFromIterator(array.begin(), array.end()); -} - void Metadata::InsertInitScores(const double* init_scores, data_size_t start_index, data_size_t len, data_size_t source_size) { if (num_init_score_ <= 0) { Log::Fatal("Inserting initial score data into dataset with no initial scores"); @@ -415,39 +403,27 @@ void Metadata::InsertInitScores(const double* init_scores, data_size_t start_ind // CUDA is handled after all insertions are complete } -template -void Metadata::SetLabelsFromIterator(It first, It last) { +void Metadata::SetLabel(const label_t* label, data_size_t len) { std::lock_guard lock(mutex_); - if (num_data_ != last - first) { - Log::Fatal("Length of labels differs from the length of #data"); + if (label == nullptr) { + Log::Fatal("label cannot be nullptr"); } - if (label_.empty()) { - label_.resize(num_data_); + if (num_data_ != len) { + Log::Fatal("Length of label is not same with #data"); } + if (label_.empty()) { label_.resize(num_data_); } - #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static, 512) if (num_data_ >= 1024) + #pragma omp parallel for schedule(static, 512) if (num_data_ >= 1024) for (data_size_t i = 0; i < num_data_; ++i) { - label_[i] = Common::AvoidInf(first[i]); + label_[i] = Common::AvoidInf(label[i]); } - #ifdef USE_CUDA if (cuda_metadata_ != nullptr) { - cuda_metadata_->SetLabel(label_.data(), label_.size()); + cuda_metadata_->SetLabel(label_.data(), len); } #endif // USE_CUDA } -void Metadata::SetLabel(const label_t* label, data_size_t len) { - if (label == nullptr) { - Log::Fatal("label cannot be nullptr"); - } - SetLabelsFromIterator(label, label + len); -} - -void Metadata::SetLabel(const ArrowChunkedArray& array) { - SetLabelsFromIterator(array.begin(), array.end()); -} - void Metadata::InsertLabels(const label_t* labels, data_size_t start_index, data_size_t len) { if (labels == nullptr) { Log::Fatal("label cannot be nullptr"); @@ -462,45 +438,33 @@ void Metadata::InsertLabels(const label_t* labels, data_size_t start_index, data // CUDA is handled after all insertions are complete } -template -void Metadata::SetWeightsFromIterator(It first, It last) { +void Metadata::SetWeights(const label_t* weights, data_size_t len) { std::lock_guard lock(mutex_); - // Clear weights on empty input - if (last - first == 0) { + // save to nullptr + if (weights == nullptr || len == 0) { weights_.clear(); num_weights_ = 0; return; } - if (num_data_ != last - first) { - Log::Fatal("Length of weights differs from the length of #data"); - } - if (weights_.empty()) { - weights_.resize(num_data_); + if (num_data_ != len) { + Log::Fatal("Length of weights is not same with #data"); } + if (weights_.empty()) { weights_.resize(num_data_); } num_weights_ = num_data_; - #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static, 512) if (num_weights_ >= 1024) + #pragma omp parallel for schedule(static, 512) if (num_weights_ >= 1024) for (data_size_t i = 0; i < num_weights_; ++i) { - weights_[i] = Common::AvoidInf(first[i]); + weights_[i] = Common::AvoidInf(weights[i]); } CalculateQueryWeights(); weight_load_from_file_ = false; - #ifdef USE_CUDA if (cuda_metadata_ != nullptr) { - cuda_metadata_->SetWeights(weights_.data(), weights_.size()); + cuda_metadata_->SetWeights(weights_.data(), len); } #endif // USE_CUDA } -void Metadata::SetWeights(const label_t* weights, data_size_t len) { - SetWeightsFromIterator(weights, weights + len); -} - -void Metadata::SetWeights(const ArrowChunkedArray& array) { - SetWeightsFromIterator(array.begin(), array.end()); -} - void Metadata::InsertWeights(const label_t* weights, data_size_t start_index, data_size_t len) { if (!weights) { Log::Fatal("Passed null weights"); @@ -519,34 +483,30 @@ void Metadata::InsertWeights(const label_t* weights, data_size_t start_index, da // CUDA is handled after all insertions are complete } -template -void Metadata::SetQueriesFromIterator(It first, It last) { +void Metadata::SetQuery(const data_size_t* query, data_size_t len) { std::lock_guard lock(mutex_); - // Clear query boundaries on empty input - if (last - first == 0) { + // save to nullptr + if (query == nullptr || len == 0) { query_boundaries_.clear(); num_queries_ = 0; return; } - data_size_t sum = 0; - #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) reduction(+:sum) - for (data_size_t i = 0; i < last - first; ++i) { - sum += first[i]; + #pragma omp parallel for schedule(static) reduction(+:sum) + for (data_size_t i = 0; i < len; ++i) { + sum += query[i]; } if (num_data_ != sum) { - Log::Fatal("Sum of query counts (%i) differs from the length of #data (%i)", num_data_, sum); + Log::Fatal("Sum of query counts is not same with #data"); } - num_queries_ = last - first; - + num_queries_ = len; query_boundaries_.resize(num_queries_ + 1); query_boundaries_[0] = 0; for (data_size_t i = 0; i < num_queries_; ++i) { - query_boundaries_[i + 1] = query_boundaries_[i] + first[i]; + query_boundaries_[i + 1] = query_boundaries_[i] + query[i]; } CalculateQueryWeights(); query_load_from_file_ = false; - #ifdef USE_CUDA if (cuda_metadata_ != nullptr) { if (query_weights_.size() > 0) { @@ -559,14 +519,6 @@ void Metadata::SetQueriesFromIterator(It first, It last) { #endif // USE_CUDA } -void Metadata::SetQuery(const data_size_t* query, data_size_t len) { - SetQueriesFromIterator(query, query + len); -} - -void Metadata::SetQuery(const ArrowChunkedArray& array) { - SetQueriesFromIterator(array.begin(), array.end()); -} - void Metadata::SetPosition(const data_size_t* positions, data_size_t len) { std::lock_guard lock(mutex_); // save to nullptr @@ -602,7 +554,7 @@ void Metadata::SetPosition(const data_size_t* positions, data_size_t len) { Log::Debug("number of unique positions found = %ld", position_ids_.size()); - #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static, 512) if (num_positions_ >= 1024) + #pragma omp parallel for schedule(static, 512) if (num_positions_ >= 1024) for (data_size_t i = 0; i < num_positions_; ++i) { positions_[i] = map_id2pos.at(positions[i]); } @@ -638,7 +590,7 @@ void Metadata::LoadWeights() { Log::Info("Loading weights..."); num_weights_ = static_cast(reader.Lines().size()); weights_ = std::vector(num_weights_); - #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) + #pragma omp parallel for schedule(static) for (data_size_t i = 0; i < num_weights_; ++i) { double tmp_weight = 0.0f; Common::Atof(reader.Lines()[i].c_str(), &tmp_weight); @@ -693,7 +645,7 @@ void Metadata::LoadInitialScore(const std::string& data_filename) { init_score_ = std::vector(num_init_score_); if (num_class == 1) { - #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) + #pragma omp parallel for schedule(static) for (data_size_t i = 0; i < num_line; ++i) { double tmp = 0.0f; Common::Atof(reader.Lines()[i].c_str(), &tmp); @@ -701,7 +653,7 @@ void Metadata::LoadInitialScore(const std::string& data_filename) { } } else { std::vector oneline_init_score; - #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) + #pragma omp parallel for schedule(static) for (data_size_t i = 0; i < num_line; ++i) { double tmp = 0.0f; oneline_init_score = Common::Split(reader.Lines()[i].c_str(), '\t'); diff --git a/src/io/multi_val_dense_bin.hpp b/src/io/multi_val_dense_bin.hpp index 2bab45f044cd..780272bdc4e1 100644 --- a/src/io/multi_val_dense_bin.hpp +++ b/src/io/multi_val_dense_bin.hpp @@ -271,7 +271,7 @@ class MultiValDenseBin : public MultiValBin { data_size_t block_size = num_data_; Threading::BlockInfo(num_data_, 1024, &n_block, &block_size); -#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static, 1) +#pragma omp parallel for schedule(static, 1) for (int tid = 0; tid < n_block; ++tid) { data_size_t start = tid * block_size; data_size_t end = std::min(num_data_, start + block_size); diff --git a/src/io/multi_val_sparse_bin.hpp b/src/io/multi_val_sparse_bin.hpp index edc48ca84c2a..32a5a51b4f89 100644 --- a/src/io/multi_val_sparse_bin.hpp +++ b/src/io/multi_val_sparse_bin.hpp @@ -85,7 +85,7 @@ class MultiValSparseBin : public MultiValBin { offsets[tid + 1] = offsets[tid] + sizes[tid + 1]; } data_.resize(row_ptr_[num_data_]); -#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static, 1) +#pragma omp parallel for schedule(static, 1) for (int tid = 0; tid < static_cast(t_data_.size()); ++tid) { std::copy_n(t_data_[tid].data(), sizes[tid + 1], data_.data() + offsets[tid]); @@ -344,7 +344,7 @@ class MultiValSparseBin : public MultiValBin { num_data_, 1024, &n_block, &block_size); std::vector sizes(t_data_.size() + 1, 0); const int pre_alloc_size = 50; -#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static, 1) +#pragma omp parallel for schedule(static, 1) for (int tid = 0; tid < n_block; ++tid) { data_size_t start = tid * block_size; data_size_t end = std::min(num_data_, start + block_size); diff --git a/src/io/train_share_states.cpp b/src/io/train_share_states.cpp index 4f2ec456c937..71b2e097ef1b 100644 --- a/src/io/train_share_states.cpp +++ b/src/io/train_share_states.cpp @@ -56,7 +56,7 @@ void MultiValBinWrapper::HistMove(const std::vector(hist_buf.data()) + hist_buf.size() / 2 - static_cast(num_bin_aligned_); - #pragma omp parallel for schedule(static) num_threads(num_threads_) + #pragma omp parallel for schedule(static) for (int i = 0; i < static_cast(hist_move_src_.size()); ++i) { std::copy_n(src + hist_move_src_[i] / 2, hist_move_size_[i] / 2, reinterpret_cast(origin_hist_data_) + hist_move_dest_[i] / 2); @@ -65,14 +65,14 @@ void MultiValBinWrapper::HistMove(const std::vector(hist_buf.data()) + hist_buf.size() / 2 - static_cast(num_bin_aligned_); if (is_use_subcol_) { - #pragma omp parallel for schedule(static) num_threads(num_threads_) + #pragma omp parallel for schedule(static) for (int i = 0; i < static_cast(hist_move_src_.size()); ++i) { std::copy_n(src + hist_move_src_[i] / 2, hist_move_size_[i] / 2, reinterpret_cast(origin_hist_data_) + hist_move_dest_[i] / 2); } } else { int32_t* orig_ptr = reinterpret_cast(origin_hist_data_); - #pragma omp parallel for schedule(static) num_threads(num_threads_) + #pragma omp parallel for schedule(static) for (int i = 0; i < num_bin_; ++i) { orig_ptr[i] = src[i]; } @@ -81,7 +81,7 @@ void MultiValBinWrapper::HistMove(const std::vector(num_bin_aligned_); - #pragma omp parallel for schedule(static) num_threads(num_threads_) + #pragma omp parallel for schedule(static) for (int i = 0; i < static_cast(hist_move_src_.size()); ++i) { std::copy_n(src + hist_move_src_[i], hist_move_size_[i], origin_hist_data_ + hist_move_dest_[i]); diff --git a/src/io/tree.cpp b/src/io/tree.cpp index 4312b4f65002..ce45d20cf454 100644 --- a/src/io/tree.cpp +++ b/src/io/tree.cpp @@ -153,7 +153,7 @@ int Tree::SplitCategorical(int leaf, int feature, int real_feature, const uint32 void Tree::AddPredictionToScore(const Dataset* data, data_size_t num_data, double* score) const { if (!is_linear_ && num_leaves_ <= 1) { if (leaf_value_[0] != 0.0f) { -#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static, 512) if (num_data >= 1024) +#pragma omp parallel for schedule(static, 512) if (num_data >= 1024) for (data_size_t i = 0; i < num_data; ++i) { score[i] += leaf_value_[0]; } @@ -234,7 +234,7 @@ void Tree::AddPredictionToScore(const Dataset* data, data_size_t num_data, double* score) const { if (!is_linear_ && num_leaves_ <= 1) { if (leaf_value_[0] != 0.0f) { -#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static, 512) if (num_data >= 1024) +#pragma omp parallel for schedule(static, 512) if (num_data >= 1024) for (data_size_t i = 0; i < num_data; ++i) { score[used_data_indices[i]] += leaf_value_[0]; } diff --git a/src/metric/binary_metric.hpp b/src/metric/binary_metric.hpp index 2fc840268e38..037f54ba091a 100644 --- a/src/metric/binary_metric.hpp +++ b/src/metric/binary_metric.hpp @@ -61,13 +61,13 @@ class BinaryMetric: public Metric { double sum_loss = 0.0f; if (objective == nullptr) { if (weights_ == nullptr) { - #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) reduction(+:sum_loss) + #pragma omp parallel for schedule(static) reduction(+:sum_loss) for (data_size_t i = 0; i < num_data_; ++i) { // add loss sum_loss += PointWiseLossCalculator::LossOnPoint(label_[i], score[i]); } } else { - #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) reduction(+:sum_loss) + #pragma omp parallel for schedule(static) reduction(+:sum_loss) for (data_size_t i = 0; i < num_data_; ++i) { // add loss sum_loss += PointWiseLossCalculator::LossOnPoint(label_[i], score[i]) * weights_[i]; @@ -75,7 +75,7 @@ class BinaryMetric: public Metric { } } else { if (weights_ == nullptr) { - #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) reduction(+:sum_loss) + #pragma omp parallel for schedule(static) reduction(+:sum_loss) for (data_size_t i = 0; i < num_data_; ++i) { double prob = 0; objective->ConvertOutput(&score[i], &prob); @@ -83,7 +83,7 @@ class BinaryMetric: public Metric { sum_loss += PointWiseLossCalculator::LossOnPoint(label_[i], prob); } } else { - #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) reduction(+:sum_loss) + #pragma omp parallel for schedule(static) reduction(+:sum_loss) for (data_size_t i = 0; i < num_data_; ++i) { double prob = 0; objective->ConvertOutput(&score[i], &prob); diff --git a/src/metric/cuda/cuda_binary_metric.hpp b/src/metric/cuda/cuda_binary_metric.hpp index 0f61063c620e..86dbdce986f5 100644 --- a/src/metric/cuda/cuda_binary_metric.hpp +++ b/src/metric/cuda/cuda_binary_metric.hpp @@ -10,7 +10,7 @@ #ifdef USE_CUDA #include -#include +#include #include diff --git a/src/metric/cuda/cuda_pointwise_metric.hpp b/src/metric/cuda/cuda_pointwise_metric.hpp index fafeafe635c1..dae1c6a7fa68 100644 --- a/src/metric/cuda/cuda_pointwise_metric.hpp +++ b/src/metric/cuda/cuda_pointwise_metric.hpp @@ -10,7 +10,7 @@ #ifdef USE_CUDA #include -#include +#include #include diff --git a/src/metric/cuda/cuda_regression_metric.hpp b/src/metric/cuda/cuda_regression_metric.hpp index e69bd221582c..4cfd996d837d 100644 --- a/src/metric/cuda/cuda_regression_metric.hpp +++ b/src/metric/cuda/cuda_regression_metric.hpp @@ -10,7 +10,7 @@ #ifdef USE_CUDA #include -#include +#include #include diff --git a/src/metric/map_metric.hpp b/src/metric/map_metric.hpp index 76fb4fe8a3e9..18539ee44ee0 100644 --- a/src/metric/map_metric.hpp +++ b/src/metric/map_metric.hpp @@ -111,7 +111,7 @@ class MapMetric:public Metric { } std::vector tmp_map(eval_at_.size(), 0.0f); if (query_weights_ == nullptr) { - #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(guided) firstprivate(tmp_map) + #pragma omp parallel for schedule(guided) firstprivate(tmp_map) for (data_size_t i = 0; i < num_queries_; ++i) { const int tid = omp_get_thread_num(); CalMapAtK(eval_at_, npos_per_query_[i], label_ + query_boundaries_[i], @@ -121,7 +121,7 @@ class MapMetric:public Metric { } } } else { - #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(guided) firstprivate(tmp_map) + #pragma omp parallel for schedule(guided) firstprivate(tmp_map) for (data_size_t i = 0; i < num_queries_; ++i) { const int tid = omp_get_thread_num(); CalMapAtK(eval_at_, npos_per_query_[i], label_ + query_boundaries_[i], diff --git a/src/metric/multiclass_metric.hpp b/src/metric/multiclass_metric.hpp index dbc769be2e3e..c83b2c842790 100644 --- a/src/metric/multiclass_metric.hpp +++ b/src/metric/multiclass_metric.hpp @@ -63,7 +63,7 @@ class MulticlassMetric: public Metric { } if (objective != nullptr) { if (weights_ == nullptr) { - #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) reduction(+:sum_loss) + #pragma omp parallel for schedule(static) reduction(+:sum_loss) for (data_size_t i = 0; i < num_data_; ++i) { std::vector raw_score(num_tree_per_iteration); for (int k = 0; k < num_tree_per_iteration; ++k) { @@ -76,7 +76,7 @@ class MulticlassMetric: public Metric { sum_loss += PointWiseLossCalculator::LossOnPoint(label_[i], &rec, config_); } } else { - #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) reduction(+:sum_loss) + #pragma omp parallel for schedule(static) reduction(+:sum_loss) for (data_size_t i = 0; i < num_data_; ++i) { std::vector raw_score(num_tree_per_iteration); for (int k = 0; k < num_tree_per_iteration; ++k) { @@ -91,7 +91,7 @@ class MulticlassMetric: public Metric { } } else { if (weights_ == nullptr) { - #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) reduction(+:sum_loss) + #pragma omp parallel for schedule(static) reduction(+:sum_loss) for (data_size_t i = 0; i < num_data_; ++i) { std::vector rec(num_tree_per_iteration); for (int k = 0; k < num_tree_per_iteration; ++k) { @@ -102,7 +102,7 @@ class MulticlassMetric: public Metric { sum_loss += PointWiseLossCalculator::LossOnPoint(label_[i], &rec, config_); } } else { - #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) reduction(+:sum_loss) + #pragma omp parallel for schedule(static) reduction(+:sum_loss) for (data_size_t i = 0; i < num_data_; ++i) { std::vector rec(num_tree_per_iteration); for (int k = 0; k < num_tree_per_iteration; ++k) { diff --git a/src/metric/rank_metric.hpp b/src/metric/rank_metric.hpp index e2adb8c082d4..888849950be3 100644 --- a/src/metric/rank_metric.hpp +++ b/src/metric/rank_metric.hpp @@ -57,7 +57,7 @@ class NDCGMetric:public Metric { } inverse_max_dcgs_.resize(num_queries_); // cache the inverse max DCG for all queries, used to calculate NDCG - #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) + #pragma omp parallel for schedule(static) for (data_size_t i = 0; i < num_queries_; ++i) { inverse_max_dcgs_[i].resize(eval_at_.size(), 0.0f); DCGCalculator::CalMaxDCG(eval_at_, label_ + query_boundaries_[i], @@ -92,7 +92,7 @@ class NDCGMetric:public Metric { } std::vector tmp_dcg(eval_at_.size(), 0.0f); if (query_weights_ == nullptr) { - #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) firstprivate(tmp_dcg) + #pragma omp parallel for schedule(static) firstprivate(tmp_dcg) for (data_size_t i = 0; i < num_queries_; ++i) { const int tid = omp_get_thread_num(); // if all doc in this query are all negative, let its NDCG=1 @@ -112,7 +112,7 @@ class NDCGMetric:public Metric { } } } else { - #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) firstprivate(tmp_dcg) + #pragma omp parallel for schedule(static) firstprivate(tmp_dcg) for (data_size_t i = 0; i < num_queries_; ++i) { const int tid = omp_get_thread_num(); // if all doc in this query are all negative, let its NDCG=1 diff --git a/src/metric/regression_metric.hpp b/src/metric/regression_metric.hpp index fbe6f2a062fb..3c4124aad4b9 100644 --- a/src/metric/regression_metric.hpp +++ b/src/metric/regression_metric.hpp @@ -59,13 +59,13 @@ class RegressionMetric: public Metric { double sum_loss = 0.0f; if (objective == nullptr) { if (weights_ == nullptr) { - #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) reduction(+:sum_loss) + #pragma omp parallel for schedule(static) reduction(+:sum_loss) for (data_size_t i = 0; i < num_data_; ++i) { // add loss sum_loss += PointWiseLossCalculator::LossOnPoint(label_[i], score[i], config_); } } else { - #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) reduction(+:sum_loss) + #pragma omp parallel for schedule(static) reduction(+:sum_loss) for (data_size_t i = 0; i < num_data_; ++i) { // add loss sum_loss += PointWiseLossCalculator::LossOnPoint(label_[i], score[i], config_) * weights_[i]; @@ -73,7 +73,7 @@ class RegressionMetric: public Metric { } } else { if (weights_ == nullptr) { - #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) reduction(+:sum_loss) + #pragma omp parallel for schedule(static) reduction(+:sum_loss) for (data_size_t i = 0; i < num_data_; ++i) { // add loss double t = 0; @@ -81,7 +81,7 @@ class RegressionMetric: public Metric { sum_loss += PointWiseLossCalculator::LossOnPoint(label_[i], t, config_); } } else { - #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) reduction(+:sum_loss) + #pragma omp parallel for schedule(static) reduction(+:sum_loss) for (data_size_t i = 0; i < num_data_; ++i) { // add loss double t = 0; diff --git a/src/metric/xentropy_metric.hpp b/src/metric/xentropy_metric.hpp index 9c7671c7546d..29d4984c64b3 100644 --- a/src/metric/xentropy_metric.hpp +++ b/src/metric/xentropy_metric.hpp @@ -107,26 +107,26 @@ class CrossEntropyMetric : public Metric { double sum_loss = 0.0f; if (objective == nullptr) { if (weights_ == nullptr) { - #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) reduction(+:sum_loss) + #pragma omp parallel for schedule(static) reduction(+:sum_loss) for (data_size_t i = 0; i < num_data_; ++i) { sum_loss += XentLoss(label_[i], score[i]); // NOTE: does not work unless score is a probability } } else { - #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) reduction(+:sum_loss) + #pragma omp parallel for schedule(static) reduction(+:sum_loss) for (data_size_t i = 0; i < num_data_; ++i) { sum_loss += XentLoss(label_[i], score[i]) * weights_[i]; // NOTE: does not work unless score is a probability } } } else { if (weights_ == nullptr) { - #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) reduction(+:sum_loss) + #pragma omp parallel for schedule(static) reduction(+:sum_loss) for (data_size_t i = 0; i < num_data_; ++i) { double p = 0; objective->ConvertOutput(&score[i], &p); sum_loss += XentLoss(label_[i], p); } } else { - #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) reduction(+:sum_loss) + #pragma omp parallel for schedule(static) reduction(+:sum_loss) for (data_size_t i = 0; i < num_data_; ++i) { double p = 0; objective->ConvertOutput(&score[i], &p); @@ -192,13 +192,13 @@ class CrossEntropyLambdaMetric : public Metric { double sum_loss = 0.0f; if (objective == nullptr) { if (weights_ == nullptr) { - #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) reduction(+:sum_loss) + #pragma omp parallel for schedule(static) reduction(+:sum_loss) for (data_size_t i = 0; i < num_data_; ++i) { double hhat = std::log1p(std::exp(score[i])); // auto-convert sum_loss += XentLambdaLoss(label_[i], 1.0f, hhat); } } else { - #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) reduction(+:sum_loss) + #pragma omp parallel for schedule(static) reduction(+:sum_loss) for (data_size_t i = 0; i < num_data_; ++i) { double hhat = std::log1p(std::exp(score[i])); // auto-convert sum_loss += XentLambdaLoss(label_[i], weights_[i], hhat); @@ -206,14 +206,14 @@ class CrossEntropyLambdaMetric : public Metric { } } else { if (weights_ == nullptr) { - #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) reduction(+:sum_loss) + #pragma omp parallel for schedule(static) reduction(+:sum_loss) for (data_size_t i = 0; i < num_data_; ++i) { double hhat = 0; objective->ConvertOutput(&score[i], &hhat); // NOTE: this only works if objective = "xentlambda" sum_loss += XentLambdaLoss(label_[i], 1.0f, hhat); } } else { - #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) reduction(+:sum_loss) + #pragma omp parallel for schedule(static) reduction(+:sum_loss) for (data_size_t i = 0; i < num_data_; ++i) { double hhat = 0; objective->ConvertOutput(&score[i], &hhat); // NOTE: this only works if objective = "xentlambda" @@ -299,26 +299,26 @@ class KullbackLeiblerDivergence : public Metric { double sum_loss = 0.0f; if (objective == nullptr) { if (weights_ == nullptr) { - #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) reduction(+:sum_loss) + #pragma omp parallel for schedule(static) reduction(+:sum_loss) for (data_size_t i = 0; i < num_data_; ++i) { sum_loss += XentLoss(label_[i], score[i]); // NOTE: does not work unless score is a probability } } else { - #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) reduction(+:sum_loss) + #pragma omp parallel for schedule(static) reduction(+:sum_loss) for (data_size_t i = 0; i < num_data_; ++i) { sum_loss += XentLoss(label_[i], score[i]) * weights_[i]; // NOTE: does not work unless score is a probability } } } else { if (weights_ == nullptr) { - #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) reduction(+:sum_loss) + #pragma omp parallel for schedule(static) reduction(+:sum_loss) for (data_size_t i = 0; i < num_data_; ++i) { double p = 0; objective->ConvertOutput(&score[i], &p); sum_loss += XentLoss(label_[i], p); } } else { - #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) reduction(+:sum_loss) + #pragma omp parallel for schedule(static) reduction(+:sum_loss) for (data_size_t i = 0; i < num_data_; ++i) { double p = 0; objective->ConvertOutput(&score[i], &p); diff --git a/src/objective/binary_objective.hpp b/src/objective/binary_objective.hpp index b189cc7701c4..b770972db9a1 100644 --- a/src/objective/binary_objective.hpp +++ b/src/objective/binary_objective.hpp @@ -63,7 +63,7 @@ class BinaryLogloss: public ObjectiveFunction { data_size_t cnt_positive = 0; data_size_t cnt_negative = 0; // count for positive and negative samples - #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) reduction(+:cnt_positive, cnt_negative) + #pragma omp parallel for schedule(static) reduction(+:cnt_positive, cnt_negative) for (data_size_t i = 0; i < num_data_; ++i) { if (is_pos_(label_[i])) { ++cnt_positive; @@ -107,7 +107,7 @@ class BinaryLogloss: public ObjectiveFunction { return; } if (weights_ == nullptr) { - #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) + #pragma omp parallel for schedule(static) for (data_size_t i = 0; i < num_data_; ++i) { // get label and label weights const int is_pos = is_pos_(label_[i]); @@ -120,7 +120,7 @@ class BinaryLogloss: public ObjectiveFunction { hessians[i] = static_cast(abs_response * (sigmoid_ - abs_response) * label_weight); } } else { - #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) + #pragma omp parallel for schedule(static) for (data_size_t i = 0; i < num_data_; ++i) { // get label and label weights const int is_pos = is_pos_(label_[i]); @@ -140,14 +140,14 @@ class BinaryLogloss: public ObjectiveFunction { double suml = 0.0f; double sumw = 0.0f; if (weights_ != nullptr) { - #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) reduction(+:suml, sumw) if (!deterministic_) + #pragma omp parallel for schedule(static) reduction(+:suml, sumw) if (!deterministic_) for (data_size_t i = 0; i < num_data_; ++i) { suml += is_pos_(label_[i]) * weights_[i]; sumw += weights_[i]; } } else { sumw = static_cast(num_data_); - #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) reduction(+:suml) if (!deterministic_) + #pragma omp parallel for schedule(static) reduction(+:suml) if (!deterministic_) for (data_size_t i = 0; i < num_data_; ++i) { suml += is_pos_(label_[i]); } diff --git a/src/objective/multiclass_objective.hpp b/src/objective/multiclass_objective.hpp index b2a49a9a40ca..1d9c8da17556 100644 --- a/src/objective/multiclass_objective.hpp +++ b/src/objective/multiclass_objective.hpp @@ -86,7 +86,7 @@ class MulticlassSoftmax: public ObjectiveFunction { void GetGradients(const double* score, score_t* gradients, score_t* hessians) const override { if (weights_ == nullptr) { std::vector rec; - #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) private(rec) + #pragma omp parallel for schedule(static) private(rec) for (data_size_t i = 0; i < num_data_; ++i) { rec.resize(num_class_); for (int k = 0; k < num_class_; ++k) { @@ -107,7 +107,7 @@ class MulticlassSoftmax: public ObjectiveFunction { } } else { std::vector rec; - #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) private(rec) + #pragma omp parallel for schedule(static) private(rec) for (data_size_t i = 0; i < num_data_; ++i) { rec.resize(num_class_); for (int k = 0; k < num_class_; ++k) { diff --git a/src/objective/rank_objective.hpp b/src/objective/rank_objective.hpp index ae3b74651759..6bd5324812f8 100644 --- a/src/objective/rank_objective.hpp +++ b/src/objective/rank_objective.hpp @@ -58,7 +58,7 @@ class RankingObjective : public ObjectiveFunction { void GetGradients(const double* score, score_t* gradients, score_t* hessians) const override { -#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(guided) +#pragma omp parallel for schedule(guided) for (data_size_t i = 0; i < num_queries_; ++i) { const data_size_t start = query_boundaries_[i]; const data_size_t cnt = query_boundaries_[i + 1] - query_boundaries_[i]; @@ -157,7 +157,7 @@ class LambdarankNDCG : public RankingObjective { DCGCalculator::CheckMetadata(metadata, num_queries_); DCGCalculator::CheckLabel(label_, num_data_); inverse_max_dcgs_.resize(num_queries_); -#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) +#pragma omp parallel for schedule(static) for (data_size_t i = 0; i < num_queries_; ++i) { inverse_max_dcgs_[i] = DCGCalculator::CalMaxDCGAtK( truncation_level_, label_ + query_boundaries_[i], @@ -289,12 +289,17 @@ class LambdarankNDCG : public RankingObjective { void UpdatePositionBiasFactors(const score_t* lambdas, const score_t* hessians) const override { /// get number of threads - int num_threads = OMP_NUM_THREADS(); + int num_threads = 1; + #pragma omp parallel + #pragma omp master + { + num_threads = omp_get_num_threads(); + } // create per-thread buffers for first and second derivatives of utility w.r.t. position bias factors std::vector bias_first_derivatives(num_position_ids_ * num_threads, 0.0); std::vector bias_second_derivatives(num_position_ids_ * num_threads, 0.0); std::vector instance_counts(num_position_ids_ * num_threads, 0); - #pragma omp parallel for schedule(guided) num_threads(num_threads) + #pragma omp parallel for schedule(guided) for (data_size_t i = 0; i < num_data_; i++) { // get thread ID const int tid = omp_get_thread_num(); @@ -305,7 +310,7 @@ class LambdarankNDCG : public RankingObjective { bias_second_derivatives[offset] -= hessians[i]; instance_counts[offset]++; } - #pragma omp parallel for schedule(guided) num_threads(num_threads) + #pragma omp parallel for schedule(guided) for (data_size_t i = 0; i < num_position_ids_; i++) { double bias_first_derivative = 0.0; double bias_second_derivative = 0.0; diff --git a/src/objective/regression_objective.hpp b/src/objective/regression_objective.hpp index 4f53319bbc49..eb149756c205 100644 --- a/src/objective/regression_objective.hpp +++ b/src/objective/regression_objective.hpp @@ -115,7 +115,7 @@ class RegressionL2loss: public ObjectiveFunction { label_ = metadata.label(); if (sqrt_) { trans_label_.resize(num_data_); - #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) + #pragma omp parallel for schedule(static) for (data_size_t i = 0; i < num_data; ++i) { trans_label_[i] = Common::Sign(label_[i]) * std::sqrt(std::fabs(label_[i])); } @@ -127,13 +127,13 @@ class RegressionL2loss: public ObjectiveFunction { void GetGradients(const double* score, score_t* gradients, score_t* hessians) const override { if (weights_ == nullptr) { - #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) + #pragma omp parallel for schedule(static) for (data_size_t i = 0; i < num_data_; ++i) { gradients[i] = static_cast(score[i] - label_[i]); hessians[i] = 1.0f; } } else { - #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) + #pragma omp parallel for schedule(static) for (data_size_t i = 0; i < num_data_; ++i) { gradients[i] = static_cast(static_cast((score[i] - label_[i])) * weights_[i]); hessians[i] = static_cast(weights_[i]); @@ -174,14 +174,14 @@ class RegressionL2loss: public ObjectiveFunction { double suml = 0.0f; double sumw = 0.0f; if (weights_ != nullptr) { - #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) reduction(+:suml, sumw) if (!deterministic_) + #pragma omp parallel for schedule(static) reduction(+:suml, sumw) if (!deterministic_) for (data_size_t i = 0; i < num_data_; ++i) { suml += static_cast(label_[i]) * weights_[i]; sumw += weights_[i]; } } else { sumw = static_cast(num_data_); - #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) reduction(+:suml) if (!deterministic_) + #pragma omp parallel for schedule(static) reduction(+:suml) if (!deterministic_) for (data_size_t i = 0; i < num_data_; ++i) { suml += label_[i]; } @@ -217,14 +217,14 @@ class RegressionL1loss: public RegressionL2loss { void GetGradients(const double* score, score_t* gradients, score_t* hessians) const override { if (weights_ == nullptr) { - #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) + #pragma omp parallel for schedule(static) for (data_size_t i = 0; i < num_data_; ++i) { const double diff = score[i] - label_[i]; gradients[i] = static_cast(Common::Sign(diff)); hessians[i] = 1.0f; } } else { - #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) + #pragma omp parallel for schedule(static) for (data_size_t i = 0; i < num_data_; ++i) { const double diff = score[i] - label_[i]; gradients[i] = static_cast(Common::Sign(diff) * weights_[i]); @@ -313,7 +313,7 @@ class RegressionHuberLoss: public RegressionL2loss { void GetGradients(const double* score, score_t* gradients, score_t* hessians) const override { if (weights_ == nullptr) { - #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) + #pragma omp parallel for schedule(static) for (data_size_t i = 0; i < num_data_; ++i) { const double diff = score[i] - label_[i]; if (std::abs(diff) <= alpha_) { @@ -324,7 +324,7 @@ class RegressionHuberLoss: public RegressionL2loss { hessians[i] = 1.0f; } } else { - #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) + #pragma omp parallel for schedule(static) for (data_size_t i = 0; i < num_data_; ++i) { const double diff = score[i] - label_[i]; if (std::abs(diff) <= alpha_) { @@ -362,14 +362,14 @@ class RegressionFairLoss: public RegressionL2loss { void GetGradients(const double* score, score_t* gradients, score_t* hessians) const override { if (weights_ == nullptr) { - #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) + #pragma omp parallel for schedule(static) for (data_size_t i = 0; i < num_data_; ++i) { const double x = score[i] - label_[i]; gradients[i] = static_cast(c_ * x / (std::fabs(x) + c_)); hessians[i] = static_cast(c_ * c_ / ((std::fabs(x) + c_) * (std::fabs(x) + c_))); } } else { - #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) + #pragma omp parallel for schedule(static) for (data_size_t i = 0; i < num_data_; ++i) { const double x = score[i] - label_[i]; gradients[i] = static_cast(c_ * x / (std::fabs(x) + c_) * weights_[i]); @@ -441,14 +441,14 @@ class RegressionPoissonLoss: public RegressionL2loss { score_t* hessians) const override { double exp_max_delta_step_ = std::exp(max_delta_step_); if (weights_ == nullptr) { - #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) + #pragma omp parallel for schedule(static) for (data_size_t i = 0; i < num_data_; ++i) { double exp_score = std::exp(score[i]); gradients[i] = static_cast(exp_score - label_[i]); hessians[i] = static_cast(exp_score * exp_max_delta_step_); } } else { - #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) + #pragma omp parallel for schedule(static) for (data_size_t i = 0; i < num_data_; ++i) { double exp_score = std::exp(score[i]); gradients[i] = static_cast((exp_score - label_[i]) * weights_[i]); @@ -493,7 +493,7 @@ class RegressionQuantileloss : public RegressionL2loss { void GetGradients(const double* score, score_t* gradients, score_t* hessians) const override { if (weights_ == nullptr) { - #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) + #pragma omp parallel for schedule(static) for (data_size_t i = 0; i < num_data_; ++i) { score_t delta = static_cast(score[i] - label_[i]); if (delta >= 0) { @@ -504,7 +504,7 @@ class RegressionQuantileloss : public RegressionL2loss { hessians[i] = 1.0f; } } else { - #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) + #pragma omp parallel for schedule(static) for (data_size_t i = 0; i < num_data_; ++i) { score_t delta = static_cast(score[i] - label_[i]); if (delta >= 0) { @@ -598,12 +598,12 @@ class RegressionMAPELOSS : public RegressionL1loss { } label_weight_.resize(num_data); if (weights_ == nullptr) { - #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) + #pragma omp parallel for schedule(static) for (data_size_t i = 0; i < num_data_; ++i) { label_weight_[i] = 1.0f / std::max(1.0f, std::fabs(label_[i])); } } else { - #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) + #pragma omp parallel for schedule(static) for (data_size_t i = 0; i < num_data_; ++i) { label_weight_[i] = 1.0f / std::max(1.0f, std::fabs(label_[i])) * weights_[i]; } @@ -613,14 +613,14 @@ class RegressionMAPELOSS : public RegressionL1loss { void GetGradients(const double* score, score_t* gradients, score_t* hessians) const override { if (weights_ == nullptr) { - #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) + #pragma omp parallel for schedule(static) for (data_size_t i = 0; i < num_data_; ++i) { const double diff = score[i] - label_[i]; gradients[i] = static_cast(Common::Sign(diff) * label_weight_[i]); hessians[i] = 1.0f; } } else { - #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) + #pragma omp parallel for schedule(static) for (data_size_t i = 0; i < num_data_; ++i) { const double diff = score[i] - label_[i]; gradients[i] = static_cast(Common::Sign(diff) * label_weight_[i]); @@ -690,14 +690,14 @@ class RegressionGammaLoss : public RegressionPoissonLoss { void GetGradients(const double* score, score_t* gradients, score_t* hessians) const override { if (weights_ == nullptr) { - #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) + #pragma omp parallel for schedule(static) for (data_size_t i = 0; i < num_data_; ++i) { double exp_score = std::exp(-score[i]); gradients[i] = static_cast(1.0 - label_[i] * exp_score); hessians[i] = static_cast(label_[i] * exp_score); } } else { - #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) + #pragma omp parallel for schedule(static) for (data_size_t i = 0; i < num_data_; ++i) { double exp_score = std::exp(-score[i]); gradients[i] = static_cast((1.0 - label_[i] * exp_score) * weights_[i]); @@ -728,7 +728,7 @@ class RegressionTweedieLoss: public RegressionPoissonLoss { void GetGradients(const double* score, score_t* gradients, score_t* hessians) const override { if (weights_ == nullptr) { - #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) + #pragma omp parallel for schedule(static) for (data_size_t i = 0; i < num_data_; ++i) { double exp_1_score = std::exp((1 - rho_) * score[i]); double exp_2_score = std::exp((2 - rho_) * score[i]); @@ -737,7 +737,7 @@ class RegressionTweedieLoss: public RegressionPoissonLoss { (2 - rho_) * exp_2_score); } } else { - #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) + #pragma omp parallel for schedule(static) for (data_size_t i = 0; i < num_data_; ++i) { double exp_1_score = std::exp((1 - rho_) * score[i]); double exp_2_score = std::exp((2 - rho_) * score[i]); diff --git a/src/objective/xentropy_objective.hpp b/src/objective/xentropy_objective.hpp index 8edb0950609e..513ccc1c2462 100644 --- a/src/objective/xentropy_objective.hpp +++ b/src/objective/xentropy_objective.hpp @@ -77,7 +77,7 @@ class CrossEntropy: public ObjectiveFunction { void GetGradients(const double* score, score_t* gradients, score_t* hessians) const override { if (weights_ == nullptr) { // compute pointwise gradients and Hessians with implied unit weights - #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) + #pragma omp parallel for schedule(static) for (data_size_t i = 0; i < num_data_; ++i) { const double z = 1.0f / (1.0f + std::exp(-score[i])); gradients[i] = static_cast(z - label_[i]); @@ -85,7 +85,7 @@ class CrossEntropy: public ObjectiveFunction { } } else { // compute pointwise gradients and Hessians with given weights - #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) + #pragma omp parallel for schedule(static) for (data_size_t i = 0; i < num_data_; ++i) { const double z = 1.0f / (1.0f + std::exp(-score[i])); gradients[i] = static_cast((z - label_[i]) * weights_[i]); @@ -114,7 +114,7 @@ class CrossEntropy: public ObjectiveFunction { double suml = 0.0f; double sumw = 0.0f; if (weights_ != nullptr) { - #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) reduction(+:suml, sumw) if (!deterministic_) + #pragma omp parallel for schedule(static) reduction(+:suml, sumw) if (!deterministic_) for (data_size_t i = 0; i < num_data_; ++i) { suml += static_cast(label_[i]) * weights_[i]; @@ -122,7 +122,7 @@ class CrossEntropy: public ObjectiveFunction { } } else { sumw = static_cast(num_data_); - #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) reduction(+:suml) if (!deterministic_) + #pragma omp parallel for schedule(static) reduction(+:suml) if (!deterministic_) for (data_size_t i = 0; i < num_data_; ++i) { suml += label_[i]; @@ -190,7 +190,7 @@ class CrossEntropyLambda: public ObjectiveFunction { void GetGradients(const double* score, score_t* gradients, score_t* hessians) const override { if (weights_ == nullptr) { // compute pointwise gradients and Hessians with implied unit weights; exactly equivalent to CrossEntropy with unit weights - #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) + #pragma omp parallel for schedule(static) for (data_size_t i = 0; i < num_data_; ++i) { const double z = 1.0f / (1.0f + std::exp(-score[i])); gradients[i] = static_cast(z - label_[i]); @@ -198,7 +198,7 @@ class CrossEntropyLambda: public ObjectiveFunction { } } else { // compute pointwise gradients and Hessians with given weights - #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) + #pragma omp parallel for schedule(static) for (data_size_t i = 0; i < num_data_; ++i) { const double w = weights_[i]; const double y = label_[i]; @@ -244,7 +244,7 @@ class CrossEntropyLambda: public ObjectiveFunction { double suml = 0.0f; double sumw = 0.0f; if (weights_ != nullptr) { - #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) reduction(+:suml, sumw) if (!deterministic_) + #pragma omp parallel for schedule(static) reduction(+:suml, sumw) if (!deterministic_) for (data_size_t i = 0; i < num_data_; ++i) { suml += static_cast(label_[i]) * weights_[i]; @@ -252,7 +252,7 @@ class CrossEntropyLambda: public ObjectiveFunction { } } else { sumw = static_cast(num_data_); - #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) reduction(+:suml) if (!deterministic_) + #pragma omp parallel for schedule(static) reduction(+:suml) if (!deterministic_) for (data_size_t i = 0; i < num_data_; ++i) { suml += label_[i]; diff --git a/src/treelearner/col_sampler.hpp b/src/treelearner/col_sampler.hpp index c70b07e50efa..6debe9db60ca 100644 --- a/src/treelearner/col_sampler.hpp +++ b/src/treelearner/col_sampler.hpp @@ -79,7 +79,7 @@ class ColSampler { static_cast(valid_feature_indices_.size()), used_cnt_bytree_); int omp_loop_size = static_cast(used_feature_indices_.size()); -#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static, 512) if (omp_loop_size >= 1024) +#pragma omp parallel for schedule(static, 512) if (omp_loop_size >= 1024) for (int i = 0; i < omp_loop_size; ++i) { int used_feature = valid_feature_indices_[used_feature_indices_[i]]; int inner_feature_index = train_data_->InnerFeatureIndex(used_feature); @@ -142,7 +142,7 @@ class ColSampler { auto sampled_indices = random_.Sample( static_cast((*allowed_used_feature_indices).size()), used_feature_cnt); int omp_loop_size = static_cast(sampled_indices.size()); -#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static, 512) if (omp_loop_size >= 1024) +#pragma omp parallel for schedule(static, 512) if (omp_loop_size >= 1024) for (int i = 0; i < omp_loop_size; ++i) { int used_feature = valid_feature_indices_[(*allowed_used_feature_indices)[sampled_indices[i]]]; @@ -168,7 +168,7 @@ class ColSampler { auto sampled_indices = random_.Sample( static_cast((*allowed_valid_feature_indices).size()), used_feature_cnt); int omp_loop_size = static_cast(sampled_indices.size()); -#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static, 512) if (omp_loop_size >= 1024) +#pragma omp parallel for schedule(static, 512) if (omp_loop_size >= 1024) for (int i = 0; i < omp_loop_size; ++i) { int used_feature = (*allowed_valid_feature_indices)[sampled_indices[i]]; int inner_feature_index = train_data_->InnerFeatureIndex(used_feature); diff --git a/src/treelearner/cuda/cuda_best_split_finder.cpp b/src/treelearner/cuda/cuda_best_split_finder.cpp index 95758542849c..761b62f21c2e 100644 --- a/src/treelearner/cuda/cuda_best_split_finder.cpp +++ b/src/treelearner/cuda/cuda_best_split_finder.cpp @@ -40,9 +40,6 @@ CUDABestSplitFinder::CUDABestSplitFinder( select_features_by_node_(select_features_by_node), cuda_hist_(cuda_hist) { InitFeatureMetaInfo(train_data); - if (has_categorical_feature_ && config->use_quantized_grad) { - Log::Fatal("Quantized training on GPU with categorical features is not supported yet."); - } cuda_leaf_best_split_info_ = nullptr; cuda_best_split_info_ = nullptr; cuda_best_split_info_buffer_ = nullptr; @@ -329,23 +326,13 @@ void CUDABestSplitFinder::FindBestSplitsForLeaf( const data_size_t num_data_in_smaller_leaf, const data_size_t num_data_in_larger_leaf, const double sum_hessians_in_smaller_leaf, - const double sum_hessians_in_larger_leaf, - const score_t* grad_scale, - const score_t* hess_scale, - const uint8_t smaller_num_bits_in_histogram_bins, - const uint8_t larger_num_bits_in_histogram_bins) { + const double sum_hessians_in_larger_leaf) { const bool is_smaller_leaf_valid = (num_data_in_smaller_leaf > min_data_in_leaf_ && sum_hessians_in_smaller_leaf > min_sum_hessian_in_leaf_); const bool is_larger_leaf_valid = (num_data_in_larger_leaf > min_data_in_leaf_ && sum_hessians_in_larger_leaf > min_sum_hessian_in_leaf_ && larger_leaf_index >= 0); - if (grad_scale != nullptr && hess_scale != nullptr) { - LaunchFindBestSplitsDiscretizedForLeafKernel(smaller_leaf_splits, larger_leaf_splits, - smaller_leaf_index, larger_leaf_index, is_smaller_leaf_valid, is_larger_leaf_valid, - grad_scale, hess_scale, smaller_num_bits_in_histogram_bins, larger_num_bits_in_histogram_bins); - } else { - LaunchFindBestSplitsForLeafKernel(smaller_leaf_splits, larger_leaf_splits, - smaller_leaf_index, larger_leaf_index, is_smaller_leaf_valid, is_larger_leaf_valid); - } + LaunchFindBestSplitsForLeafKernel(smaller_leaf_splits, larger_leaf_splits, + smaller_leaf_index, larger_leaf_index, is_smaller_leaf_valid, is_larger_leaf_valid); global_timer.Start("CUDABestSplitFinder::LaunchSyncBestSplitForLeafKernel"); LaunchSyncBestSplitForLeafKernel(smaller_leaf_index, larger_leaf_index, is_smaller_leaf_valid, is_larger_leaf_valid); SynchronizeCUDADevice(__FILE__, __LINE__); diff --git a/src/treelearner/cuda/cuda_best_split_finder.cu b/src/treelearner/cuda/cuda_best_split_finder.cu index d5c819d392c9..3fee5562953c 100644 --- a/src/treelearner/cuda/cuda_best_split_finder.cu +++ b/src/treelearner/cuda/cuda_best_split_finder.cu @@ -320,175 +320,6 @@ __device__ void FindBestSplitsForLeafKernelInner( } } -template -__device__ void FindBestSplitsDiscretizedForLeafKernelInner( - // input feature information - const BIN_HIST_TYPE* feature_hist_ptr, - // input task information - const SplitFindTask* task, - CUDARandom* cuda_random, - // input config parameter values - const double lambda_l1, - const double lambda_l2, - const double path_smooth, - const data_size_t min_data_in_leaf, - const double min_sum_hessian_in_leaf, - const double min_gain_to_split, - // input parent node information - const double parent_gain, - const int64_t sum_gradients_hessians, - const data_size_t num_data, - const double parent_output, - // gradient scale - const double grad_scale, - const double hess_scale, - // output parameters - CUDASplitInfo* cuda_best_split_info) { - const double sum_hessians = static_cast(sum_gradients_hessians & 0x00000000ffffffff) * hess_scale; - const double cnt_factor = num_data / sum_hessians; - const double min_gain_shift = parent_gain + min_gain_to_split; - - cuda_best_split_info->is_valid = false; - - ACC_HIST_TYPE local_grad_hess_hist = 0; - double local_gain = 0.0f; - bool threshold_found = false; - uint32_t threshold_value = 0; - __shared__ int rand_threshold; - if (USE_RAND && threadIdx.x == 0) { - if (task->num_bin - 2 > 0) { - rand_threshold = cuda_random->NextInt(0, task->num_bin - 2); - } - } - __shared__ uint32_t best_thread_index; - __shared__ double shared_double_buffer[32]; - __shared__ bool shared_bool_buffer[32]; - __shared__ uint32_t shared_int_buffer[64]; - const unsigned int threadIdx_x = threadIdx.x; - const bool skip_sum = REVERSE ? - (task->skip_default_bin && (task->num_bin - 1 - threadIdx_x) == static_cast(task->default_bin)) : - (task->skip_default_bin && (threadIdx_x + task->mfb_offset) == static_cast(task->default_bin)); - const uint32_t feature_num_bin_minus_offset = task->num_bin - task->mfb_offset; - if (!REVERSE) { - if (threadIdx_x < feature_num_bin_minus_offset && !skip_sum) { - const unsigned int bin_offset = threadIdx_x; - if (USE_16BIT_BIN_HIST && !USE_16BIT_ACC_HIST) { - const int32_t local_grad_hess_hist_int32 = feature_hist_ptr[bin_offset]; - local_grad_hess_hist = (static_cast(static_cast(local_grad_hess_hist_int32 >> 16)) << 32) | (static_cast(local_grad_hess_hist_int32 & 0x0000ffff)); - } else { - local_grad_hess_hist = feature_hist_ptr[bin_offset]; - } - } - } else { - if (threadIdx_x >= static_cast(task->na_as_missing) && - threadIdx_x < feature_num_bin_minus_offset && !skip_sum) { - const unsigned int read_index = feature_num_bin_minus_offset - 1 - threadIdx_x; - if (USE_16BIT_BIN_HIST && !USE_16BIT_ACC_HIST) { - const int32_t local_grad_hess_hist_int32 = feature_hist_ptr[read_index]; - local_grad_hess_hist = (static_cast(static_cast(local_grad_hess_hist_int32 >> 16)) << 32) | (static_cast(local_grad_hess_hist_int32 & 0x0000ffff)); - } else { - local_grad_hess_hist = feature_hist_ptr[read_index]; - } - } - } - __syncthreads(); - local_gain = kMinScore; - local_grad_hess_hist = ShufflePrefixSum(local_grad_hess_hist, reinterpret_cast(shared_int_buffer)); - double sum_left_gradient = 0.0f; - double sum_left_hessian = 0.0f; - double sum_right_gradient = 0.0f; - double sum_right_hessian = 0.0f; - data_size_t left_count = 0; - data_size_t right_count = 0; - int64_t sum_left_gradient_hessian = 0; - int64_t sum_right_gradient_hessian = 0; - if (REVERSE) { - if (threadIdx_x >= static_cast(task->na_as_missing) && threadIdx_x <= task->num_bin - 2 && !skip_sum) { - sum_right_gradient_hessian = USE_16BIT_ACC_HIST ? - (static_cast(static_cast(local_grad_hess_hist >> 16)) << 32) | static_cast(local_grad_hess_hist & 0x0000ffff) : - local_grad_hess_hist; - sum_right_gradient = static_cast(static_cast((sum_right_gradient_hessian & 0xffffffff00000000) >> 32)) * grad_scale; - sum_right_hessian = static_cast(static_cast(sum_right_gradient_hessian & 0x00000000ffffffff)) * hess_scale; - right_count = static_cast(__double2int_rn(sum_right_hessian * cnt_factor)); - sum_left_gradient_hessian = sum_gradients_hessians - sum_right_gradient_hessian; - sum_left_gradient = static_cast(static_cast((sum_left_gradient_hessian & 0xffffffff00000000)>> 32)) * grad_scale; - sum_left_hessian = static_cast(static_cast(sum_left_gradient_hessian & 0x00000000ffffffff)) * hess_scale; - left_count = num_data - right_count; - if (sum_left_hessian >= min_sum_hessian_in_leaf && left_count >= min_data_in_leaf && - sum_right_hessian >= min_sum_hessian_in_leaf && right_count >= min_data_in_leaf && - (!USE_RAND || static_cast(task->num_bin - 2 - threadIdx_x) == rand_threshold)) { - double current_gain = CUDALeafSplits::GetSplitGains( - sum_left_gradient, sum_left_hessian + kEpsilon, sum_right_gradient, - sum_right_hessian + kEpsilon, lambda_l1, - lambda_l2, path_smooth, left_count, right_count, parent_output); - // gain with split is worse than without split - if (current_gain > min_gain_shift) { - local_gain = current_gain - min_gain_shift; - threshold_value = static_cast(task->num_bin - 2 - threadIdx_x); - threshold_found = true; - } - } - } - } else { - if (threadIdx_x <= feature_num_bin_minus_offset - 2 && !skip_sum) { - sum_left_gradient_hessian = USE_16BIT_ACC_HIST ? - (static_cast(static_cast(local_grad_hess_hist >> 16)) << 32) | static_cast(local_grad_hess_hist & 0x0000ffff) : - local_grad_hess_hist; - sum_left_gradient = static_cast(static_cast((sum_left_gradient_hessian & 0xffffffff00000000) >> 32)) * grad_scale; - sum_left_hessian = static_cast(static_cast(sum_left_gradient_hessian & 0x00000000ffffffff)) * hess_scale; - left_count = static_cast(__double2int_rn(sum_left_hessian * cnt_factor)); - sum_right_gradient_hessian = sum_gradients_hessians - sum_left_gradient_hessian; - sum_right_gradient = static_cast(static_cast((sum_right_gradient_hessian & 0xffffffff00000000) >> 32)) * grad_scale; - sum_right_hessian = static_cast(static_cast(sum_right_gradient_hessian & 0x00000000ffffffff)) * hess_scale; - right_count = num_data - left_count; - if (sum_left_hessian >= min_sum_hessian_in_leaf && left_count >= min_data_in_leaf && - sum_right_hessian >= min_sum_hessian_in_leaf && right_count >= min_data_in_leaf && - (!USE_RAND || static_cast(threadIdx_x + task->mfb_offset) == rand_threshold)) { - double current_gain = CUDALeafSplits::GetSplitGains( - sum_left_gradient, sum_left_hessian + kEpsilon, sum_right_gradient, - sum_right_hessian + kEpsilon, lambda_l1, - lambda_l2, path_smooth, left_count, right_count, parent_output); - // gain with split is worse than without split - if (current_gain > min_gain_shift) { - local_gain = current_gain - min_gain_shift; - threshold_value = static_cast(threadIdx_x + task->mfb_offset); - threshold_found = true; - } - } - } - } - __syncthreads(); - const uint32_t result = ReduceBestGain(local_gain, threshold_found, threadIdx_x, shared_double_buffer, shared_bool_buffer, shared_int_buffer); - if (threadIdx_x == 0) { - best_thread_index = result; - } - __syncthreads(); - if (threshold_found && threadIdx_x == best_thread_index) { - cuda_best_split_info->is_valid = true; - cuda_best_split_info->threshold = threshold_value; - cuda_best_split_info->gain = local_gain; - cuda_best_split_info->default_left = task->assume_out_default_left; - const double left_output = CUDALeafSplits::CalculateSplittedLeafOutput(sum_left_gradient, - sum_left_hessian, lambda_l1, lambda_l2, path_smooth, left_count, parent_output); - const double right_output = CUDALeafSplits::CalculateSplittedLeafOutput(sum_right_gradient, - sum_right_hessian, lambda_l1, lambda_l2, path_smooth, right_count, parent_output); - cuda_best_split_info->left_sum_gradients = sum_left_gradient; - cuda_best_split_info->left_sum_hessians = sum_left_hessian; - cuda_best_split_info->left_sum_of_gradients_hessians = sum_left_gradient_hessian; - cuda_best_split_info->left_count = left_count; - cuda_best_split_info->right_sum_gradients = sum_right_gradient; - cuda_best_split_info->right_sum_hessians = sum_right_hessian; - cuda_best_split_info->right_sum_of_gradients_hessians = sum_right_gradient_hessian; - cuda_best_split_info->right_count = right_count; - cuda_best_split_info->left_value = left_output; - cuda_best_split_info->left_gain = CUDALeafSplits::GetLeafGainGivenOutput(sum_left_gradient, - sum_left_hessian, lambda_l1, lambda_l2, left_output); - cuda_best_split_info->right_value = right_output; - cuda_best_split_info->right_gain = CUDALeafSplits::GetLeafGainGivenOutput(sum_right_gradient, - sum_right_hessian, lambda_l1, lambda_l2, right_output); - } -} - template __device__ void FindBestSplitsForLeafKernelCategoricalInner( // input feature information @@ -884,169 +715,6 @@ __global__ void FindBestSplitsForLeafKernel( } } - -template -__global__ void FindBestSplitsDiscretizedForLeafKernel( - // input feature information - const int8_t* is_feature_used_bytree, - // input task information - const int num_tasks, - const SplitFindTask* tasks, - CUDARandom* cuda_randoms, - // input leaf information - const CUDALeafSplitsStruct* smaller_leaf_splits, - const CUDALeafSplitsStruct* larger_leaf_splits, - const uint8_t smaller_leaf_num_bits_in_histogram_bin, - const uint8_t larger_leaf_num_bits_in_histogram_bin, - // input config parameter values - const data_size_t min_data_in_leaf, - const double min_sum_hessian_in_leaf, - const double min_gain_to_split, - const double lambda_l1, - const double lambda_l2, - const double path_smooth, - const double cat_smooth, - const double cat_l2, - const int max_cat_threshold, - const int min_data_per_group, - const int max_cat_to_onehot, - // gradient scale - const score_t* grad_scale, - const score_t* hess_scale, - // output - CUDASplitInfo* cuda_best_split_info) { - const unsigned int task_index = blockIdx.x; - const SplitFindTask* task = tasks + task_index; - const int inner_feature_index = task->inner_feature_index; - const double parent_gain = IS_LARGER ? larger_leaf_splits->gain : smaller_leaf_splits->gain; - const int64_t sum_gradients_hessians = IS_LARGER ? larger_leaf_splits->sum_of_gradients_hessians : smaller_leaf_splits->sum_of_gradients_hessians; - const data_size_t num_data = IS_LARGER ? larger_leaf_splits->num_data_in_leaf : smaller_leaf_splits->num_data_in_leaf; - const double parent_output = IS_LARGER ? larger_leaf_splits->leaf_value : smaller_leaf_splits->leaf_value; - const unsigned int output_offset = IS_LARGER ? (task_index + num_tasks) : task_index; - CUDASplitInfo* out = cuda_best_split_info + output_offset; - CUDARandom* cuda_random = USE_RAND ? - (IS_LARGER ? cuda_randoms + task_index * 2 + 1 : cuda_randoms + task_index * 2) : nullptr; - const bool use_16bit_bin = IS_LARGER ? (larger_leaf_num_bits_in_histogram_bin <= 16) : (smaller_leaf_num_bits_in_histogram_bin <= 16); - if (is_feature_used_bytree[inner_feature_index]) { - if (task->is_categorical) { - __threadfence(); // ensure store issued before trap - asm("trap;"); - } else { - if (!task->reverse) { - if (use_16bit_bin) { - const int32_t* hist_ptr = - reinterpret_cast(IS_LARGER ? larger_leaf_splits->hist_in_leaf : smaller_leaf_splits->hist_in_leaf) + task->hist_offset; - FindBestSplitsDiscretizedForLeafKernelInner( - // input feature information - hist_ptr, - // input task information - task, - cuda_random, - // input config parameter values - lambda_l1, - lambda_l2, - path_smooth, - min_data_in_leaf, - min_sum_hessian_in_leaf, - min_gain_to_split, - // input parent node information - parent_gain, - sum_gradients_hessians, - num_data, - parent_output, - // gradient scale - *grad_scale, - *hess_scale, - // output parameters - out); - } else { - const int32_t* hist_ptr = - reinterpret_cast(IS_LARGER ? larger_leaf_splits->hist_in_leaf : smaller_leaf_splits->hist_in_leaf) + task->hist_offset; - FindBestSplitsDiscretizedForLeafKernelInner( - // input feature information - hist_ptr, - // input task information - task, - cuda_random, - // input config parameter values - lambda_l1, - lambda_l2, - path_smooth, - min_data_in_leaf, - min_sum_hessian_in_leaf, - min_gain_to_split, - // input parent node information - parent_gain, - sum_gradients_hessians, - num_data, - parent_output, - // gradient scale - *grad_scale, - *hess_scale, - // output parameters - out); - } - } else { - if (use_16bit_bin) { - const int32_t* hist_ptr = - reinterpret_cast(IS_LARGER ? larger_leaf_splits->hist_in_leaf : smaller_leaf_splits->hist_in_leaf) + task->hist_offset; - FindBestSplitsDiscretizedForLeafKernelInner( - // input feature information - hist_ptr, - // input task information - task, - cuda_random, - // input config parameter values - lambda_l1, - lambda_l2, - path_smooth, - min_data_in_leaf, - min_sum_hessian_in_leaf, - min_gain_to_split, - // input parent node information - parent_gain, - sum_gradients_hessians, - num_data, - parent_output, - // gradient scale - *grad_scale, - *hess_scale, - // output parameters - out); - } else { - const int32_t* hist_ptr = - reinterpret_cast(IS_LARGER ? larger_leaf_splits->hist_in_leaf : smaller_leaf_splits->hist_in_leaf) + task->hist_offset; - FindBestSplitsDiscretizedForLeafKernelInner( - // input feature information - hist_ptr, - // input task information - task, - cuda_random, - // input config parameter values - lambda_l1, - lambda_l2, - path_smooth, - min_data_in_leaf, - min_sum_hessian_in_leaf, - min_gain_to_split, - // input parent node information - parent_gain, - sum_gradients_hessians, - num_data, - parent_output, - // gradient scale - *grad_scale, - *hess_scale, - // output parameters - out); - } - } - } - } else { - out->is_valid = false; - } -} - template __device__ void FindBestSplitsForLeafKernelInner_GlobalMemory( // input feature information @@ -1798,108 +1466,6 @@ void CUDABestSplitFinder::LaunchFindBestSplitsForLeafKernelInner2(LaunchFindBest #undef FindBestSplitsForLeafKernel_ARGS #undef GlobalMemory_Buffer_ARGS - -#define LaunchFindBestSplitsDiscretizedForLeafKernel_PARAMS \ - const CUDALeafSplitsStruct* smaller_leaf_splits, \ - const CUDALeafSplitsStruct* larger_leaf_splits, \ - const int smaller_leaf_index, \ - const int larger_leaf_index, \ - const bool is_smaller_leaf_valid, \ - const bool is_larger_leaf_valid, \ - const score_t* grad_scale, \ - const score_t* hess_scale, \ - const uint8_t smaller_num_bits_in_histogram_bins, \ - const uint8_t larger_num_bits_in_histogram_bins - -#define LaunchFindBestSplitsDiscretizedForLeafKernel_ARGS \ - smaller_leaf_splits, \ - larger_leaf_splits, \ - smaller_leaf_index, \ - larger_leaf_index, \ - is_smaller_leaf_valid, \ - is_larger_leaf_valid, \ - grad_scale, \ - hess_scale, \ - smaller_num_bits_in_histogram_bins, \ - larger_num_bits_in_histogram_bins - -#define FindBestSplitsDiscretizedForLeafKernel_ARGS \ - cuda_is_feature_used_bytree_, \ - num_tasks_, \ - cuda_split_find_tasks_.RawData(), \ - cuda_randoms_.RawData(), \ - smaller_leaf_splits, \ - larger_leaf_splits, \ - smaller_num_bits_in_histogram_bins, \ - larger_num_bits_in_histogram_bins, \ - min_data_in_leaf_, \ - min_sum_hessian_in_leaf_, \ - min_gain_to_split_, \ - lambda_l1_, \ - lambda_l2_, \ - path_smooth_, \ - cat_smooth_, \ - cat_l2_, \ - max_cat_threshold_, \ - min_data_per_group_, \ - max_cat_to_onehot_, \ - grad_scale, \ - hess_scale, \ - cuda_best_split_info_ - -void CUDABestSplitFinder::LaunchFindBestSplitsDiscretizedForLeafKernel(LaunchFindBestSplitsDiscretizedForLeafKernel_PARAMS) { - if (!is_smaller_leaf_valid && !is_larger_leaf_valid) { - return; - } - if (!extra_trees_) { - LaunchFindBestSplitsDiscretizedForLeafKernelInner0(LaunchFindBestSplitsDiscretizedForLeafKernel_ARGS); - } else { - LaunchFindBestSplitsDiscretizedForLeafKernelInner0(LaunchFindBestSplitsDiscretizedForLeafKernel_ARGS); - } -} - -template -void CUDABestSplitFinder::LaunchFindBestSplitsDiscretizedForLeafKernelInner0(LaunchFindBestSplitsDiscretizedForLeafKernel_PARAMS) { - if (lambda_l1_ <= 0.0f) { - LaunchFindBestSplitsDiscretizedForLeafKernelInner1(LaunchFindBestSplitsDiscretizedForLeafKernel_ARGS); - } else { - LaunchFindBestSplitsDiscretizedForLeafKernelInner1(LaunchFindBestSplitsDiscretizedForLeafKernel_ARGS); - } -} - -template -void CUDABestSplitFinder::LaunchFindBestSplitsDiscretizedForLeafKernelInner1(LaunchFindBestSplitsDiscretizedForLeafKernel_PARAMS) { - if (!use_smoothing_) { - LaunchFindBestSplitsDiscretizedForLeafKernelInner2(LaunchFindBestSplitsDiscretizedForLeafKernel_ARGS); - } else { - LaunchFindBestSplitsDiscretizedForLeafKernelInner2(LaunchFindBestSplitsDiscretizedForLeafKernel_ARGS); - } -} - -template -void CUDABestSplitFinder::LaunchFindBestSplitsDiscretizedForLeafKernelInner2(LaunchFindBestSplitsDiscretizedForLeafKernel_PARAMS) { - if (!use_global_memory_) { - if (is_smaller_leaf_valid) { - FindBestSplitsDiscretizedForLeafKernel - <<>> - (FindBestSplitsDiscretizedForLeafKernel_ARGS); - } - SynchronizeCUDADevice(__FILE__, __LINE__); - if (is_larger_leaf_valid) { - FindBestSplitsDiscretizedForLeafKernel - <<>> - (FindBestSplitsDiscretizedForLeafKernel_ARGS); - } - } else { - // TODO(shiyu1994) - } -} - -#undef LaunchFindBestSplitsDiscretizedForLeafKernel_PARAMS -#undef LaunchFindBestSplitsDiscretizedForLeafKernel_ARGS -#undef FindBestSplitsDiscretizedForLeafKernel_ARGS - - __device__ void ReduceBestSplit(bool* found, double* gain, uint32_t* shared_read_index, uint32_t num_features_aligned) { const uint32_t threadIdx_x = threadIdx.x; diff --git a/src/treelearner/cuda/cuda_best_split_finder.hpp b/src/treelearner/cuda/cuda_best_split_finder.hpp index 2d9940312533..69f8169f8d85 100644 --- a/src/treelearner/cuda/cuda_best_split_finder.hpp +++ b/src/treelearner/cuda/cuda_best_split_finder.hpp @@ -67,11 +67,7 @@ class CUDABestSplitFinder { const data_size_t num_data_in_smaller_leaf, const data_size_t num_data_in_larger_leaf, const double sum_hessians_in_smaller_leaf, - const double sum_hessians_in_larger_leaf, - const score_t* grad_scale, - const score_t* hess_scale, - const uint8_t smaller_num_bits_in_histogram_bins, - const uint8_t larger_num_bits_in_histogram_bins); + const double sum_hessians_in_larger_leaf); const CUDASplitInfo* FindBestFromAllSplits( const int cur_num_leaves, @@ -118,31 +114,6 @@ class CUDABestSplitFinder { #undef LaunchFindBestSplitsForLeafKernel_PARAMS - #define LaunchFindBestSplitsDiscretizedForLeafKernel_PARAMS \ - const CUDALeafSplitsStruct* smaller_leaf_splits, \ - const CUDALeafSplitsStruct* larger_leaf_splits, \ - const int smaller_leaf_index, \ - const int larger_leaf_index, \ - const bool is_smaller_leaf_valid, \ - const bool is_larger_leaf_valid, \ - const score_t* grad_scale, \ - const score_t* hess_scale, \ - const uint8_t smaller_num_bits_in_histogram_bins, \ - const uint8_t larger_num_bits_in_histogram_bins - - void LaunchFindBestSplitsDiscretizedForLeafKernel(LaunchFindBestSplitsDiscretizedForLeafKernel_PARAMS); - - template - void LaunchFindBestSplitsDiscretizedForLeafKernelInner0(LaunchFindBestSplitsDiscretizedForLeafKernel_PARAMS); - - template - void LaunchFindBestSplitsDiscretizedForLeafKernelInner1(LaunchFindBestSplitsDiscretizedForLeafKernel_PARAMS); - - template - void LaunchFindBestSplitsDiscretizedForLeafKernelInner2(LaunchFindBestSplitsDiscretizedForLeafKernel_PARAMS); - - #undef LaunchFindBestSplitsDiscretizedForLeafKernel_PARAMS - void LaunchSyncBestSplitForLeafKernel( const int host_smaller_leaf_index, const int host_larger_leaf_index, diff --git a/src/treelearner/cuda/cuda_data_partition.cpp b/src/treelearner/cuda/cuda_data_partition.cpp index c09021ad356f..3ad157ef0105 100644 --- a/src/treelearner/cuda/cuda_data_partition.cpp +++ b/src/treelearner/cuda/cuda_data_partition.cpp @@ -368,12 +368,6 @@ void CUDADataPartition::ResetByLeafPred(const std::vector& leaf_pred, int n cur_num_leaves_ = num_leaves; } -void CUDADataPartition::ReduceLeafGradStat( - const score_t* gradients, const score_t* hessians, - CUDATree* tree, double* leaf_grad_stat_buffer, double* leaf_hess_state_buffer) const { - LaunchReduceLeafGradStat(gradients, hessians, tree, leaf_grad_stat_buffer, leaf_hess_state_buffer); -} - } // namespace LightGBM #endif // USE_CUDA diff --git a/src/treelearner/cuda/cuda_data_partition.cu b/src/treelearner/cuda/cuda_data_partition.cu index 3090b7a84176..b1d3fa496ab9 100644 --- a/src/treelearner/cuda/cuda_data_partition.cu +++ b/src/treelearner/cuda/cuda_data_partition.cu @@ -1069,53 +1069,6 @@ void CUDADataPartition::LaunchAddPredictionToScoreKernel(const double* leaf_valu global_timer.Stop("CUDADataPartition::AddPredictionToScoreKernel"); } -__global__ void RenewDiscretizedTreeLeavesKernel( - const score_t* gradients, - const score_t* hessians, - const data_size_t* data_indices, - const data_size_t* leaf_data_start, - const data_size_t* leaf_num_data, - double* leaf_grad_stat_buffer, - double* leaf_hess_stat_buffer, - double* leaf_values) { - __shared__ double shared_mem_buffer[32]; - const int leaf_index = static_cast(blockIdx.x); - const data_size_t* data_indices_in_leaf = data_indices + leaf_data_start[leaf_index]; - const data_size_t num_data_in_leaf = leaf_num_data[leaf_index]; - double sum_gradients = 0.0f; - double sum_hessians = 0.0f; - for (data_size_t inner_data_index = static_cast(threadIdx.x); - inner_data_index < num_data_in_leaf; inner_data_index += static_cast(blockDim.x)) { - const data_size_t data_index = data_indices_in_leaf[inner_data_index]; - const score_t gradient = gradients[data_index]; - const score_t hessian = hessians[data_index]; - sum_gradients += static_cast(gradient); - sum_hessians += static_cast(hessian); - } - sum_gradients = ShuffleReduceSum(sum_gradients, shared_mem_buffer, blockDim.x); - __syncthreads(); - sum_hessians = ShuffleReduceSum(sum_hessians, shared_mem_buffer, blockDim.x); - if (threadIdx.x == 0) { - leaf_grad_stat_buffer[leaf_index] = sum_gradients; - leaf_hess_stat_buffer[leaf_index] = sum_hessians; - } -} - -void CUDADataPartition::LaunchReduceLeafGradStat( - const score_t* gradients, const score_t* hessians, - CUDATree* tree, double* leaf_grad_stat_buffer, double* leaf_hess_state_buffer) const { - const int num_blocks = tree->num_leaves(); - RenewDiscretizedTreeLeavesKernel<<>>( - gradients, - hessians, - cuda_data_indices_, - cuda_leaf_data_start_, - cuda_leaf_num_data_, - leaf_grad_stat_buffer, - leaf_hess_state_buffer, - tree->cuda_leaf_value_ref()); -} - } // namespace LightGBM #endif // USE_CUDA diff --git a/src/treelearner/cuda/cuda_data_partition.hpp b/src/treelearner/cuda/cuda_data_partition.hpp index f6bbab9b8c65..84050565c085 100644 --- a/src/treelearner/cuda/cuda_data_partition.hpp +++ b/src/treelearner/cuda/cuda_data_partition.hpp @@ -78,10 +78,6 @@ class CUDADataPartition { void ResetByLeafPred(const std::vector& leaf_pred, int num_leaves); - void ReduceLeafGradStat( - const score_t* gradients, const score_t* hessians, - CUDATree* tree, double* leaf_grad_stat_buffer, double* leaf_hess_state_buffer) const; - data_size_t root_num_data() const { if (use_bagging_) { return num_used_indices_; @@ -296,10 +292,6 @@ class CUDADataPartition { void LaunchFillDataIndexToLeafIndex(); - void LaunchReduceLeafGradStat( - const score_t* gradients, const score_t* hessians, - CUDATree* tree, double* leaf_grad_stat_buffer, double* leaf_hess_state_buffer) const; - // Host memory // dataset information diff --git a/src/treelearner/cuda/cuda_gradient_discretizer.cu b/src/treelearner/cuda/cuda_gradient_discretizer.cu deleted file mode 100644 index bcea706b4285..000000000000 --- a/src/treelearner/cuda/cuda_gradient_discretizer.cu +++ /dev/null @@ -1,171 +0,0 @@ -/*! - * Copyright (c) 2021 Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See LICENSE file in the project root for - * license information. - */ - -#ifdef USE_CUDA - -#include - -#include - -#include "cuda_gradient_discretizer.hpp" - -namespace LightGBM { - -__global__ void ReduceMinMaxKernel( - const data_size_t num_data, - const score_t* input_gradients, - const score_t* input_hessians, - score_t* grad_min_block_buffer, - score_t* grad_max_block_buffer, - score_t* hess_min_block_buffer, - score_t* hess_max_block_buffer) { - __shared__ score_t shared_mem_buffer[32]; - const data_size_t index = static_cast(threadIdx.x + blockIdx.x * blockDim.x); - score_t grad_max_val = kMinScore; - score_t grad_min_val = kMaxScore; - score_t hess_max_val = kMinScore; - score_t hess_min_val = kMaxScore; - if (index < num_data) { - grad_max_val = input_gradients[index]; - grad_min_val = input_gradients[index]; - hess_max_val = input_hessians[index]; - hess_min_val = input_hessians[index]; - } - grad_min_val = ShuffleReduceMin(grad_min_val, shared_mem_buffer, blockDim.x); - __syncthreads(); - grad_max_val = ShuffleReduceMax(grad_max_val, shared_mem_buffer, blockDim.x); - __syncthreads(); - hess_min_val = ShuffleReduceMin(hess_min_val, shared_mem_buffer, blockDim.x); - __syncthreads(); - hess_max_val = ShuffleReduceMax(hess_max_val, shared_mem_buffer, blockDim.x); - if (threadIdx.x == 0) { - grad_min_block_buffer[blockIdx.x] = grad_min_val; - grad_max_block_buffer[blockIdx.x] = grad_max_val; - hess_min_block_buffer[blockIdx.x] = hess_min_val; - hess_max_block_buffer[blockIdx.x] = hess_max_val; - } -} - -__global__ void ReduceBlockMinMaxKernel( - const int num_blocks, - const int grad_discretize_bins, - score_t* grad_min_block_buffer, - score_t* grad_max_block_buffer, - score_t* hess_min_block_buffer, - score_t* hess_max_block_buffer) { - __shared__ score_t shared_mem_buffer[32]; - score_t grad_max_val = kMinScore; - score_t grad_min_val = kMaxScore; - score_t hess_max_val = kMinScore; - score_t hess_min_val = kMaxScore; - for (int block_index = static_cast(threadIdx.x); block_index < num_blocks; block_index += static_cast(blockDim.x)) { - grad_min_val = min(grad_min_val, grad_min_block_buffer[block_index]); - grad_max_val = max(grad_max_val, grad_max_block_buffer[block_index]); - hess_min_val = min(hess_min_val, hess_min_block_buffer[block_index]); - hess_max_val = max(hess_max_val, hess_max_block_buffer[block_index]); - } - grad_min_val = ShuffleReduceMin(grad_min_val, shared_mem_buffer, blockDim.x); - __syncthreads(); - grad_max_val = ShuffleReduceMax(grad_max_val, shared_mem_buffer, blockDim.x); - __syncthreads(); - hess_max_val = ShuffleReduceMax(hess_max_val, shared_mem_buffer, blockDim.x); - __syncthreads(); - hess_max_val = ShuffleReduceMax(hess_max_val, shared_mem_buffer, blockDim.x); - if (threadIdx.x == 0) { - const score_t grad_abs_max = max(fabs(grad_min_val), fabs(grad_max_val)); - const score_t hess_abs_max = max(fabs(hess_min_val), fabs(hess_max_val)); - grad_min_block_buffer[0] = 1.0f / (grad_abs_max / (grad_discretize_bins / 2)); - grad_max_block_buffer[0] = (grad_abs_max / (grad_discretize_bins / 2)); - hess_min_block_buffer[0] = 1.0f / (hess_abs_max / (grad_discretize_bins)); - hess_max_block_buffer[0] = (hess_abs_max / (grad_discretize_bins)); - } -} - -template -__global__ void DiscretizeGradientsKernel( - const data_size_t num_data, - const score_t* input_gradients, - const score_t* input_hessians, - const score_t* grad_scale_ptr, - const score_t* hess_scale_ptr, - const int iter, - const int* random_values_use_start, - const score_t* gradient_random_values, - const score_t* hessian_random_values, - const int grad_discretize_bins, - int8_t* output_gradients_and_hessians) { - const int start = random_values_use_start[iter]; - const data_size_t index = static_cast(threadIdx.x + blockIdx.x * blockDim.x); - const score_t grad_scale = *grad_scale_ptr; - const score_t hess_scale = *hess_scale_ptr; - int16_t* output_gradients_and_hessians_ptr = reinterpret_cast(output_gradients_and_hessians); - if (index < num_data) { - if (STOCHASTIC_ROUNDING) { - const data_size_t index_offset = (index + start) % num_data; - const score_t gradient = input_gradients[index]; - const score_t hessian = input_hessians[index]; - const score_t gradient_random_value = gradient_random_values[index_offset]; - const score_t hessian_random_value = hessian_random_values[index_offset]; - output_gradients_and_hessians_ptr[2 * index + 1] = gradient > 0.0f ? - static_cast(gradient * grad_scale + gradient_random_value) : - static_cast(gradient * grad_scale - gradient_random_value); - output_gradients_and_hessians_ptr[2 * index] = static_cast(hessian * hess_scale + hessian_random_value); - } else { - const score_t gradient = input_gradients[index]; - const score_t hessian = input_hessians[index]; - output_gradients_and_hessians_ptr[2 * index + 1] = gradient > 0.0f ? - static_cast(gradient * grad_scale + 0.5) : - static_cast(gradient * grad_scale - 0.5); - output_gradients_and_hessians_ptr[2 * index] = static_cast(hessian * hess_scale + 0.5); - } - } -} - -void CUDAGradientDiscretizer::DiscretizeGradients( - const data_size_t num_data, - const score_t* input_gradients, - const score_t* input_hessians) { - ReduceMinMaxKernel<<>>( - num_data, input_gradients, input_hessians, - grad_min_block_buffer_.RawData(), - grad_max_block_buffer_.RawData(), - hess_min_block_buffer_.RawData(), - hess_max_block_buffer_.RawData()); - SynchronizeCUDADevice(__FILE__, __LINE__); - ReduceBlockMinMaxKernel<<<1, CUDA_GRADIENT_DISCRETIZER_BLOCK_SIZE>>>( - num_reduce_blocks_, - num_grad_quant_bins_, - grad_min_block_buffer_.RawData(), - grad_max_block_buffer_.RawData(), - hess_min_block_buffer_.RawData(), - hess_max_block_buffer_.RawData()); - SynchronizeCUDADevice(__FILE__, __LINE__); - - #define DiscretizeGradientsKernel_ARGS \ - num_data, \ - input_gradients, \ - input_hessians, \ - grad_min_block_buffer_.RawData(), \ - hess_min_block_buffer_.RawData(), \ - iter_, \ - random_values_use_start_.RawData(), \ - gradient_random_values_.RawData(), \ - hessian_random_values_.RawData(), \ - num_grad_quant_bins_, \ - discretized_gradients_and_hessians_.RawData() - - if (stochastic_rounding_) { - DiscretizeGradientsKernel<<>>(DiscretizeGradientsKernel_ARGS); - } else { - DiscretizeGradientsKernel<<>>(DiscretizeGradientsKernel_ARGS); - } - SynchronizeCUDADevice(__FILE__, __LINE__); - ++iter_; -} - -} // namespace LightGBM - -#endif // USE_CUDA diff --git a/src/treelearner/cuda/cuda_gradient_discretizer.hpp b/src/treelearner/cuda/cuda_gradient_discretizer.hpp deleted file mode 100644 index d5c2fb0e041a..000000000000 --- a/src/treelearner/cuda/cuda_gradient_discretizer.hpp +++ /dev/null @@ -1,118 +0,0 @@ -/*! - * Copyright (c) 2021 Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See LICENSE file in the project root for - * license information. - */ - -#ifndef LIGHTGBM_TREELEARNER_CUDA_CUDA_GRADIENT_DISCRETIZER_HPP_ -#define LIGHTGBM_TREELEARNER_CUDA_CUDA_GRADIENT_DISCRETIZER_HPP_ - -#ifdef USE_CUDA - -#include -#include -#include -#include - -#include -#include -#include - -#include "cuda_leaf_splits.hpp" -#include "../gradient_discretizer.hpp" - -namespace LightGBM { - -#define CUDA_GRADIENT_DISCRETIZER_BLOCK_SIZE (1024) - -class CUDAGradientDiscretizer: public GradientDiscretizer { - public: - CUDAGradientDiscretizer(int num_grad_quant_bins, int num_trees, int random_seed, bool is_constant_hessian, bool stochastic_roudning): - GradientDiscretizer(num_grad_quant_bins, num_trees, random_seed, is_constant_hessian, stochastic_roudning) { - } - - void DiscretizeGradients( - const data_size_t num_data, - const score_t* input_gradients, - const score_t* input_hessians) override; - - const int8_t* discretized_gradients_and_hessians() const override { return discretized_gradients_and_hessians_.RawData(); } - - double grad_scale() const override { - Log::Fatal("grad_scale() of CUDAGradientDiscretizer should not be called."); - return 0.0; - } - - double hess_scale() const override { - Log::Fatal("hess_scale() of CUDAGradientDiscretizer should not be called."); - return 0.0; - } - - const score_t* grad_scale_ptr() const { return grad_max_block_buffer_.RawData(); } - - const score_t* hess_scale_ptr() const { return hess_max_block_buffer_.RawData(); } - - void Init(const data_size_t num_data, const int num_leaves, - const int num_features, const Dataset* train_data) override { - GradientDiscretizer::Init(num_data, num_leaves, num_features, train_data); - discretized_gradients_and_hessians_.Resize(num_data * 2); - num_reduce_blocks_ = (num_data + CUDA_GRADIENT_DISCRETIZER_BLOCK_SIZE - 1) / CUDA_GRADIENT_DISCRETIZER_BLOCK_SIZE; - grad_min_block_buffer_.Resize(num_reduce_blocks_); - grad_max_block_buffer_.Resize(num_reduce_blocks_); - hess_min_block_buffer_.Resize(num_reduce_blocks_); - hess_max_block_buffer_.Resize(num_reduce_blocks_); - random_values_use_start_.Resize(num_trees_); - gradient_random_values_.Resize(num_data); - hessian_random_values_.Resize(num_data); - - std::vector gradient_random_values(num_data, 0.0f); - std::vector hessian_random_values(num_data, 0.0f); - std::vector random_values_use_start(num_trees_, 0); - - const int num_threads = OMP_NUM_THREADS(); - - std::mt19937 random_values_use_start_eng = std::mt19937(random_seed_); - std::uniform_int_distribution random_values_use_start_dist = std::uniform_int_distribution(0, num_data); - for (int tree_index = 0; tree_index < num_trees_; ++tree_index) { - random_values_use_start[tree_index] = random_values_use_start_dist(random_values_use_start_eng); - } - - int num_blocks = 0; - data_size_t block_size = 0; - Threading::BlockInfo(num_data, 512, &num_blocks, &block_size); - #pragma omp parallel for schedule(static, 1) num_threads(num_threads) - for (int thread_id = 0; thread_id < num_blocks; ++thread_id) { - const data_size_t start = thread_id * block_size; - const data_size_t end = std::min(start + block_size, num_data); - std::mt19937 gradient_random_values_eng(random_seed_ + thread_id); - std::uniform_real_distribution gradient_random_values_dist(0.0f, 1.0f); - std::mt19937 hessian_random_values_eng(random_seed_ + thread_id + num_threads); - std::uniform_real_distribution hessian_random_values_dist(0.0f, 1.0f); - for (data_size_t i = start; i < end; ++i) { - gradient_random_values[i] = gradient_random_values_dist(gradient_random_values_eng); - hessian_random_values[i] = hessian_random_values_dist(hessian_random_values_eng); - } - } - - CopyFromHostToCUDADevice(gradient_random_values_.RawData(), gradient_random_values.data(), gradient_random_values.size(), __FILE__, __LINE__); - CopyFromHostToCUDADevice(hessian_random_values_.RawData(), hessian_random_values.data(), hessian_random_values.size(), __FILE__, __LINE__); - CopyFromHostToCUDADevice(random_values_use_start_.RawData(), random_values_use_start.data(), random_values_use_start.size(), __FILE__, __LINE__); - iter_ = 0; - } - - protected: - mutable CUDAVector discretized_gradients_and_hessians_; - mutable CUDAVector grad_min_block_buffer_; - mutable CUDAVector grad_max_block_buffer_; - mutable CUDAVector hess_min_block_buffer_; - mutable CUDAVector hess_max_block_buffer_; - CUDAVector random_values_use_start_; - CUDAVector gradient_random_values_; - CUDAVector hessian_random_values_; - int num_reduce_blocks_; -}; - -} // namespace LightGBM - -#endif // USE_CUDA -#endif // LIGHTGBM_TREELEARNER_CUDA_CUDA_GRADIENT_DISCRETIZER_HPP_ diff --git a/src/treelearner/cuda/cuda_histogram_constructor.cpp b/src/treelearner/cuda/cuda_histogram_constructor.cpp index 659db2aad24c..7e6be1c1069c 100644 --- a/src/treelearner/cuda/cuda_histogram_constructor.cpp +++ b/src/treelearner/cuda/cuda_histogram_constructor.cpp @@ -20,9 +20,7 @@ CUDAHistogramConstructor::CUDAHistogramConstructor( const int min_data_in_leaf, const double min_sum_hessian_in_leaf, const int gpu_device_id, - const bool gpu_use_dp, - const bool use_quantized_grad, - const int num_grad_quant_bins): + const bool gpu_use_dp): num_data_(train_data->num_data()), num_features_(train_data->num_features()), num_leaves_(num_leaves), @@ -30,14 +28,24 @@ CUDAHistogramConstructor::CUDAHistogramConstructor( min_data_in_leaf_(min_data_in_leaf), min_sum_hessian_in_leaf_(min_sum_hessian_in_leaf), gpu_device_id_(gpu_device_id), - gpu_use_dp_(gpu_use_dp), - use_quantized_grad_(use_quantized_grad), - num_grad_quant_bins_(num_grad_quant_bins) { + gpu_use_dp_(gpu_use_dp) { InitFeatureMetaInfo(train_data, feature_hist_offsets); cuda_row_data_.reset(nullptr); + cuda_feature_num_bins_ = nullptr; + cuda_feature_hist_offsets_ = nullptr; + cuda_feature_most_freq_bins_ = nullptr; + cuda_hist_ = nullptr; + cuda_need_fix_histogram_features_ = nullptr; + cuda_need_fix_histogram_features_num_bin_aligned_ = nullptr; } CUDAHistogramConstructor::~CUDAHistogramConstructor() { + DeallocateCUDAMemory(&cuda_feature_num_bins_, __FILE__, __LINE__); + DeallocateCUDAMemory(&cuda_feature_hist_offsets_, __FILE__, __LINE__); + DeallocateCUDAMemory(&cuda_feature_most_freq_bins_, __FILE__, __LINE__); + DeallocateCUDAMemory(&cuda_hist_, __FILE__, __LINE__); + DeallocateCUDAMemory(&cuda_need_fix_histogram_features_, __FILE__, __LINE__); + DeallocateCUDAMemory(&cuda_need_fix_histogram_features_num_bin_aligned_, __FILE__, __LINE__); gpuAssert(cudaStreamDestroy(cuda_stream_), __FILE__, __LINE__); } @@ -76,70 +84,54 @@ void CUDAHistogramConstructor::InitFeatureMetaInfo(const Dataset* train_data, co void CUDAHistogramConstructor::BeforeTrain(const score_t* gradients, const score_t* hessians) { cuda_gradients_ = gradients; cuda_hessians_ = hessians; - cuda_hist_.SetValue(0); + SetCUDAMemory(cuda_hist_, 0, num_total_bin_ * 2 * num_leaves_, __FILE__, __LINE__); } void CUDAHistogramConstructor::Init(const Dataset* train_data, TrainingShareStates* share_state) { - cuda_hist_.Resize(static_cast(num_total_bin_ * 2 * num_leaves_)); - cuda_hist_.SetValue(0); + AllocateCUDAMemory(&cuda_hist_, num_total_bin_ * 2 * num_leaves_, __FILE__, __LINE__); + SetCUDAMemory(cuda_hist_, 0, num_total_bin_ * 2 * num_leaves_, __FILE__, __LINE__); - cuda_feature_num_bins_.InitFromHostVector(feature_num_bins_); - cuda_feature_hist_offsets_.InitFromHostVector(feature_hist_offsets_); - cuda_feature_most_freq_bins_.InitFromHostVector(feature_most_freq_bins_); + InitCUDAMemoryFromHostMemory(&cuda_feature_num_bins_, + feature_num_bins_.data(), feature_num_bins_.size(), __FILE__, __LINE__); + + InitCUDAMemoryFromHostMemory(&cuda_feature_hist_offsets_, + feature_hist_offsets_.data(), feature_hist_offsets_.size(), __FILE__, __LINE__); + + InitCUDAMemoryFromHostMemory(&cuda_feature_most_freq_bins_, + feature_most_freq_bins_.data(), feature_most_freq_bins_.size(), __FILE__, __LINE__); cuda_row_data_.reset(new CUDARowData(train_data, share_state, gpu_device_id_, gpu_use_dp_)); cuda_row_data_->Init(train_data, share_state); CUDASUCCESS_OR_FATAL(cudaStreamCreate(&cuda_stream_)); - cuda_need_fix_histogram_features_.InitFromHostVector(need_fix_histogram_features_); - cuda_need_fix_histogram_features_num_bin_aligned_.InitFromHostVector(need_fix_histogram_features_num_bin_aligend_); + InitCUDAMemoryFromHostMemory(&cuda_need_fix_histogram_features_, need_fix_histogram_features_.data(), need_fix_histogram_features_.size(), __FILE__, __LINE__); + InitCUDAMemoryFromHostMemory(&cuda_need_fix_histogram_features_num_bin_aligned_, need_fix_histogram_features_num_bin_aligend_.data(), + need_fix_histogram_features_num_bin_aligend_.size(), __FILE__, __LINE__); if (cuda_row_data_->NumLargeBinPartition() > 0) { int grid_dim_x = 0, grid_dim_y = 0, block_dim_x = 0, block_dim_y = 0; CalcConstructHistogramKernelDim(&grid_dim_x, &grid_dim_y, &block_dim_x, &block_dim_y, num_data_); - const size_t buffer_size = static_cast(grid_dim_y) * static_cast(num_total_bin_); - if (!use_quantized_grad_) { - if (gpu_use_dp_) { - // need to double the size of histogram buffer in global memory when using double precision in histogram construction - cuda_hist_buffer_.Resize(buffer_size * 4); - } else { - cuda_hist_buffer_.Resize(buffer_size * 2); - } - } else { - // use only half the size of histogram buffer in global memory when quantized training since each gradient and hessian takes only 2 bytes - cuda_hist_buffer_.Resize(buffer_size); - } + const size_t buffer_size = static_cast(grid_dim_y) * static_cast(num_total_bin_) * 2; + AllocateCUDAMemory(&cuda_hist_buffer_, buffer_size, __FILE__, __LINE__); } - hist_buffer_for_num_bit_change_.Resize(num_total_bin_ * 2); } void CUDAHistogramConstructor::ConstructHistogramForLeaf( const CUDALeafSplitsStruct* cuda_smaller_leaf_splits, - const CUDALeafSplitsStruct* /*cuda_larger_leaf_splits*/, + const CUDALeafSplitsStruct* cuda_larger_leaf_splits, const data_size_t num_data_in_smaller_leaf, const data_size_t num_data_in_larger_leaf, const double sum_hessians_in_smaller_leaf, - const double sum_hessians_in_larger_leaf, - const uint8_t num_bits_in_histogram_bins) { + const double sum_hessians_in_larger_leaf) { if ((num_data_in_smaller_leaf <= min_data_in_leaf_ || sum_hessians_in_smaller_leaf <= min_sum_hessian_in_leaf_) && (num_data_in_larger_leaf <= min_data_in_leaf_ || sum_hessians_in_larger_leaf <= min_sum_hessian_in_leaf_)) { return; } - LaunchConstructHistogramKernel(cuda_smaller_leaf_splits, num_data_in_smaller_leaf, num_bits_in_histogram_bins); + LaunchConstructHistogramKernel(cuda_smaller_leaf_splits, num_data_in_smaller_leaf); SynchronizeCUDADevice(__FILE__, __LINE__); -} - -void CUDAHistogramConstructor::SubtractHistogramForLeaf( - const CUDALeafSplitsStruct* cuda_smaller_leaf_splits, - const CUDALeafSplitsStruct* cuda_larger_leaf_splits, - const bool use_quantized_grad, - const uint8_t parent_num_bits_in_histogram_bins, - const uint8_t smaller_num_bits_in_histogram_bins, - const uint8_t larger_num_bits_in_histogram_bins) { global_timer.Start("CUDAHistogramConstructor::ConstructHistogramForLeaf::LaunchSubtractHistogramKernel"); - LaunchSubtractHistogramKernel(cuda_smaller_leaf_splits, cuda_larger_leaf_splits, use_quantized_grad, - parent_num_bits_in_histogram_bins, smaller_num_bits_in_histogram_bins, larger_num_bits_in_histogram_bins); + LaunchSubtractHistogramKernel(cuda_smaller_leaf_splits, cuda_larger_leaf_splits); global_timer.Stop("CUDAHistogramConstructor::ConstructHistogramForLeaf::LaunchSubtractHistogramKernel"); } @@ -160,18 +152,33 @@ void CUDAHistogramConstructor::ResetTrainingData(const Dataset* train_data, Trai num_data_ = train_data->num_data(); num_features_ = train_data->num_features(); InitFeatureMetaInfo(train_data, share_states->feature_hist_offsets()); + if (feature_num_bins_.size() > 0) { + DeallocateCUDAMemory(&cuda_feature_num_bins_, __FILE__, __LINE__); + DeallocateCUDAMemory(&cuda_feature_hist_offsets_, __FILE__, __LINE__); + DeallocateCUDAMemory(&cuda_feature_most_freq_bins_, __FILE__, __LINE__); + DeallocateCUDAMemory(&cuda_need_fix_histogram_features_, __FILE__, __LINE__); + DeallocateCUDAMemory(&cuda_need_fix_histogram_features_num_bin_aligned_, __FILE__, __LINE__); + DeallocateCUDAMemory(&cuda_hist_, __FILE__, __LINE__); + } + + AllocateCUDAMemory(&cuda_hist_, num_total_bin_ * 2 * num_leaves_, __FILE__, __LINE__); + SetCUDAMemory(cuda_hist_, 0, num_total_bin_ * 2 * num_leaves_, __FILE__, __LINE__); + + InitCUDAMemoryFromHostMemory(&cuda_feature_num_bins_, + feature_num_bins_.data(), feature_num_bins_.size(), __FILE__, __LINE__); + + InitCUDAMemoryFromHostMemory(&cuda_feature_hist_offsets_, + feature_hist_offsets_.data(), feature_hist_offsets_.size(), __FILE__, __LINE__); - cuda_hist_.Resize(static_cast(num_total_bin_ * 2 * num_leaves_)); - cuda_hist_.SetValue(0); - cuda_feature_num_bins_.InitFromHostVector(feature_num_bins_); - cuda_feature_hist_offsets_.InitFromHostVector(feature_hist_offsets_); - cuda_feature_most_freq_bins_.InitFromHostVector(feature_most_freq_bins_); + InitCUDAMemoryFromHostMemory(&cuda_feature_most_freq_bins_, + feature_most_freq_bins_.data(), feature_most_freq_bins_.size(), __FILE__, __LINE__); cuda_row_data_.reset(new CUDARowData(train_data, share_states, gpu_device_id_, gpu_use_dp_)); cuda_row_data_->Init(train_data, share_states); - cuda_need_fix_histogram_features_.InitFromHostVector(need_fix_histogram_features_); - cuda_need_fix_histogram_features_num_bin_aligned_.InitFromHostVector(need_fix_histogram_features_num_bin_aligend_); + InitCUDAMemoryFromHostMemory(&cuda_need_fix_histogram_features_, need_fix_histogram_features_.data(), need_fix_histogram_features_.size(), __FILE__, __LINE__); + InitCUDAMemoryFromHostMemory(&cuda_need_fix_histogram_features_num_bin_aligned_, need_fix_histogram_features_num_bin_aligend_.data(), + need_fix_histogram_features_num_bin_aligend_.size(), __FILE__, __LINE__); } void CUDAHistogramConstructor::ResetConfig(const Config* config) { @@ -179,8 +186,9 @@ void CUDAHistogramConstructor::ResetConfig(const Config* config) { num_leaves_ = config->num_leaves; min_data_in_leaf_ = config->min_data_in_leaf; min_sum_hessian_in_leaf_ = config->min_sum_hessian_in_leaf; - cuda_hist_.Resize(static_cast(num_total_bin_ * 2 * num_leaves_)); - cuda_hist_.SetValue(0); + DeallocateCUDAMemory(&cuda_hist_, __FILE__, __LINE__); + AllocateCUDAMemory(&cuda_hist_, num_total_bin_ * 2 * num_leaves_, __FILE__, __LINE__); + SetCUDAMemory(cuda_hist_, 0, num_total_bin_ * 2 * num_leaves_, __FILE__, __LINE__); } } // namespace LightGBM diff --git a/src/treelearner/cuda/cuda_histogram_constructor.cu b/src/treelearner/cuda/cuda_histogram_constructor.cu index 03d3b8979439..c884383304a4 100644 --- a/src/treelearner/cuda/cuda_histogram_constructor.cu +++ b/src/treelearner/cuda/cuda_histogram_constructor.cu @@ -125,7 +125,7 @@ __global__ void CUDAConstructHistogramSparseKernel( } } -template +template __global__ void CUDAConstructHistogramDenseKernel_GlobalMemory( const CUDALeafSplitsStruct* smaller_leaf_splits, const score_t* cuda_gradients, @@ -135,7 +135,7 @@ __global__ void CUDAConstructHistogramDenseKernel_GlobalMemory( const uint32_t* column_hist_offsets_full, const int* feature_partition_column_index_offsets, const data_size_t num_data, - HIST_TYPE* global_hist_buffer) { + float* global_hist_buffer) { const int dim_y = static_cast(gridDim.y * blockDim.y); const data_size_t num_data_in_smaller_leaf = smaller_leaf_splits->num_data_in_leaf; const data_size_t num_data_per_thread = (num_data_in_smaller_leaf + dim_y - 1) / dim_y; @@ -150,7 +150,7 @@ __global__ void CUDAConstructHistogramDenseKernel_GlobalMemory( const uint32_t num_items_in_partition = (partition_hist_end - partition_hist_start) << 1; const unsigned int thread_idx = threadIdx.x + threadIdx.y * blockDim.x; const int num_total_bin = column_hist_offsets_full[gridDim.x]; - HIST_TYPE* shared_hist = global_hist_buffer + (blockIdx.y * num_total_bin + partition_hist_start) * 2; + float* shared_hist = global_hist_buffer + (blockIdx.y * num_total_bin + partition_hist_start) * 2; for (unsigned int i = thread_idx; i < num_items_in_partition; i += num_threads_per_block) { shared_hist[i] = 0.0f; } @@ -166,14 +166,14 @@ __global__ void CUDAConstructHistogramDenseKernel_GlobalMemory( data_size_t inner_data_index = static_cast(threadIdx_y); const int column_index = static_cast(threadIdx.x) + partition_column_start; if (threadIdx.x < static_cast(num_columns_in_partition)) { - HIST_TYPE* shared_hist_ptr = shared_hist + (column_hist_offsets[column_index] << 1); + float* shared_hist_ptr = shared_hist + (column_hist_offsets[column_index] << 1); for (data_size_t i = 0; i < num_iteration_this; ++i) { const data_size_t data_index = data_indices_ref_this_block[inner_data_index]; const score_t grad = cuda_gradients[data_index]; const score_t hess = cuda_hessians[data_index]; const uint32_t bin = static_cast(data_ptr[static_cast(data_index) * num_columns_in_partition + threadIdx.x]); const uint32_t pos = bin << 1; - HIST_TYPE* pos_ptr = shared_hist_ptr + pos; + float* pos_ptr = shared_hist_ptr + pos; atomicAdd_block(pos_ptr, grad); atomicAdd_block(pos_ptr + 1, hess); inner_data_index += blockDim.y; @@ -186,7 +186,7 @@ __global__ void CUDAConstructHistogramDenseKernel_GlobalMemory( } } -template +template __global__ void CUDAConstructHistogramSparseKernel_GlobalMemory( const CUDALeafSplitsStruct* smaller_leaf_splits, const score_t* cuda_gradients, @@ -196,7 +196,7 @@ __global__ void CUDAConstructHistogramSparseKernel_GlobalMemory( const DATA_PTR_TYPE* partition_ptr, const uint32_t* column_hist_offsets_full, const data_size_t num_data, - HIST_TYPE* global_hist_buffer) { + float* global_hist_buffer) { const int dim_y = static_cast(gridDim.y * blockDim.y); const data_size_t num_data_in_smaller_leaf = smaller_leaf_splits->num_data_in_leaf; const data_size_t num_data_per_thread = (num_data_in_smaller_leaf + dim_y - 1) / dim_y; @@ -209,7 +209,7 @@ __global__ void CUDAConstructHistogramSparseKernel_GlobalMemory( const uint32_t num_items_in_partition = (partition_hist_end - partition_hist_start) << 1; const unsigned int thread_idx = threadIdx.x + threadIdx.y * blockDim.x; const int num_total_bin = column_hist_offsets_full[gridDim.x]; - HIST_TYPE* shared_hist = global_hist_buffer + (blockIdx.y * num_total_bin + partition_hist_start) * 2; + float* shared_hist = global_hist_buffer + (blockIdx.y * num_total_bin + partition_hist_start) * 2; for (unsigned int i = thread_idx; i < num_items_in_partition; i += num_threads_per_block) { shared_hist[i] = 0.0f; } @@ -233,7 +233,7 @@ __global__ void CUDAConstructHistogramSparseKernel_GlobalMemory( const score_t hess = cuda_hessians[data_index]; const uint32_t bin = static_cast(data_ptr[row_start + threadIdx.x]); const uint32_t pos = bin << 1; - HIST_TYPE* pos_ptr = shared_hist + pos; + float* pos_ptr = shared_hist + pos; atomicAdd_block(pos_ptr, grad); atomicAdd_block(pos_ptr + 1, hess); } @@ -246,278 +246,13 @@ __global__ void CUDAConstructHistogramSparseKernel_GlobalMemory( } } -template -__global__ void CUDAConstructDiscretizedHistogramDenseKernel( - const CUDALeafSplitsStruct* smaller_leaf_splits, - const int32_t* cuda_gradients_and_hessians, - const BIN_TYPE* data, - const uint32_t* column_hist_offsets, - const uint32_t* column_hist_offsets_full, - const int* feature_partition_column_index_offsets, - const data_size_t num_data) { - const int dim_y = static_cast(gridDim.y * blockDim.y); - const data_size_t num_data_in_smaller_leaf = smaller_leaf_splits->num_data_in_leaf; - const data_size_t num_data_per_thread = (num_data_in_smaller_leaf + dim_y - 1) / dim_y; - const data_size_t* data_indices_ref = smaller_leaf_splits->data_indices_in_leaf; - __shared__ int16_t shared_hist[SHARED_HIST_SIZE]; - int32_t* shared_hist_packed = reinterpret_cast(shared_hist); - const unsigned int num_threads_per_block = blockDim.x * blockDim.y; - const int partition_column_start = feature_partition_column_index_offsets[blockIdx.x]; - const int partition_column_end = feature_partition_column_index_offsets[blockIdx.x + 1]; - const BIN_TYPE* data_ptr = data + partition_column_start * num_data; - const int num_columns_in_partition = partition_column_end - partition_column_start; - const uint32_t partition_hist_start = column_hist_offsets_full[blockIdx.x]; - const uint32_t partition_hist_end = column_hist_offsets_full[blockIdx.x + 1]; - const uint32_t num_items_in_partition = (partition_hist_end - partition_hist_start); - const unsigned int thread_idx = threadIdx.x + threadIdx.y * blockDim.x; - for (unsigned int i = thread_idx; i < num_items_in_partition; i += num_threads_per_block) { - shared_hist_packed[i] = 0; - } - __syncthreads(); - const unsigned int threadIdx_y = threadIdx.y; - const unsigned int blockIdx_y = blockIdx.y; - const data_size_t block_start = (blockIdx_y * blockDim.y) * num_data_per_thread; - const data_size_t* data_indices_ref_this_block = data_indices_ref + block_start; - data_size_t block_num_data = max(0, min(num_data_in_smaller_leaf - block_start, num_data_per_thread * static_cast(blockDim.y))); - const data_size_t num_iteration_total = (block_num_data + blockDim.y - 1) / blockDim.y; - const data_size_t remainder = block_num_data % blockDim.y; - const data_size_t num_iteration_this = remainder == 0 ? num_iteration_total : num_iteration_total - static_cast(threadIdx_y >= remainder); - data_size_t inner_data_index = static_cast(threadIdx_y); - const int column_index = static_cast(threadIdx.x) + partition_column_start; - if (threadIdx.x < static_cast(num_columns_in_partition)) { - int32_t* shared_hist_ptr = shared_hist_packed + (column_hist_offsets[column_index]); - for (data_size_t i = 0; i < num_iteration_this; ++i) { - const data_size_t data_index = data_indices_ref_this_block[inner_data_index]; - const int32_t grad_and_hess = cuda_gradients_and_hessians[data_index]; - const uint32_t bin = static_cast(data_ptr[data_index * num_columns_in_partition + threadIdx.x]); - int32_t* pos_ptr = shared_hist_ptr + bin; - atomicAdd_block(pos_ptr, grad_and_hess); - inner_data_index += blockDim.y; - } - } - __syncthreads(); - if (USE_16BIT_HIST) { - int32_t* feature_histogram_ptr = reinterpret_cast(smaller_leaf_splits->hist_in_leaf) + partition_hist_start; - for (unsigned int i = thread_idx; i < num_items_in_partition; i += num_threads_per_block) { - const int32_t packed_grad_hess = shared_hist_packed[i]; - atomicAdd_system(feature_histogram_ptr + i, packed_grad_hess); - } - } else { - atomic_add_long_t* feature_histogram_ptr = reinterpret_cast(smaller_leaf_splits->hist_in_leaf) + partition_hist_start; - for (unsigned int i = thread_idx; i < num_items_in_partition; i += num_threads_per_block) { - const int32_t packed_grad_hess = shared_hist_packed[i]; - const int64_t packed_grad_hess_int64 = (static_cast(static_cast(packed_grad_hess >> 16)) << 32) | (static_cast(packed_grad_hess & 0x0000ffff)); - atomicAdd_system(feature_histogram_ptr + i, (atomic_add_long_t)(packed_grad_hess_int64)); - } - } -} - -template -__global__ void CUDAConstructDiscretizedHistogramSparseKernel( - const CUDALeafSplitsStruct* smaller_leaf_splits, - const int32_t* cuda_gradients_and_hessians, - const BIN_TYPE* data, - const DATA_PTR_TYPE* row_ptr, - const DATA_PTR_TYPE* partition_ptr, - const uint32_t* column_hist_offsets_full, - const data_size_t num_data) { - const int dim_y = static_cast(gridDim.y * blockDim.y); - const data_size_t num_data_in_smaller_leaf = smaller_leaf_splits->num_data_in_leaf; - const data_size_t num_data_per_thread = (num_data_in_smaller_leaf + dim_y - 1) / dim_y; - const data_size_t* data_indices_ref = smaller_leaf_splits->data_indices_in_leaf; - __shared__ int16_t shared_hist[SHARED_HIST_SIZE]; - int32_t* shared_hist_packed = reinterpret_cast(shared_hist); - const unsigned int num_threads_per_block = blockDim.x * blockDim.y; - const DATA_PTR_TYPE* block_row_ptr = row_ptr + blockIdx.x * (num_data + 1); - const BIN_TYPE* data_ptr = data + partition_ptr[blockIdx.x]; - const uint32_t partition_hist_start = column_hist_offsets_full[blockIdx.x]; - const uint32_t partition_hist_end = column_hist_offsets_full[blockIdx.x + 1]; - const uint32_t num_items_in_partition = (partition_hist_end - partition_hist_start); - const unsigned int thread_idx = threadIdx.x + threadIdx.y * blockDim.x; - for (unsigned int i = thread_idx; i < num_items_in_partition; i += num_threads_per_block) { - shared_hist_packed[i] = 0.0f; - } - __syncthreads(); - const unsigned int threadIdx_y = threadIdx.y; - const unsigned int blockIdx_y = blockIdx.y; - const data_size_t block_start = (blockIdx_y * blockDim.y) * num_data_per_thread; - const data_size_t* data_indices_ref_this_block = data_indices_ref + block_start; - data_size_t block_num_data = max(0, min(num_data_in_smaller_leaf - block_start, num_data_per_thread * static_cast(blockDim.y))); - const data_size_t num_iteration_total = (block_num_data + blockDim.y - 1) / blockDim.y; - const data_size_t remainder = block_num_data % blockDim.y; - const data_size_t num_iteration_this = remainder == 0 ? num_iteration_total : num_iteration_total - static_cast(threadIdx_y >= remainder); - data_size_t inner_data_index = static_cast(threadIdx_y); - for (data_size_t i = 0; i < num_iteration_this; ++i) { - const data_size_t data_index = data_indices_ref_this_block[inner_data_index]; - const DATA_PTR_TYPE row_start = block_row_ptr[data_index]; - const DATA_PTR_TYPE row_end = block_row_ptr[data_index + 1]; - const DATA_PTR_TYPE row_size = row_end - row_start; - if (threadIdx.x < row_size) { - const int32_t grad_and_hess = cuda_gradients_and_hessians[data_index]; - const uint32_t bin = static_cast(data_ptr[row_start + threadIdx.x]); - int32_t* pos_ptr = shared_hist_packed + bin; - atomicAdd_block(pos_ptr, grad_and_hess); - } - inner_data_index += blockDim.y; - } - __syncthreads(); - if (USE_16BIT_HIST) { - int32_t* feature_histogram_ptr = reinterpret_cast(smaller_leaf_splits->hist_in_leaf) + partition_hist_start; - for (unsigned int i = thread_idx; i < num_items_in_partition; i += num_threads_per_block) { - const int32_t packed_grad_hess = shared_hist_packed[i]; - atomicAdd_system(feature_histogram_ptr + i, packed_grad_hess); - } - } else { - atomic_add_long_t* feature_histogram_ptr = reinterpret_cast(smaller_leaf_splits->hist_in_leaf) + partition_hist_start; - for (unsigned int i = thread_idx; i < num_items_in_partition; i += num_threads_per_block) { - const int32_t packed_grad_hess = shared_hist_packed[i]; - const int64_t packed_grad_hess_int64 = (static_cast(static_cast(packed_grad_hess >> 16)) << 32) | (static_cast(packed_grad_hess & 0x0000ffff)); - atomicAdd_system(feature_histogram_ptr + i, (atomic_add_long_t)(packed_grad_hess_int64)); - } - } -} - -template -__global__ void CUDAConstructDiscretizedHistogramDenseKernel_GlobalMemory( - const CUDALeafSplitsStruct* smaller_leaf_splits, - const int32_t* cuda_gradients_and_hessians, - const BIN_TYPE* data, - const uint32_t* column_hist_offsets, - const uint32_t* column_hist_offsets_full, - const int* feature_partition_column_index_offsets, - const data_size_t num_data, - int32_t* global_hist_buffer) { - const int dim_y = static_cast(gridDim.y * blockDim.y); - const data_size_t num_data_in_smaller_leaf = smaller_leaf_splits->num_data_in_leaf; - const data_size_t num_data_per_thread = (num_data_in_smaller_leaf + dim_y - 1) / dim_y; - const data_size_t* data_indices_ref = smaller_leaf_splits->data_indices_in_leaf; - const unsigned int num_threads_per_block = blockDim.x * blockDim.y; - const int partition_column_start = feature_partition_column_index_offsets[blockIdx.x]; - const int partition_column_end = feature_partition_column_index_offsets[blockIdx.x + 1]; - const BIN_TYPE* data_ptr = data + partition_column_start * num_data; - const int num_columns_in_partition = partition_column_end - partition_column_start; - const uint32_t partition_hist_start = column_hist_offsets_full[blockIdx.x]; - const uint32_t partition_hist_end = column_hist_offsets_full[blockIdx.x + 1]; - const uint32_t num_items_in_partition = (partition_hist_end - partition_hist_start); - const int num_total_bin = column_hist_offsets_full[gridDim.x]; - int32_t* shared_hist_packed = global_hist_buffer + (blockIdx.y * num_total_bin + partition_column_start); - const unsigned int thread_idx = threadIdx.x + threadIdx.y * blockDim.x; - for (unsigned int i = thread_idx; i < num_items_in_partition; i += num_threads_per_block) { - shared_hist_packed[i] = 0; - } - __syncthreads(); - const unsigned int threadIdx_y = threadIdx.y; - const unsigned int blockIdx_y = blockIdx.y; - const data_size_t block_start = (blockIdx_y * blockDim.y) * num_data_per_thread; - const data_size_t* data_indices_ref_this_block = data_indices_ref + block_start; - data_size_t block_num_data = max(0, min(num_data_in_smaller_leaf - block_start, num_data_per_thread * static_cast(blockDim.y))); - const data_size_t num_iteration_total = (block_num_data + blockDim.y - 1) / blockDim.y; - const data_size_t remainder = block_num_data % blockDim.y; - const data_size_t num_iteration_this = remainder == 0 ? num_iteration_total : num_iteration_total - static_cast(threadIdx_y >= remainder); - data_size_t inner_data_index = static_cast(threadIdx_y); - const int column_index = static_cast(threadIdx.x) + partition_column_start; - if (threadIdx.x < static_cast(num_columns_in_partition)) { - int32_t* shared_hist_ptr = shared_hist_packed + (column_hist_offsets[column_index]); - for (data_size_t i = 0; i < num_iteration_this; ++i) { - const data_size_t data_index = data_indices_ref_this_block[inner_data_index]; - const int32_t grad_and_hess = cuda_gradients_and_hessians[data_index]; - const uint32_t bin = static_cast(data_ptr[data_index * num_columns_in_partition + threadIdx.x]); - int32_t* pos_ptr = shared_hist_ptr + bin; - atomicAdd_block(pos_ptr, grad_and_hess); - inner_data_index += blockDim.y; - } - } - __syncthreads(); - if (USE_16BIT_HIST) { - int32_t* feature_histogram_ptr = reinterpret_cast(smaller_leaf_splits->hist_in_leaf) + partition_hist_start; - for (unsigned int i = thread_idx; i < num_items_in_partition; i += num_threads_per_block) { - const int32_t packed_grad_hess = shared_hist_packed[i]; - atomicAdd_system(feature_histogram_ptr + i, packed_grad_hess); - } - } else { - atomic_add_long_t* feature_histogram_ptr = reinterpret_cast(smaller_leaf_splits->hist_in_leaf) + partition_hist_start; - for (unsigned int i = thread_idx; i < num_items_in_partition; i += num_threads_per_block) { - const int32_t packed_grad_hess = shared_hist_packed[i]; - const int64_t packed_grad_hess_int64 = (static_cast(static_cast(packed_grad_hess >> 16)) << 32) | (static_cast(packed_grad_hess & 0x0000ffff)); - atomicAdd_system(feature_histogram_ptr + i, (atomic_add_long_t)(packed_grad_hess_int64)); - } - } -} - -template -__global__ void CUDAConstructDiscretizedHistogramSparseKernel_GlobalMemory( - const CUDALeafSplitsStruct* smaller_leaf_splits, - const int32_t* cuda_gradients_and_hessians, - const BIN_TYPE* data, - const DATA_PTR_TYPE* row_ptr, - const DATA_PTR_TYPE* partition_ptr, - const uint32_t* column_hist_offsets_full, - const data_size_t num_data, - int32_t* global_hist_buffer) { - const int dim_y = static_cast(gridDim.y * blockDim.y); - const data_size_t num_data_in_smaller_leaf = smaller_leaf_splits->num_data_in_leaf; - const data_size_t num_data_per_thread = (num_data_in_smaller_leaf + dim_y - 1) / dim_y; - const data_size_t* data_indices_ref = smaller_leaf_splits->data_indices_in_leaf; - const int num_total_bin = column_hist_offsets_full[gridDim.x]; - const unsigned int num_threads_per_block = blockDim.x * blockDim.y; - const DATA_PTR_TYPE* block_row_ptr = row_ptr + blockIdx.x * (num_data + 1); - const BIN_TYPE* data_ptr = data + partition_ptr[blockIdx.x]; - const uint32_t partition_hist_start = column_hist_offsets_full[blockIdx.x]; - const uint32_t partition_hist_end = column_hist_offsets_full[blockIdx.x + 1]; - const uint32_t num_items_in_partition = (partition_hist_end - partition_hist_start); - const unsigned int thread_idx = threadIdx.x + threadIdx.y * blockDim.x; - int32_t* shared_hist_packed = global_hist_buffer + (blockIdx.y * num_total_bin + partition_hist_start); - for (unsigned int i = thread_idx; i < num_items_in_partition; i += num_threads_per_block) { - shared_hist_packed[i] = 0.0f; - } - __syncthreads(); - const unsigned int threadIdx_y = threadIdx.y; - const unsigned int blockIdx_y = blockIdx.y; - const data_size_t block_start = (blockIdx_y * blockDim.y) * num_data_per_thread; - const data_size_t* data_indices_ref_this_block = data_indices_ref + block_start; - data_size_t block_num_data = max(0, min(num_data_in_smaller_leaf - block_start, num_data_per_thread * static_cast(blockDim.y))); - const data_size_t num_iteration_total = (block_num_data + blockDim.y - 1) / blockDim.y; - const data_size_t remainder = block_num_data % blockDim.y; - const data_size_t num_iteration_this = remainder == 0 ? num_iteration_total : num_iteration_total - static_cast(threadIdx_y >= remainder); - data_size_t inner_data_index = static_cast(threadIdx_y); - for (data_size_t i = 0; i < num_iteration_this; ++i) { - const data_size_t data_index = data_indices_ref_this_block[inner_data_index]; - const DATA_PTR_TYPE row_start = block_row_ptr[data_index]; - const DATA_PTR_TYPE row_end = block_row_ptr[data_index + 1]; - const DATA_PTR_TYPE row_size = row_end - row_start; - if (threadIdx.x < row_size) { - const int32_t grad_and_hess = cuda_gradients_and_hessians[data_index]; - const uint32_t bin = static_cast(data_ptr[row_start + threadIdx.x]); - int32_t* pos_ptr = shared_hist_packed + bin; - atomicAdd_block(pos_ptr, grad_and_hess); - } - inner_data_index += blockDim.y; - } - __syncthreads(); - if (USE_16BIT_HIST) { - int32_t* feature_histogram_ptr = reinterpret_cast(smaller_leaf_splits->hist_in_leaf) + partition_hist_start; - for (unsigned int i = thread_idx; i < num_items_in_partition; i += num_threads_per_block) { - const int32_t packed_grad_hess = shared_hist_packed[i]; - atomicAdd_system(feature_histogram_ptr + i, packed_grad_hess); - } - } else { - atomic_add_long_t* feature_histogram_ptr = reinterpret_cast(smaller_leaf_splits->hist_in_leaf) + partition_hist_start; - for (unsigned int i = thread_idx; i < num_items_in_partition; i += num_threads_per_block) { - const int32_t packed_grad_hess = shared_hist_packed[i]; - const int64_t packed_grad_hess_int64 = (static_cast(static_cast(packed_grad_hess >> 16)) << 32) | (static_cast(packed_grad_hess & 0x0000ffff)); - atomicAdd_system(feature_histogram_ptr + i, (atomic_add_long_t)(packed_grad_hess_int64)); - } - } -} - void CUDAHistogramConstructor::LaunchConstructHistogramKernel( const CUDALeafSplitsStruct* cuda_smaller_leaf_splits, - const data_size_t num_data_in_smaller_leaf, - const uint8_t num_bits_in_histogram_bins) { + const data_size_t num_data_in_smaller_leaf) { if (cuda_row_data_->shared_hist_size() == DP_SHARED_HIST_SIZE && gpu_use_dp_) { - LaunchConstructHistogramKernelInner(cuda_smaller_leaf_splits, num_data_in_smaller_leaf, num_bits_in_histogram_bins); + LaunchConstructHistogramKernelInner(cuda_smaller_leaf_splits, num_data_in_smaller_leaf); } else if (cuda_row_data_->shared_hist_size() == SP_SHARED_HIST_SIZE && !gpu_use_dp_) { - LaunchConstructHistogramKernelInner(cuda_smaller_leaf_splits, num_data_in_smaller_leaf, num_bits_in_histogram_bins); + LaunchConstructHistogramKernelInner(cuda_smaller_leaf_splits, num_data_in_smaller_leaf); } else { Log::Fatal("Unknown shared histogram size %d", cuda_row_data_->shared_hist_size()); } @@ -526,14 +261,13 @@ void CUDAHistogramConstructor::LaunchConstructHistogramKernel( template void CUDAHistogramConstructor::LaunchConstructHistogramKernelInner( const CUDALeafSplitsStruct* cuda_smaller_leaf_splits, - const data_size_t num_data_in_smaller_leaf, - const uint8_t num_bits_in_histogram_bins) { + const data_size_t num_data_in_smaller_leaf) { if (cuda_row_data_->bit_type() == 8) { - LaunchConstructHistogramKernelInner0(cuda_smaller_leaf_splits, num_data_in_smaller_leaf, num_bits_in_histogram_bins); + LaunchConstructHistogramKernelInner0(cuda_smaller_leaf_splits, num_data_in_smaller_leaf); } else if (cuda_row_data_->bit_type() == 16) { - LaunchConstructHistogramKernelInner0(cuda_smaller_leaf_splits, num_data_in_smaller_leaf, num_bits_in_histogram_bins); + LaunchConstructHistogramKernelInner0(cuda_smaller_leaf_splits, num_data_in_smaller_leaf); } else if (cuda_row_data_->bit_type() == 32) { - LaunchConstructHistogramKernelInner0(cuda_smaller_leaf_splits, num_data_in_smaller_leaf, num_bits_in_histogram_bins); + LaunchConstructHistogramKernelInner0(cuda_smaller_leaf_splits, num_data_in_smaller_leaf); } else { Log::Fatal("Unknown bit_type = %d", cuda_row_data_->bit_type()); } @@ -542,17 +276,16 @@ void CUDAHistogramConstructor::LaunchConstructHistogramKernelInner( template void CUDAHistogramConstructor::LaunchConstructHistogramKernelInner0( const CUDALeafSplitsStruct* cuda_smaller_leaf_splits, - const data_size_t num_data_in_smaller_leaf, - const uint8_t num_bits_in_histogram_bins) { + const data_size_t num_data_in_smaller_leaf) { if (cuda_row_data_->row_ptr_bit_type() == 16) { - LaunchConstructHistogramKernelInner1(cuda_smaller_leaf_splits, num_data_in_smaller_leaf, num_bits_in_histogram_bins); + LaunchConstructHistogramKernelInner1(cuda_smaller_leaf_splits, num_data_in_smaller_leaf); } else if (cuda_row_data_->row_ptr_bit_type() == 32) { - LaunchConstructHistogramKernelInner1(cuda_smaller_leaf_splits, num_data_in_smaller_leaf, num_bits_in_histogram_bins); + LaunchConstructHistogramKernelInner1(cuda_smaller_leaf_splits, num_data_in_smaller_leaf); } else if (cuda_row_data_->row_ptr_bit_type() == 64) { - LaunchConstructHistogramKernelInner1(cuda_smaller_leaf_splits, num_data_in_smaller_leaf, num_bits_in_histogram_bins); + LaunchConstructHistogramKernelInner1(cuda_smaller_leaf_splits, num_data_in_smaller_leaf); } else { if (!cuda_row_data_->is_sparse()) { - LaunchConstructHistogramKernelInner1(cuda_smaller_leaf_splits, num_data_in_smaller_leaf, num_bits_in_histogram_bins); + LaunchConstructHistogramKernelInner1(cuda_smaller_leaf_splits, num_data_in_smaller_leaf); } else { Log::Fatal("Unknown row_ptr_bit_type = %d", cuda_row_data_->row_ptr_bit_type()); } @@ -562,20 +295,18 @@ void CUDAHistogramConstructor::LaunchConstructHistogramKernelInner0( template void CUDAHistogramConstructor::LaunchConstructHistogramKernelInner1( const CUDALeafSplitsStruct* cuda_smaller_leaf_splits, - const data_size_t num_data_in_smaller_leaf, - const uint8_t num_bits_in_histogram_bins) { + const data_size_t num_data_in_smaller_leaf) { if (cuda_row_data_->NumLargeBinPartition() == 0) { - LaunchConstructHistogramKernelInner2(cuda_smaller_leaf_splits, num_data_in_smaller_leaf, num_bits_in_histogram_bins); + LaunchConstructHistogramKernelInner2(cuda_smaller_leaf_splits, num_data_in_smaller_leaf); } else { - LaunchConstructHistogramKernelInner2(cuda_smaller_leaf_splits, num_data_in_smaller_leaf, num_bits_in_histogram_bins); + LaunchConstructHistogramKernelInner2(cuda_smaller_leaf_splits, num_data_in_smaller_leaf); } } template void CUDAHistogramConstructor::LaunchConstructHistogramKernelInner2( const CUDALeafSplitsStruct* cuda_smaller_leaf_splits, - const data_size_t num_data_in_smaller_leaf, - const uint8_t num_bits_in_histogram_bins) { + const data_size_t num_data_in_smaller_leaf) { int grid_dim_x = 0; int grid_dim_y = 0; int block_dim_x = 0; @@ -583,139 +314,47 @@ void CUDAHistogramConstructor::LaunchConstructHistogramKernelInner2( CalcConstructHistogramKernelDim(&grid_dim_x, &grid_dim_y, &block_dim_x, &block_dim_y, num_data_in_smaller_leaf); dim3 grid_dim(grid_dim_x, grid_dim_y); dim3 block_dim(block_dim_x, block_dim_y); - if (use_quantized_grad_) { - if (USE_GLOBAL_MEM_BUFFER) { - if (cuda_row_data_->is_sparse()) { - if (num_bits_in_histogram_bins <= 16) { - CUDAConstructDiscretizedHistogramSparseKernel_GlobalMemory<<>>( - cuda_smaller_leaf_splits, - reinterpret_cast(cuda_gradients_), - cuda_row_data_->GetBin(), - cuda_row_data_->GetRowPtr(), - cuda_row_data_->GetPartitionPtr(), - cuda_row_data_->cuda_partition_hist_offsets(), - num_data_, - reinterpret_cast(cuda_hist_buffer_.RawData())); - } else { - CUDAConstructDiscretizedHistogramSparseKernel_GlobalMemory<<>>( - cuda_smaller_leaf_splits, - reinterpret_cast(cuda_gradients_), - cuda_row_data_->GetBin(), - cuda_row_data_->GetRowPtr(), - cuda_row_data_->GetPartitionPtr(), - cuda_row_data_->cuda_partition_hist_offsets(), - num_data_, - reinterpret_cast(cuda_hist_buffer_.RawData())); - } - } else { - if (num_bits_in_histogram_bins <= 16) { - CUDAConstructDiscretizedHistogramDenseKernel_GlobalMemory<<>>( - cuda_smaller_leaf_splits, - reinterpret_cast(cuda_gradients_), - cuda_row_data_->GetBin(), - cuda_row_data_->cuda_column_hist_offsets(), - cuda_row_data_->cuda_partition_hist_offsets(), - cuda_row_data_->cuda_feature_partition_column_index_offsets(), - num_data_, - reinterpret_cast(cuda_hist_buffer_.RawData())); - } else { - CUDAConstructDiscretizedHistogramDenseKernel_GlobalMemory<<>>( - cuda_smaller_leaf_splits, - reinterpret_cast(cuda_gradients_), - cuda_row_data_->GetBin(), - cuda_row_data_->cuda_column_hist_offsets(), - cuda_row_data_->cuda_partition_hist_offsets(), - cuda_row_data_->cuda_feature_partition_column_index_offsets(), - num_data_, - reinterpret_cast(cuda_hist_buffer_.RawData())); - } - } + if (!USE_GLOBAL_MEM_BUFFER) { + if (cuda_row_data_->is_sparse()) { + CUDAConstructHistogramSparseKernel<<>>( + cuda_smaller_leaf_splits, + cuda_gradients_, cuda_hessians_, + cuda_row_data_->GetBin(), + cuda_row_data_->GetRowPtr(), + cuda_row_data_->GetPartitionPtr(), + cuda_row_data_->cuda_partition_hist_offsets(), + num_data_); } else { - if (cuda_row_data_->is_sparse()) { - if (num_bits_in_histogram_bins <= 16) { - CUDAConstructDiscretizedHistogramSparseKernel<<>>( - cuda_smaller_leaf_splits, - reinterpret_cast(cuda_gradients_), - cuda_row_data_->GetBin(), - cuda_row_data_->GetRowPtr(), - cuda_row_data_->GetPartitionPtr(), - cuda_row_data_->cuda_partition_hist_offsets(), - num_data_); - } else { - CUDAConstructDiscretizedHistogramSparseKernel<<>>( - cuda_smaller_leaf_splits, - reinterpret_cast(cuda_gradients_), - cuda_row_data_->GetBin(), - cuda_row_data_->GetRowPtr(), - cuda_row_data_->GetPartitionPtr(), - cuda_row_data_->cuda_partition_hist_offsets(), - num_data_); - } - } else { - if (num_bits_in_histogram_bins <= 16) { - CUDAConstructDiscretizedHistogramDenseKernel<<>>( - cuda_smaller_leaf_splits, - reinterpret_cast(cuda_gradients_), - cuda_row_data_->GetBin(), - cuda_row_data_->cuda_column_hist_offsets(), - cuda_row_data_->cuda_partition_hist_offsets(), - cuda_row_data_->cuda_feature_partition_column_index_offsets(), - num_data_); - } else { - CUDAConstructDiscretizedHistogramDenseKernel<<>>( - cuda_smaller_leaf_splits, - reinterpret_cast(cuda_gradients_), - cuda_row_data_->GetBin(), - cuda_row_data_->cuda_column_hist_offsets(), - cuda_row_data_->cuda_partition_hist_offsets(), - cuda_row_data_->cuda_feature_partition_column_index_offsets(), - num_data_); - } - } + CUDAConstructHistogramDenseKernel<<>>( + cuda_smaller_leaf_splits, + cuda_gradients_, cuda_hessians_, + cuda_row_data_->GetBin(), + cuda_row_data_->cuda_column_hist_offsets(), + cuda_row_data_->cuda_partition_hist_offsets(), + cuda_row_data_->cuda_feature_partition_column_index_offsets(), + num_data_); } } else { - if (!USE_GLOBAL_MEM_BUFFER) { - if (cuda_row_data_->is_sparse()) { - CUDAConstructHistogramSparseKernel<<>>( - cuda_smaller_leaf_splits, - cuda_gradients_, cuda_hessians_, - cuda_row_data_->GetBin(), - cuda_row_data_->GetRowPtr(), - cuda_row_data_->GetPartitionPtr(), - cuda_row_data_->cuda_partition_hist_offsets(), - num_data_); - } else { - CUDAConstructHistogramDenseKernel<<>>( - cuda_smaller_leaf_splits, - cuda_gradients_, cuda_hessians_, - cuda_row_data_->GetBin(), - cuda_row_data_->cuda_column_hist_offsets(), - cuda_row_data_->cuda_partition_hist_offsets(), - cuda_row_data_->cuda_feature_partition_column_index_offsets(), - num_data_); - } + if (cuda_row_data_->is_sparse()) { + CUDAConstructHistogramSparseKernel_GlobalMemory<<>>( + cuda_smaller_leaf_splits, + cuda_gradients_, cuda_hessians_, + cuda_row_data_->GetBin(), + cuda_row_data_->GetRowPtr(), + cuda_row_data_->GetPartitionPtr(), + cuda_row_data_->cuda_partition_hist_offsets(), + num_data_, + cuda_hist_buffer_); } else { - if (cuda_row_data_->is_sparse()) { - CUDAConstructHistogramSparseKernel_GlobalMemory<<>>( - cuda_smaller_leaf_splits, - cuda_gradients_, cuda_hessians_, - cuda_row_data_->GetBin(), - cuda_row_data_->GetRowPtr(), - cuda_row_data_->GetPartitionPtr(), - cuda_row_data_->cuda_partition_hist_offsets(), - num_data_, - reinterpret_cast(cuda_hist_buffer_.RawData())); - } else { - CUDAConstructHistogramDenseKernel_GlobalMemory<<>>( - cuda_smaller_leaf_splits, - cuda_gradients_, cuda_hessians_, - cuda_row_data_->GetBin(), - cuda_row_data_->cuda_column_hist_offsets(), - cuda_row_data_->cuda_partition_hist_offsets(), - cuda_row_data_->cuda_feature_partition_column_index_offsets(), - num_data_, - reinterpret_cast(cuda_hist_buffer_.RawData())); - } + CUDAConstructHistogramDenseKernel_GlobalMemory<<>>( + cuda_smaller_leaf_splits, + cuda_gradients_, cuda_hessians_, + cuda_row_data_->GetBin(), + cuda_row_data_->cuda_column_hist_offsets(), + cuda_row_data_->cuda_partition_hist_offsets(), + cuda_row_data_->cuda_feature_partition_column_index_offsets(), + num_data_, + cuda_hist_buffer_); } } } @@ -764,195 +403,28 @@ __global__ void FixHistogramKernel( } } -template -__global__ void SubtractHistogramDiscretizedKernel( - const int num_total_bin, - const CUDALeafSplitsStruct* cuda_smaller_leaf_splits, - const CUDALeafSplitsStruct* cuda_larger_leaf_splits, - hist_t* num_bit_change_buffer) { - const unsigned int global_thread_index = threadIdx.x + blockIdx.x * blockDim.x; - const int cuda_larger_leaf_index_ref = cuda_larger_leaf_splits->leaf_index; - if (cuda_larger_leaf_index_ref >= 0) { - if (PARENT_USE_16BIT_HIST) { - const int32_t* smaller_leaf_hist = reinterpret_cast(cuda_smaller_leaf_splits->hist_in_leaf); - int32_t* larger_leaf_hist = reinterpret_cast(cuda_larger_leaf_splits->hist_in_leaf); - if (global_thread_index < num_total_bin) { - larger_leaf_hist[global_thread_index] -= smaller_leaf_hist[global_thread_index]; - } - } else if (LARGER_USE_16BIT_HIST) { - int32_t* buffer = reinterpret_cast(num_bit_change_buffer); - const int32_t* smaller_leaf_hist = reinterpret_cast(cuda_smaller_leaf_splits->hist_in_leaf); - int64_t* larger_leaf_hist = reinterpret_cast(cuda_larger_leaf_splits->hist_in_leaf); - if (global_thread_index < num_total_bin) { - const int64_t parent_hist_item = larger_leaf_hist[global_thread_index]; - const int32_t smaller_hist_item = smaller_leaf_hist[global_thread_index]; - const int64_t smaller_hist_item_int64 = (static_cast(static_cast(smaller_hist_item >> 16)) << 32) | - static_cast(smaller_hist_item & 0x0000ffff); - const int64_t larger_hist_item = parent_hist_item - smaller_hist_item_int64; - buffer[global_thread_index] = static_cast(static_cast(larger_hist_item >> 32) << 16) | - static_cast(larger_hist_item & 0x000000000000ffff); - } - } else if (SMALLER_USE_16BIT_HIST) { - const int32_t* smaller_leaf_hist = reinterpret_cast(cuda_smaller_leaf_splits->hist_in_leaf); - int64_t* larger_leaf_hist = reinterpret_cast(cuda_larger_leaf_splits->hist_in_leaf); - if (global_thread_index < num_total_bin) { - const int64_t parent_hist_item = larger_leaf_hist[global_thread_index]; - const int32_t smaller_hist_item = smaller_leaf_hist[global_thread_index]; - const int64_t smaller_hist_item_int64 = (static_cast(static_cast(smaller_hist_item >> 16)) << 32) | - static_cast(smaller_hist_item & 0x0000ffff); - const int64_t larger_hist_item = parent_hist_item - smaller_hist_item_int64; - larger_leaf_hist[global_thread_index] = larger_hist_item; - } - } else { - const int64_t* smaller_leaf_hist = reinterpret_cast(cuda_smaller_leaf_splits->hist_in_leaf); - int64_t* larger_leaf_hist = reinterpret_cast(cuda_larger_leaf_splits->hist_in_leaf); - if (global_thread_index < num_total_bin) { - larger_leaf_hist[global_thread_index] -= smaller_leaf_hist[global_thread_index]; - } - } - } -} - -__global__ void CopyChangedNumBitHistogram( - const int num_total_bin, - const CUDALeafSplitsStruct* cuda_larger_leaf_splits, - hist_t* num_bit_change_buffer) { - int32_t* hist_dst = reinterpret_cast(cuda_larger_leaf_splits->hist_in_leaf); - const int32_t* hist_src = reinterpret_cast(num_bit_change_buffer); - const unsigned int global_thread_index = threadIdx.x + blockIdx.x * blockDim.x; - if (global_thread_index < static_cast(num_total_bin)) { - hist_dst[global_thread_index] = hist_src[global_thread_index]; - } -} - -template -__global__ void FixHistogramDiscretizedKernel( - const uint32_t* cuda_feature_num_bins, - const uint32_t* cuda_feature_hist_offsets, - const uint32_t* cuda_feature_most_freq_bins, - const int* cuda_need_fix_histogram_features, - const uint32_t* cuda_need_fix_histogram_features_num_bin_aligned, - const CUDALeafSplitsStruct* cuda_smaller_leaf_splits) { - __shared__ int64_t shared_mem_buffer[32]; - const unsigned int blockIdx_x = blockIdx.x; - const int feature_index = cuda_need_fix_histogram_features[blockIdx_x]; - const uint32_t num_bin_aligned = cuda_need_fix_histogram_features_num_bin_aligned[blockIdx_x]; - const uint32_t feature_hist_offset = cuda_feature_hist_offsets[feature_index]; - const uint32_t most_freq_bin = cuda_feature_most_freq_bins[feature_index]; - if (USE_16BIT_HIST) { - const int64_t leaf_sum_gradients_hessians_int64 = cuda_smaller_leaf_splits->sum_of_gradients_hessians; - const int32_t leaf_sum_gradients_hessians = - (static_cast(leaf_sum_gradients_hessians_int64 >> 32) << 16) | static_cast(leaf_sum_gradients_hessians_int64 & 0x000000000000ffff); - int32_t* feature_hist = reinterpret_cast(cuda_smaller_leaf_splits->hist_in_leaf) + feature_hist_offset; - const unsigned int threadIdx_x = threadIdx.x; - const uint32_t num_bin = cuda_feature_num_bins[feature_index]; - const int32_t bin_gradient_hessian = (threadIdx_x < num_bin && threadIdx_x != most_freq_bin) ? feature_hist[threadIdx_x] : 0; - const int32_t sum_gradient_hessian = ShuffleReduceSum( - bin_gradient_hessian, - reinterpret_cast(shared_mem_buffer), - num_bin_aligned); - if (threadIdx_x == 0) { - feature_hist[most_freq_bin] = leaf_sum_gradients_hessians - sum_gradient_hessian; - } - } else { - const int64_t leaf_sum_gradients_hessians = cuda_smaller_leaf_splits->sum_of_gradients_hessians; - int64_t* feature_hist = reinterpret_cast(cuda_smaller_leaf_splits->hist_in_leaf) + feature_hist_offset; - const unsigned int threadIdx_x = threadIdx.x; - const uint32_t num_bin = cuda_feature_num_bins[feature_index]; - const int64_t bin_gradient_hessian = (threadIdx_x < num_bin && threadIdx_x != most_freq_bin) ? feature_hist[threadIdx_x] : 0; - const int64_t sum_gradient_hessian = ShuffleReduceSum(bin_gradient_hessian, shared_mem_buffer, num_bin_aligned); - if (threadIdx_x == 0) { - feature_hist[most_freq_bin] = leaf_sum_gradients_hessians - sum_gradient_hessian; - } - } -} - void CUDAHistogramConstructor::LaunchSubtractHistogramKernel( const CUDALeafSplitsStruct* cuda_smaller_leaf_splits, - const CUDALeafSplitsStruct* cuda_larger_leaf_splits, - const bool use_discretized_grad, - const uint8_t parent_num_bits_in_histogram_bins, - const uint8_t smaller_num_bits_in_histogram_bins, - const uint8_t larger_num_bits_in_histogram_bins) { - if (!use_discretized_grad) { - const int num_subtract_threads = 2 * num_total_bin_; - const int num_subtract_blocks = (num_subtract_threads + SUBTRACT_BLOCK_SIZE - 1) / SUBTRACT_BLOCK_SIZE; - global_timer.Start("CUDAHistogramConstructor::FixHistogramKernel"); - if (need_fix_histogram_features_.size() > 0) { - FixHistogramKernel<<>>( - cuda_feature_num_bins_.RawData(), - cuda_feature_hist_offsets_.RawData(), - cuda_feature_most_freq_bins_.RawData(), - cuda_need_fix_histogram_features_.RawData(), - cuda_need_fix_histogram_features_num_bin_aligned_.RawData(), - cuda_smaller_leaf_splits); - } - global_timer.Stop("CUDAHistogramConstructor::FixHistogramKernel"); - global_timer.Start("CUDAHistogramConstructor::SubtractHistogramKernel"); - SubtractHistogramKernel<<>>( - num_total_bin_, - cuda_smaller_leaf_splits, - cuda_larger_leaf_splits); - global_timer.Stop("CUDAHistogramConstructor::SubtractHistogramKernel"); - } else { - const int num_subtract_threads = num_total_bin_; - const int num_subtract_blocks = (num_subtract_threads + SUBTRACT_BLOCK_SIZE - 1) / SUBTRACT_BLOCK_SIZE; - global_timer.Start("CUDAHistogramConstructor::FixHistogramDiscretizedKernel"); - if (need_fix_histogram_features_.size() > 0) { - if (smaller_num_bits_in_histogram_bins <= 16) { - FixHistogramDiscretizedKernel<<>>( - cuda_feature_num_bins_.RawData(), - cuda_feature_hist_offsets_.RawData(), - cuda_feature_most_freq_bins_.RawData(), - cuda_need_fix_histogram_features_.RawData(), - cuda_need_fix_histogram_features_num_bin_aligned_.RawData(), - cuda_smaller_leaf_splits); - } else { - FixHistogramDiscretizedKernel<<>>( - cuda_feature_num_bins_.RawData(), - cuda_feature_hist_offsets_.RawData(), - cuda_feature_most_freq_bins_.RawData(), - cuda_need_fix_histogram_features_.RawData(), - cuda_need_fix_histogram_features_num_bin_aligned_.RawData(), - cuda_smaller_leaf_splits); - } - } - global_timer.Stop("CUDAHistogramConstructor::FixHistogramDiscretizedKernel"); - global_timer.Start("CUDAHistogramConstructor::SubtractHistogramDiscretizedKernel"); - if (parent_num_bits_in_histogram_bins <= 16) { - CHECK_LE(smaller_num_bits_in_histogram_bins, 16); - CHECK_LE(larger_num_bits_in_histogram_bins, 16); - SubtractHistogramDiscretizedKernel<<>>( - num_total_bin_, - cuda_smaller_leaf_splits, - cuda_larger_leaf_splits, - hist_buffer_for_num_bit_change_.RawData()); - } else if (larger_num_bits_in_histogram_bins <= 16) { - CHECK_LE(smaller_num_bits_in_histogram_bins, 16); - SubtractHistogramDiscretizedKernel<<>>( - num_total_bin_, - cuda_smaller_leaf_splits, - cuda_larger_leaf_splits, - hist_buffer_for_num_bit_change_.RawData()); - CopyChangedNumBitHistogram<<>>( - num_total_bin_, - cuda_larger_leaf_splits, - hist_buffer_for_num_bit_change_.RawData()); - } else if (smaller_num_bits_in_histogram_bins <= 16) { - SubtractHistogramDiscretizedKernel<<>>( - num_total_bin_, - cuda_smaller_leaf_splits, - cuda_larger_leaf_splits, - hist_buffer_for_num_bit_change_.RawData()); - } else { - SubtractHistogramDiscretizedKernel<<>>( - num_total_bin_, - cuda_smaller_leaf_splits, - cuda_larger_leaf_splits, - hist_buffer_for_num_bit_change_.RawData()); - } - global_timer.Stop("CUDAHistogramConstructor::SubtractHistogramDiscretizedKernel"); - } + const CUDALeafSplitsStruct* cuda_larger_leaf_splits) { + const int num_subtract_threads = 2 * num_total_bin_; + const int num_subtract_blocks = (num_subtract_threads + SUBTRACT_BLOCK_SIZE - 1) / SUBTRACT_BLOCK_SIZE; + global_timer.Start("CUDAHistogramConstructor::FixHistogramKernel"); + if (need_fix_histogram_features_.size() > 0) { + FixHistogramKernel<<>>( + cuda_feature_num_bins_, + cuda_feature_hist_offsets_, + cuda_feature_most_freq_bins_, + cuda_need_fix_histogram_features_, + cuda_need_fix_histogram_features_num_bin_aligned_, + cuda_smaller_leaf_splits); + } + global_timer.Stop("CUDAHistogramConstructor::FixHistogramKernel"); + global_timer.Start("CUDAHistogramConstructor::SubtractHistogramKernel"); + SubtractHistogramKernel<<>>( + num_total_bin_, + cuda_smaller_leaf_splits, + cuda_larger_leaf_splits); + global_timer.Stop("CUDAHistogramConstructor::SubtractHistogramKernel"); } } // namespace LightGBM diff --git a/src/treelearner/cuda/cuda_histogram_constructor.hpp b/src/treelearner/cuda/cuda_histogram_constructor.hpp index ddc78cb17d90..7e600e7c01b4 100644 --- a/src/treelearner/cuda/cuda_histogram_constructor.hpp +++ b/src/treelearner/cuda/cuda_histogram_constructor.hpp @@ -9,7 +9,6 @@ #ifdef USE_CUDA #include -#include #include #include @@ -38,9 +37,7 @@ class CUDAHistogramConstructor { const int min_data_in_leaf, const double min_sum_hessian_in_leaf, const int gpu_device_id, - const bool gpu_use_dp, - const bool use_discretized_grad, - const int grad_discretized_bins); + const bool gpu_use_dp); ~CUDAHistogramConstructor(); @@ -52,16 +49,7 @@ class CUDAHistogramConstructor { const data_size_t num_data_in_smaller_leaf, const data_size_t num_data_in_larger_leaf, const double sum_hessians_in_smaller_leaf, - const double sum_hessians_in_larger_leaf, - const uint8_t num_bits_in_histogram_bins); - - void SubtractHistogramForLeaf( - const CUDALeafSplitsStruct* cuda_smaller_leaf_splits, - const CUDALeafSplitsStruct* cuda_larger_leaf_splits, - const bool use_discretized_grad, - const uint8_t parent_num_bits_in_histogram_bins, - const uint8_t smaller_num_bits_in_histogram_bins, - const uint8_t larger_num_bits_in_histogram_bins); + const double sum_hessians_in_larger_leaf); void ResetTrainingData(const Dataset* train_data, TrainingShareStates* share_states); @@ -69,9 +57,9 @@ class CUDAHistogramConstructor { void BeforeTrain(const score_t* gradients, const score_t* hessians); - const hist_t* cuda_hist() const { return cuda_hist_.RawData(); } + const hist_t* cuda_hist() const { return cuda_hist_; } - hist_t* cuda_hist_pointer() { return cuda_hist_.RawData(); } + hist_t* cuda_hist_pointer() { return cuda_hist_; } private: void InitFeatureMetaInfo(const Dataset* train_data, const std::vector& feature_hist_offsets); @@ -86,39 +74,30 @@ class CUDAHistogramConstructor { template void LaunchConstructHistogramKernelInner( const CUDALeafSplitsStruct* cuda_smaller_leaf_splits, - const data_size_t num_data_in_smaller_leaf, - const uint8_t num_bits_in_histogram_bins); + const data_size_t num_data_in_smaller_leaf); template void LaunchConstructHistogramKernelInner0( const CUDALeafSplitsStruct* cuda_smaller_leaf_splits, - const data_size_t num_data_in_smaller_leaf, - const uint8_t num_bits_in_histogram_bins); + const data_size_t num_data_in_smaller_leaf); template void LaunchConstructHistogramKernelInner1( const CUDALeafSplitsStruct* cuda_smaller_leaf_splits, - const data_size_t num_data_in_smaller_leaf, - const uint8_t num_bits_in_histogram_bins); + const data_size_t num_data_in_smaller_leaf); template void LaunchConstructHistogramKernelInner2( const CUDALeafSplitsStruct* cuda_smaller_leaf_splits, - const data_size_t num_data_in_smaller_leaf, - const uint8_t num_bits_in_histogram_bins); + const data_size_t num_data_in_smaller_leaf); void LaunchConstructHistogramKernel( const CUDALeafSplitsStruct* cuda_smaller_leaf_splits, - const data_size_t num_data_in_smaller_leaf, - const uint8_t num_bits_in_histogram_bins); + const data_size_t num_data_in_smaller_leaf); void LaunchSubtractHistogramKernel( const CUDALeafSplitsStruct* cuda_smaller_leaf_splits, - const CUDALeafSplitsStruct* cuda_larger_leaf_splits, - const bool use_discretized_grad, - const uint8_t parent_num_bits_in_histogram_bins, - const uint8_t smaller_num_bits_in_histogram_bins, - const uint8_t larger_num_bits_in_histogram_bins); + const CUDALeafSplitsStruct* cuda_larger_leaf_splits); // Host memory @@ -157,21 +136,19 @@ class CUDAHistogramConstructor { /*! \brief CUDA row wise data */ std::unique_ptr cuda_row_data_; /*! \brief number of bins per feature */ - CUDAVector cuda_feature_num_bins_; + uint32_t* cuda_feature_num_bins_; /*! \brief offsets in histogram of all features */ - CUDAVector cuda_feature_hist_offsets_; + uint32_t* cuda_feature_hist_offsets_; /*! \brief most frequent bins in each feature */ - CUDAVector cuda_feature_most_freq_bins_; + uint32_t* cuda_feature_most_freq_bins_; /*! \brief CUDA histograms */ - CUDAVector cuda_hist_; + hist_t* cuda_hist_; /*! \brief CUDA histograms buffer for each block */ - CUDAVector cuda_hist_buffer_; + float* cuda_hist_buffer_; /*! \brief indices of feature whose histograms need to be fixed */ - CUDAVector cuda_need_fix_histogram_features_; + int* cuda_need_fix_histogram_features_; /*! \brief aligned number of bins of the features whose histograms need to be fixed */ - CUDAVector cuda_need_fix_histogram_features_num_bin_aligned_; - /*! \brief histogram buffer used in histogram subtraction with different number of bits for histogram bins */ - CUDAVector hist_buffer_for_num_bit_change_; + uint32_t* cuda_need_fix_histogram_features_num_bin_aligned_; // CUDA memory, held by other object @@ -184,10 +161,6 @@ class CUDAHistogramConstructor { const int gpu_device_id_; /*! \brief use double precision histogram per block */ const bool gpu_use_dp_; - /*! \brief whether to use quantized gradients */ - const bool use_quantized_grad_; - /*! \brief the number of bins to quantized gradients */ - const int num_grad_quant_bins_; }; } // namespace LightGBM diff --git a/src/treelearner/cuda/cuda_leaf_splits.cpp b/src/treelearner/cuda/cuda_leaf_splits.cpp index 57b5b777c142..6aa020d9ea0d 100644 --- a/src/treelearner/cuda/cuda_leaf_splits.cpp +++ b/src/treelearner/cuda/cuda_leaf_splits.cpp @@ -11,22 +11,27 @@ namespace LightGBM { CUDALeafSplits::CUDALeafSplits(const data_size_t num_data): -num_data_(num_data) {} +num_data_(num_data) { + cuda_struct_ = nullptr; + cuda_sum_of_gradients_buffer_ = nullptr; + cuda_sum_of_hessians_buffer_ = nullptr; +} -CUDALeafSplits::~CUDALeafSplits() {} +CUDALeafSplits::~CUDALeafSplits() { + DeallocateCUDAMemory(&cuda_struct_, __FILE__, __LINE__); + DeallocateCUDAMemory(&cuda_sum_of_gradients_buffer_, __FILE__, __LINE__); + DeallocateCUDAMemory(&cuda_sum_of_hessians_buffer_, __FILE__, __LINE__); +} -void CUDALeafSplits::Init(const bool use_quantized_grad) { +void CUDALeafSplits::Init() { num_blocks_init_from_gradients_ = (num_data_ + NUM_THRADS_PER_BLOCK_LEAF_SPLITS - 1) / NUM_THRADS_PER_BLOCK_LEAF_SPLITS; // allocate more memory for sum reduction in CUDA // only the first element records the final sum - cuda_sum_of_gradients_buffer_.Resize(static_cast(num_blocks_init_from_gradients_)); - cuda_sum_of_hessians_buffer_.Resize(static_cast(num_blocks_init_from_gradients_)); - if (use_quantized_grad) { - cuda_sum_of_gradients_hessians_buffer_.Resize(static_cast(num_blocks_init_from_gradients_)); - } + AllocateCUDAMemory(&cuda_sum_of_gradients_buffer_, num_blocks_init_from_gradients_, __FILE__, __LINE__); + AllocateCUDAMemory(&cuda_sum_of_hessians_buffer_, num_blocks_init_from_gradients_, __FILE__, __LINE__); - cuda_struct_.Resize(1); + AllocateCUDAMemory(&cuda_struct_, 1, __FILE__, __LINE__); } void CUDALeafSplits::InitValues() { @@ -41,33 +46,24 @@ void CUDALeafSplits::InitValues( const data_size_t num_used_indices, hist_t* cuda_hist_in_leaf, double* root_sum_hessians) { cuda_gradients_ = cuda_gradients; cuda_hessians_ = cuda_hessians; - cuda_sum_of_gradients_buffer_.SetValue(0); - cuda_sum_of_hessians_buffer_.SetValue(0); + SetCUDAMemory(cuda_sum_of_gradients_buffer_, 0, num_blocks_init_from_gradients_, __FILE__, __LINE__); + SetCUDAMemory(cuda_sum_of_hessians_buffer_, 0, num_blocks_init_from_gradients_, __FILE__, __LINE__); LaunchInitValuesKernal(lambda_l1, lambda_l2, cuda_bagging_data_indices, cuda_data_indices_in_leaf, num_used_indices, cuda_hist_in_leaf); - CopyFromCUDADeviceToHost(root_sum_hessians, cuda_sum_of_hessians_buffer_.RawData(), 1, __FILE__, __LINE__); - SynchronizeCUDADevice(__FILE__, __LINE__); -} - -void CUDALeafSplits::InitValues( - const double lambda_l1, const double lambda_l2, - const int16_t* cuda_gradients_and_hessians, - const data_size_t* cuda_bagging_data_indices, - const data_size_t* cuda_data_indices_in_leaf, const data_size_t num_used_indices, - hist_t* cuda_hist_in_leaf, double* root_sum_hessians, - const score_t* grad_scale, const score_t* hess_scale) { - cuda_gradients_ = reinterpret_cast(cuda_gradients_and_hessians); - cuda_hessians_ = nullptr; - LaunchInitValuesKernal(lambda_l1, lambda_l2, cuda_bagging_data_indices, cuda_data_indices_in_leaf, num_used_indices, cuda_hist_in_leaf, grad_scale, hess_scale); - CopyFromCUDADeviceToHost(root_sum_hessians, cuda_sum_of_hessians_buffer_.RawData(), 1, __FILE__, __LINE__); + CopyFromCUDADeviceToHost(root_sum_hessians, cuda_sum_of_hessians_buffer_, 1, __FILE__, __LINE__); SynchronizeCUDADevice(__FILE__, __LINE__); } void CUDALeafSplits::Resize(const data_size_t num_data) { + if (num_data > num_data_) { + DeallocateCUDAMemory(&cuda_sum_of_gradients_buffer_, __FILE__, __LINE__); + DeallocateCUDAMemory(&cuda_sum_of_hessians_buffer_, __FILE__, __LINE__); + num_blocks_init_from_gradients_ = (num_data + NUM_THRADS_PER_BLOCK_LEAF_SPLITS - 1) / NUM_THRADS_PER_BLOCK_LEAF_SPLITS; + AllocateCUDAMemory(&cuda_sum_of_gradients_buffer_, num_blocks_init_from_gradients_, __FILE__, __LINE__); + AllocateCUDAMemory(&cuda_sum_of_hessians_buffer_, num_blocks_init_from_gradients_, __FILE__, __LINE__); + } else { + num_blocks_init_from_gradients_ = (num_data + NUM_THRADS_PER_BLOCK_LEAF_SPLITS - 1) / NUM_THRADS_PER_BLOCK_LEAF_SPLITS; + } num_data_ = num_data; - num_blocks_init_from_gradients_ = (num_data + NUM_THRADS_PER_BLOCK_LEAF_SPLITS - 1) / NUM_THRADS_PER_BLOCK_LEAF_SPLITS; - cuda_sum_of_gradients_buffer_.Resize(static_cast(num_blocks_init_from_gradients_)); - cuda_sum_of_hessians_buffer_.Resize(static_cast(num_blocks_init_from_gradients_)); - cuda_sum_of_gradients_hessians_buffer_.Resize(static_cast(num_blocks_init_from_gradients_)); } } // namespace LightGBM diff --git a/src/treelearner/cuda/cuda_leaf_splits.cu b/src/treelearner/cuda/cuda_leaf_splits.cu index ae505ecd55dd..29e42f67ead9 100644 --- a/src/treelearner/cuda/cuda_leaf_splits.cu +++ b/src/treelearner/cuda/cuda_leaf_splits.cu @@ -81,90 +81,6 @@ __global__ void CUDAInitValuesKernel2( } } -template -__global__ void CUDAInitValuesKernel3(const int16_t* cuda_gradients_and_hessians, - const data_size_t num_data, const data_size_t* cuda_bagging_data_indices, - double* cuda_sum_of_gradients, double* cuda_sum_of_hessians, int64_t* cuda_sum_of_hessians_hessians, - const score_t* grad_scale_pointer, const score_t* hess_scale_pointer) { - const score_t grad_scale = *grad_scale_pointer; - const score_t hess_scale = *hess_scale_pointer; - __shared__ int64_t shared_mem_buffer[32]; - const data_size_t data_index = static_cast(threadIdx.x + blockIdx.x * blockDim.x); - int64_t int_gradient = 0; - int64_t int_hessian = 0; - if (data_index < num_data) { - int_gradient = USE_INDICES ? cuda_gradients_and_hessians[2 * cuda_bagging_data_indices[data_index] + 1] : - cuda_gradients_and_hessians[2 * data_index + 1]; - int_hessian = USE_INDICES ? cuda_gradients_and_hessians[2 * cuda_bagging_data_indices[data_index]] : - cuda_gradients_and_hessians[2 * data_index]; - } - const int64_t block_sum_gradient = ShuffleReduceSum(int_gradient, shared_mem_buffer, blockDim.x); - __syncthreads(); - const int64_t block_sum_hessian = ShuffleReduceSum(int_hessian, shared_mem_buffer, blockDim.x); - if (threadIdx.x == 0) { - cuda_sum_of_gradients[blockIdx.x] = block_sum_gradient * grad_scale; - cuda_sum_of_hessians[blockIdx.x] = block_sum_hessian * hess_scale; - cuda_sum_of_hessians_hessians[blockIdx.x] = ((block_sum_gradient << 32) | block_sum_hessian); - } -} - -__global__ void CUDAInitValuesKernel4( - const double lambda_l1, - const double lambda_l2, - const int num_blocks_to_reduce, - double* cuda_sum_of_gradients, - double* cuda_sum_of_hessians, - int64_t* cuda_sum_of_gradients_hessians, - const data_size_t num_data, - const data_size_t* cuda_data_indices_in_leaf, - hist_t* cuda_hist_in_leaf, - CUDALeafSplitsStruct* cuda_struct) { - __shared__ double shared_mem_buffer[32]; - double thread_sum_of_gradients = 0.0f; - double thread_sum_of_hessians = 0.0f; - int64_t thread_sum_of_gradients_hessians = 0; - for (int block_index = static_cast(threadIdx.x); block_index < num_blocks_to_reduce; block_index += static_cast(blockDim.x)) { - thread_sum_of_gradients += cuda_sum_of_gradients[block_index]; - thread_sum_of_hessians += cuda_sum_of_hessians[block_index]; - thread_sum_of_gradients_hessians += cuda_sum_of_gradients_hessians[block_index]; - } - const double sum_of_gradients = ShuffleReduceSum(thread_sum_of_gradients, shared_mem_buffer, blockDim.x); - __syncthreads(); - const double sum_of_hessians = ShuffleReduceSum(thread_sum_of_hessians, shared_mem_buffer, blockDim.x); - __syncthreads(); - const double sum_of_gradients_hessians = ShuffleReduceSum( - thread_sum_of_gradients_hessians, - reinterpret_cast(shared_mem_buffer), - blockDim.x); - if (threadIdx.x == 0) { - cuda_sum_of_hessians[0] = sum_of_hessians; - cuda_struct->leaf_index = 0; - cuda_struct->sum_of_gradients = sum_of_gradients; - cuda_struct->sum_of_hessians = sum_of_hessians; - cuda_struct->sum_of_gradients_hessians = sum_of_gradients_hessians; - cuda_struct->num_data_in_leaf = num_data; - const bool use_l1 = lambda_l1 > 0.0f; - if (!use_l1) { - // no smoothing on root node - cuda_struct->gain = CUDALeafSplits::GetLeafGain(sum_of_gradients, sum_of_hessians, lambda_l1, lambda_l2, 0.0f, 0, 0.0f); - } else { - // no smoothing on root node - cuda_struct->gain = CUDALeafSplits::GetLeafGain(sum_of_gradients, sum_of_hessians, lambda_l1, lambda_l2, 0.0f, 0, 0.0f); - } - if (!use_l1) { - // no smoothing on root node - cuda_struct->leaf_value = - CUDALeafSplits::CalculateSplittedLeafOutput(sum_of_gradients, sum_of_hessians, lambda_l1, lambda_l2, 0.0f, 0, 0.0f); - } else { - // no smoothing on root node - cuda_struct->leaf_value = - CUDALeafSplits::CalculateSplittedLeafOutput(sum_of_gradients, sum_of_hessians, lambda_l1, lambda_l2, 0.0f, 0, 0.0f); - } - cuda_struct->data_indices_in_leaf = cuda_data_indices_in_leaf; - cuda_struct->hist_in_leaf = cuda_hist_in_leaf; - } -} - __global__ void InitValuesEmptyKernel(CUDALeafSplitsStruct* cuda_struct) { cuda_struct->leaf_index = -1; cuda_struct->sum_of_gradients = 0.0f; @@ -177,7 +93,7 @@ __global__ void InitValuesEmptyKernel(CUDALeafSplitsStruct* cuda_struct) { } void CUDALeafSplits::LaunchInitValuesEmptyKernel() { - InitValuesEmptyKernel<<<1, 1>>>(cuda_struct_.RawData()); + InitValuesEmptyKernel<<<1, 1>>>(cuda_struct_); } void CUDALeafSplits::LaunchInitValuesKernal( @@ -188,55 +104,23 @@ void CUDALeafSplits::LaunchInitValuesKernal( hist_t* cuda_hist_in_leaf) { if (cuda_bagging_data_indices == nullptr) { CUDAInitValuesKernel1<<>>( - cuda_gradients_, cuda_hessians_, num_used_indices, nullptr, cuda_sum_of_gradients_buffer_.RawData(), - cuda_sum_of_hessians_buffer_.RawData()); + cuda_gradients_, cuda_hessians_, num_used_indices, nullptr, cuda_sum_of_gradients_buffer_, + cuda_sum_of_hessians_buffer_); } else { CUDAInitValuesKernel1<<>>( - cuda_gradients_, cuda_hessians_, num_used_indices, cuda_bagging_data_indices, cuda_sum_of_gradients_buffer_.RawData(), - cuda_sum_of_hessians_buffer_.RawData()); + cuda_gradients_, cuda_hessians_, num_used_indices, cuda_bagging_data_indices, cuda_sum_of_gradients_buffer_, + cuda_sum_of_hessians_buffer_); } SynchronizeCUDADevice(__FILE__, __LINE__); CUDAInitValuesKernel2<<<1, NUM_THRADS_PER_BLOCK_LEAF_SPLITS>>>( lambda_l1, lambda_l2, num_blocks_init_from_gradients_, - cuda_sum_of_gradients_buffer_.RawData(), - cuda_sum_of_hessians_buffer_.RawData(), - num_used_indices, - cuda_data_indices_in_leaf, - cuda_hist_in_leaf, - cuda_struct_.RawData()); - SynchronizeCUDADevice(__FILE__, __LINE__); -} - -void CUDALeafSplits::LaunchInitValuesKernal( - const double lambda_l1, const double lambda_l2, - const data_size_t* cuda_bagging_data_indices, - const data_size_t* cuda_data_indices_in_leaf, - const data_size_t num_used_indices, - hist_t* cuda_hist_in_leaf, - const score_t* grad_scale, - const score_t* hess_scale) { - if (cuda_bagging_data_indices == nullptr) { - CUDAInitValuesKernel3<<>>( - reinterpret_cast(cuda_gradients_), num_used_indices, nullptr, cuda_sum_of_gradients_buffer_.RawData(), - cuda_sum_of_hessians_buffer_.RawData(), cuda_sum_of_gradients_hessians_buffer_.RawData(), grad_scale, hess_scale); - } else { - CUDAInitValuesKernel3<<>>( - reinterpret_cast(cuda_gradients_), num_used_indices, cuda_bagging_data_indices, cuda_sum_of_gradients_buffer_.RawData(), - cuda_sum_of_hessians_buffer_.RawData(), cuda_sum_of_gradients_hessians_buffer_.RawData(), grad_scale, hess_scale); - } - - SynchronizeCUDADevice(__FILE__, __LINE__); - CUDAInitValuesKernel4<<<1, NUM_THRADS_PER_BLOCK_LEAF_SPLITS>>>( - lambda_l1, lambda_l2, - num_blocks_init_from_gradients_, - cuda_sum_of_gradients_buffer_.RawData(), - cuda_sum_of_hessians_buffer_.RawData(), - cuda_sum_of_gradients_hessians_buffer_.RawData(), + cuda_sum_of_gradients_buffer_, + cuda_sum_of_hessians_buffer_, num_used_indices, cuda_data_indices_in_leaf, cuda_hist_in_leaf, - cuda_struct_.RawData()); + cuda_struct_); SynchronizeCUDADevice(__FILE__, __LINE__); } diff --git a/src/treelearner/cuda/cuda_leaf_splits.hpp b/src/treelearner/cuda/cuda_leaf_splits.hpp index 33a9ea578a1f..769f956b95c3 100644 --- a/src/treelearner/cuda/cuda_leaf_splits.hpp +++ b/src/treelearner/cuda/cuda_leaf_splits.hpp @@ -8,7 +8,7 @@ #ifdef USE_CUDA -#include +#include #include #include #include @@ -23,7 +23,6 @@ struct CUDALeafSplitsStruct { int leaf_index; double sum_of_gradients; double sum_of_hessians; - int64_t sum_of_gradients_hessians; data_size_t num_data_in_leaf; double gain; double leaf_value; @@ -37,7 +36,7 @@ class CUDALeafSplits { ~CUDALeafSplits(); - void Init(const bool use_quantized_grad); + void Init(); void InitValues( const double lambda_l1, const double lambda_l2, @@ -46,19 +45,11 @@ class CUDALeafSplits { const data_size_t* cuda_data_indices_in_leaf, const data_size_t num_used_indices, hist_t* cuda_hist_in_leaf, double* root_sum_hessians); - void InitValues( - const double lambda_l1, const double lambda_l2, - const int16_t* cuda_gradients_and_hessians, - const data_size_t* cuda_bagging_data_indices, - const data_size_t* cuda_data_indices_in_leaf, const data_size_t num_used_indices, - hist_t* cuda_hist_in_leaf, double* root_sum_hessians, - const score_t* grad_scale, const score_t* hess_scale); - void InitValues(); - const CUDALeafSplitsStruct* GetCUDAStruct() const { return cuda_struct_.RawDataReadOnly(); } + const CUDALeafSplitsStruct* GetCUDAStruct() const { return cuda_struct_; } - CUDALeafSplitsStruct* GetCUDAStructRef() { return cuda_struct_.RawData(); } + CUDALeafSplitsStruct* GetCUDAStructRef() { return cuda_struct_; } void Resize(const data_size_t num_data); @@ -149,24 +140,14 @@ class CUDALeafSplits { const data_size_t num_used_indices, hist_t* cuda_hist_in_leaf); - void LaunchInitValuesKernal( - const double lambda_l1, const double lambda_l2, - const data_size_t* cuda_bagging_data_indices, - const data_size_t* cuda_data_indices_in_leaf, - const data_size_t num_used_indices, - hist_t* cuda_hist_in_leaf, - const score_t* grad_scale, - const score_t* hess_scale); - // Host memory data_size_t num_data_; int num_blocks_init_from_gradients_; // CUDA memory, held by this object - CUDAVector cuda_struct_; - CUDAVector cuda_sum_of_gradients_buffer_; - CUDAVector cuda_sum_of_hessians_buffer_; - CUDAVector cuda_sum_of_gradients_hessians_buffer_; + CUDALeafSplitsStruct* cuda_struct_; + double* cuda_sum_of_gradients_buffer_; + double* cuda_sum_of_hessians_buffer_; // CUDA memory, held by other object const score_t* cuda_gradients_; diff --git a/src/treelearner/cuda/cuda_single_gpu_tree_learner.cpp b/src/treelearner/cuda/cuda_single_gpu_tree_learner.cpp index 8f8ff15f0715..1600f3767c0c 100644 --- a/src/treelearner/cuda/cuda_single_gpu_tree_learner.cpp +++ b/src/treelearner/cuda/cuda_single_gpu_tree_learner.cpp @@ -9,7 +9,7 @@ #include "cuda_single_gpu_tree_learner.hpp" #include -#include +#include #include #include #include @@ -39,14 +39,13 @@ void CUDASingleGPUTreeLearner::Init(const Dataset* train_data, bool is_constant_ SetCUDADevice(gpu_device_id_, __FILE__, __LINE__); cuda_smaller_leaf_splits_.reset(new CUDALeafSplits(num_data_)); - cuda_smaller_leaf_splits_->Init(config_->use_quantized_grad); + cuda_smaller_leaf_splits_->Init(); cuda_larger_leaf_splits_.reset(new CUDALeafSplits(num_data_)); - cuda_larger_leaf_splits_->Init(config_->use_quantized_grad); + cuda_larger_leaf_splits_->Init(); cuda_histogram_constructor_.reset(new CUDAHistogramConstructor(train_data_, config_->num_leaves, num_threads_, share_state_->feature_hist_offsets(), - config_->min_data_in_leaf, config_->min_sum_hessian_in_leaf, gpu_device_id_, config_->gpu_use_dp, - config_->use_quantized_grad, config_->num_grad_quant_bins)); + config_->min_data_in_leaf, config_->min_sum_hessian_in_leaf, gpu_device_id_, config_->gpu_use_dp)); cuda_histogram_constructor_->Init(train_data_, share_state_.get()); const auto& feature_hist_offsets = share_state_->feature_hist_offsets(); @@ -74,19 +73,11 @@ void CUDASingleGPUTreeLearner::Init(const Dataset* train_data, bool is_constant_ } AllocateBitset(); + cuda_leaf_gradient_stat_buffer_ = nullptr; + cuda_leaf_hessian_stat_buffer_ = nullptr; leaf_stat_buffer_size_ = 0; num_cat_threshold_ = 0; - if (config_->use_quantized_grad) { - cuda_leaf_gradient_stat_buffer_.Resize(config_->num_leaves); - cuda_leaf_hessian_stat_buffer_.Resize(config_->num_leaves); - cuda_gradient_discretizer_.reset(new CUDAGradientDiscretizer( - config_->num_grad_quant_bins, config_->num_iterations, config_->seed, is_constant_hessian, config_->stochastic_rounding)); - cuda_gradient_discretizer_->Init(num_data_, config_->num_leaves, train_data_->num_features(), train_data_); - } else { - cuda_gradient_discretizer_.reset(nullptr); - } - #ifdef DEBUG host_gradients_.resize(num_data_, 0.0f); host_hessians_.resize(num_data_, 0.0f); @@ -110,37 +101,19 @@ void CUDASingleGPUTreeLearner::BeforeTrain() { const data_size_t* leaf_splits_init_indices = cuda_data_partition_->use_bagging() ? cuda_data_partition_->cuda_data_indices() : nullptr; cuda_data_partition_->BeforeTrain(); - if (config_->use_quantized_grad) { - cuda_gradient_discretizer_->DiscretizeGradients(num_data_, gradients_, hessians_); - cuda_histogram_constructor_->BeforeTrain( - reinterpret_cast(cuda_gradient_discretizer_->discretized_gradients_and_hessians()), nullptr); - cuda_smaller_leaf_splits_->InitValues( - config_->lambda_l1, - config_->lambda_l2, - reinterpret_cast(cuda_gradient_discretizer_->discretized_gradients_and_hessians()), - leaf_splits_init_indices, - cuda_data_partition_->cuda_data_indices(), - root_num_data, - cuda_histogram_constructor_->cuda_hist_pointer(), - &leaf_sum_hessians_[0], - cuda_gradient_discretizer_->grad_scale_ptr(), - cuda_gradient_discretizer_->hess_scale_ptr()); - cuda_gradient_discretizer_->SetNumBitsInHistogramBin(0, -1, root_num_data, 0); - } else { - cuda_histogram_constructor_->BeforeTrain(gradients_, hessians_); - cuda_smaller_leaf_splits_->InitValues( - config_->lambda_l1, - config_->lambda_l2, - gradients_, - hessians_, - leaf_splits_init_indices, - cuda_data_partition_->cuda_data_indices(), - root_num_data, - cuda_histogram_constructor_->cuda_hist_pointer(), - &leaf_sum_hessians_[0]); - } + cuda_smaller_leaf_splits_->InitValues( + config_->lambda_l1, + config_->lambda_l2, + gradients_, + hessians_, + leaf_splits_init_indices, + cuda_data_partition_->cuda_data_indices(), + root_num_data, + cuda_histogram_constructor_->cuda_hist_pointer(), + &leaf_sum_hessians_[0]); leaf_num_data_[0] = root_num_data; cuda_larger_leaf_splits_->InitValues(); + cuda_histogram_constructor_->BeforeTrain(gradients_, hessians_); col_sampler_.ResetByTree(); cuda_best_split_finder_->BeforeTrain(col_sampler_.is_feature_used_bytree()); leaf_data_start_[0] = 0; @@ -168,70 +141,24 @@ Tree* CUDASingleGPUTreeLearner::Train(const score_t* gradients, const data_size_t num_data_in_larger_leaf = larger_leaf_index_ < 0 ? 0 : leaf_num_data_[larger_leaf_index_]; const double sum_hessians_in_smaller_leaf = leaf_sum_hessians_[smaller_leaf_index_]; const double sum_hessians_in_larger_leaf = larger_leaf_index_ < 0 ? 0 : leaf_sum_hessians_[larger_leaf_index_]; - const uint8_t num_bits_in_histogram_bins = config_->use_quantized_grad ? cuda_gradient_discretizer_->GetHistBitsInLeaf(smaller_leaf_index_) : 0; cuda_histogram_constructor_->ConstructHistogramForLeaf( cuda_smaller_leaf_splits_->GetCUDAStruct(), cuda_larger_leaf_splits_->GetCUDAStruct(), num_data_in_smaller_leaf, num_data_in_larger_leaf, sum_hessians_in_smaller_leaf, - sum_hessians_in_larger_leaf, - num_bits_in_histogram_bins); + sum_hessians_in_larger_leaf); global_timer.Stop("CUDASingleGPUTreeLearner::ConstructHistogramForLeaf"); global_timer.Start("CUDASingleGPUTreeLearner::FindBestSplitsForLeaf"); - uint8_t parent_num_bits_bin = 0; - uint8_t smaller_num_bits_bin = 0; - uint8_t larger_num_bits_bin = 0; - if (config_->use_quantized_grad) { - if (larger_leaf_index_ != -1) { - const int parent_leaf_index = std::min(smaller_leaf_index_, larger_leaf_index_); - parent_num_bits_bin = cuda_gradient_discretizer_->GetHistBitsInNode(parent_leaf_index); - smaller_num_bits_bin = cuda_gradient_discretizer_->GetHistBitsInLeaf(smaller_leaf_index_); - larger_num_bits_bin = cuda_gradient_discretizer_->GetHistBitsInLeaf(larger_leaf_index_); - } else { - parent_num_bits_bin = cuda_gradient_discretizer_->GetHistBitsInLeaf(0); - smaller_num_bits_bin = cuda_gradient_discretizer_->GetHistBitsInLeaf(0); - larger_num_bits_bin = cuda_gradient_discretizer_->GetHistBitsInLeaf(0); - } - } else { - parent_num_bits_bin = 0; - smaller_num_bits_bin = 0; - larger_num_bits_bin = 0; - } - cuda_histogram_constructor_->SubtractHistogramForLeaf( - cuda_smaller_leaf_splits_->GetCUDAStruct(), - cuda_larger_leaf_splits_->GetCUDAStruct(), - config_->use_quantized_grad, - parent_num_bits_bin, - smaller_num_bits_bin, - larger_num_bits_bin); - SelectFeatureByNode(tree.get()); - if (config_->use_quantized_grad) { - const uint8_t smaller_leaf_num_bits_bin = cuda_gradient_discretizer_->GetHistBitsInLeaf(smaller_leaf_index_); - const uint8_t larger_leaf_num_bits_bin = larger_leaf_index_ < 0 ? 32 : cuda_gradient_discretizer_->GetHistBitsInLeaf(larger_leaf_index_); - cuda_best_split_finder_->FindBestSplitsForLeaf( - cuda_smaller_leaf_splits_->GetCUDAStruct(), - cuda_larger_leaf_splits_->GetCUDAStruct(), - smaller_leaf_index_, larger_leaf_index_, - num_data_in_smaller_leaf, num_data_in_larger_leaf, - sum_hessians_in_smaller_leaf, sum_hessians_in_larger_leaf, - cuda_gradient_discretizer_->grad_scale_ptr(), - cuda_gradient_discretizer_->hess_scale_ptr(), - smaller_leaf_num_bits_bin, - larger_leaf_num_bits_bin); - } else { - cuda_best_split_finder_->FindBestSplitsForLeaf( - cuda_smaller_leaf_splits_->GetCUDAStruct(), - cuda_larger_leaf_splits_->GetCUDAStruct(), - smaller_leaf_index_, larger_leaf_index_, - num_data_in_smaller_leaf, num_data_in_larger_leaf, - sum_hessians_in_smaller_leaf, sum_hessians_in_larger_leaf, - nullptr, nullptr, 0, 0); - } - + cuda_best_split_finder_->FindBestSplitsForLeaf( + cuda_smaller_leaf_splits_->GetCUDAStruct(), + cuda_larger_leaf_splits_->GetCUDAStruct(), + smaller_leaf_index_, larger_leaf_index_, + num_data_in_smaller_leaf, num_data_in_larger_leaf, + sum_hessians_in_smaller_leaf, sum_hessians_in_larger_leaf); global_timer.Stop("CUDASingleGPUTreeLearner::FindBestSplitsForLeaf"); global_timer.Start("CUDASingleGPUTreeLearner::FindBestFromAllSplits"); const CUDASplitInfo* best_split_info = nullptr; @@ -320,19 +247,9 @@ Tree* CUDASingleGPUTreeLearner::Train(const score_t* gradients, #endif // DEBUG smaller_leaf_index_ = (leaf_num_data_[best_leaf_index_] < leaf_num_data_[right_leaf_index] ? best_leaf_index_ : right_leaf_index); larger_leaf_index_ = (smaller_leaf_index_ == best_leaf_index_ ? right_leaf_index : best_leaf_index_); - - if (config_->use_quantized_grad) { - cuda_gradient_discretizer_->SetNumBitsInHistogramBin( - best_leaf_index_, right_leaf_index, leaf_num_data_[best_leaf_index_], leaf_num_data_[right_leaf_index]); - } global_timer.Stop("CUDASingleGPUTreeLearner::Split"); } SynchronizeCUDADevice(__FILE__, __LINE__); - if (config_->use_quantized_grad && config_->quant_train_renew_leaf) { - global_timer.Start("CUDASingleGPUTreeLearner::RenewDiscretizedTreeLeaves"); - RenewDiscretizedTreeLeaves(tree.get()); - global_timer.Stop("CUDASingleGPUTreeLearner::RenewDiscretizedTreeLeaves"); - } tree->ToHost(); return tree.release(); } @@ -405,7 +322,7 @@ void CUDASingleGPUTreeLearner::RenewTreeOutput(Tree* tree, const ObjectiveFuncti } std::vector n_nozeroworker_perleaf(cuda_tree->num_leaves(), 1); int num_machines = Network::num_machines(); - #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) + #pragma omp parallel for schedule(static) for (int i = 0; i < cuda_tree->num_leaves(); ++i) { const double output = static_cast(cuda_tree->LeafOutput(i)); data_size_t cnt_leaf_data = leaf_num_data_[i]; @@ -440,8 +357,8 @@ void CUDASingleGPUTreeLearner::RenewTreeOutput(Tree* tree, const ObjectiveFuncti Tree* CUDASingleGPUTreeLearner::FitByExistingTree(const Tree* old_tree, const score_t* gradients, const score_t* hessians) const { std::unique_ptr cuda_tree(new CUDATree(old_tree)); - cuda_leaf_gradient_stat_buffer_.SetValue(0); - cuda_leaf_hessian_stat_buffer_.SetValue(0); + SetCUDAMemory(cuda_leaf_gradient_stat_buffer_, 0, static_cast(old_tree->num_leaves()), __FILE__, __LINE__); + SetCUDAMemory(cuda_leaf_hessian_stat_buffer_, 0, static_cast(old_tree->num_leaves()), __FILE__, __LINE__); ReduceLeafStat(cuda_tree.get(), gradients, hessians, cuda_data_partition_->cuda_data_indices()); cuda_tree->SyncLeafOutputFromCUDAToHost(); return cuda_tree.release(); @@ -456,9 +373,13 @@ Tree* CUDASingleGPUTreeLearner::FitByExistingTree(const Tree* old_tree, const st const int num_block = (refit_num_data_ + CUDA_SINGLE_GPU_TREE_LEARNER_BLOCK_SIZE - 1) / CUDA_SINGLE_GPU_TREE_LEARNER_BLOCK_SIZE; buffer_size *= static_cast(num_block + 1); } - if (static_cast(buffer_size) > cuda_leaf_gradient_stat_buffer_.Size()) { - cuda_leaf_gradient_stat_buffer_.Resize(buffer_size); - cuda_leaf_hessian_stat_buffer_.Resize(buffer_size); + if (buffer_size != leaf_stat_buffer_size_) { + if (leaf_stat_buffer_size_ != 0) { + DeallocateCUDAMemory(&cuda_leaf_gradient_stat_buffer_, __FILE__, __LINE__); + DeallocateCUDAMemory(&cuda_leaf_hessian_stat_buffer_, __FILE__, __LINE__); + } + AllocateCUDAMemory(&cuda_leaf_gradient_stat_buffer_, static_cast(buffer_size), __FILE__, __LINE__); + AllocateCUDAMemory(&cuda_leaf_hessian_stat_buffer_, static_cast(buffer_size), __FILE__, __LINE__); } return FitByExistingTree(old_tree, gradients, hessians); } @@ -592,15 +513,6 @@ void CUDASingleGPUTreeLearner::CheckSplitValid( } #endif // DEBUG -void CUDASingleGPUTreeLearner::RenewDiscretizedTreeLeaves(CUDATree* cuda_tree) { - cuda_data_partition_->ReduceLeafGradStat( - gradients_, hessians_, cuda_tree, - cuda_leaf_gradient_stat_buffer_.RawData(), - cuda_leaf_hessian_stat_buffer_.RawData()); - LaunchCalcLeafValuesGivenGradStat(cuda_tree, cuda_data_partition_->cuda_data_indices()); - SynchronizeCUDADevice(__FILE__, __LINE__); -} - } // namespace LightGBM #endif // USE_CUDA diff --git a/src/treelearner/cuda/cuda_single_gpu_tree_learner.cu b/src/treelearner/cuda/cuda_single_gpu_tree_learner.cu index 670f1f36d643..8a558ddc43d1 100644 --- a/src/treelearner/cuda/cuda_single_gpu_tree_learner.cu +++ b/src/treelearner/cuda/cuda_single_gpu_tree_learner.cu @@ -129,18 +129,18 @@ void CUDASingleGPUTreeLearner::LaunchReduceLeafStatKernel( if (num_leaves <= 2048) { ReduceLeafStatKernel_SharedMemory<<>>( gradients, hessians, num_leaves, num_data, cuda_data_partition_->cuda_data_index_to_leaf_index(), - cuda_leaf_gradient_stat_buffer_.RawData(), cuda_leaf_hessian_stat_buffer_.RawData()); + cuda_leaf_gradient_stat_buffer_, cuda_leaf_hessian_stat_buffer_); } else { ReduceLeafStatKernel_GlobalMemory<<>>( gradients, hessians, num_leaves, num_data, cuda_data_partition_->cuda_data_index_to_leaf_index(), - cuda_leaf_gradient_stat_buffer_.RawData(), cuda_leaf_hessian_stat_buffer_.RawData()); + cuda_leaf_gradient_stat_buffer_, cuda_leaf_hessian_stat_buffer_); } const bool use_l1 = config_->lambda_l1 > 0.0f; const bool use_smoothing = config_->path_smooth > 0.0f; num_block = (num_leaves + CUDA_SINGLE_GPU_TREE_LEARNER_BLOCK_SIZE - 1) / CUDA_SINGLE_GPU_TREE_LEARNER_BLOCK_SIZE; #define CalcRefitLeafOutputKernel_ARGS \ - num_leaves, cuda_leaf_gradient_stat_buffer_.RawData(), cuda_leaf_hessian_stat_buffer_.RawData(), num_data_in_leaf, \ + num_leaves, cuda_leaf_gradient_stat_buffer_, cuda_leaf_hessian_stat_buffer_, num_data_in_leaf, \ leaf_parent, left_child, right_child, \ config_->lambda_l1, config_->lambda_l2, config_->path_smooth, \ shrinkage_rate, config_->refit_decay_rate, cuda_leaf_value @@ -162,7 +162,6 @@ void CUDASingleGPUTreeLearner::LaunchReduceLeafStatKernel( <<>>(CalcRefitLeafOutputKernel_ARGS); } } - #undef CalcRefitLeafOutputKernel_ARGS } template @@ -257,37 +256,6 @@ void CUDASingleGPUTreeLearner::LaunchConstructBitsetForCategoricalSplitKernel( CUDAConstructBitset(best_split_info, num_cat_threshold_, cuda_bitset_, cuda_bitset_len_); } -void CUDASingleGPUTreeLearner::LaunchCalcLeafValuesGivenGradStat( - CUDATree* cuda_tree, const data_size_t* num_data_in_leaf) { - #define CalcRefitLeafOutputKernel_ARGS \ - cuda_tree->num_leaves(), cuda_leaf_gradient_stat_buffer_.RawData(), cuda_leaf_hessian_stat_buffer_.RawData(), num_data_in_leaf, \ - cuda_tree->cuda_leaf_parent(), cuda_tree->cuda_left_child(), cuda_tree->cuda_right_child(), \ - config_->lambda_l1, config_->lambda_l2, config_->path_smooth, \ - 1.0f, config_->refit_decay_rate, cuda_tree->cuda_leaf_value_ref() - const bool use_l1 = config_->lambda_l1 > 0.0f; - const bool use_smoothing = config_->path_smooth > 0.0f; - const int num_block = (cuda_tree->num_leaves() + CUDA_SINGLE_GPU_TREE_LEARNER_BLOCK_SIZE - 1) / CUDA_SINGLE_GPU_TREE_LEARNER_BLOCK_SIZE; - if (!use_l1) { - if (!use_smoothing) { - CalcRefitLeafOutputKernel - <<>>(CalcRefitLeafOutputKernel_ARGS); - } else { - CalcRefitLeafOutputKernel - <<>>(CalcRefitLeafOutputKernel_ARGS); - } - } else { - if (!use_smoothing) { - CalcRefitLeafOutputKernel - <<>>(CalcRefitLeafOutputKernel_ARGS); - } else { - CalcRefitLeafOutputKernel - <<>>(CalcRefitLeafOutputKernel_ARGS); - } - } - - #undef CalcRefitLeafOutputKernel_ARGS -} - } // namespace LightGBM #endif // USE_CUDA diff --git a/src/treelearner/cuda/cuda_single_gpu_tree_learner.hpp b/src/treelearner/cuda/cuda_single_gpu_tree_learner.hpp index a1ea79efa1a1..576d01ffe5cc 100644 --- a/src/treelearner/cuda/cuda_single_gpu_tree_learner.hpp +++ b/src/treelearner/cuda/cuda_single_gpu_tree_learner.hpp @@ -16,7 +16,6 @@ #include "cuda_data_partition.hpp" #include "cuda_best_split_finder.hpp" -#include "cuda_gradient_discretizer.hpp" #include "../serial_tree_learner.h" namespace LightGBM { @@ -75,10 +74,6 @@ class CUDASingleGPUTreeLearner: public SerialTreeLearner { const double sum_left_gradients, const double sum_right_gradients); #endif // DEBUG - void RenewDiscretizedTreeLeaves(CUDATree* cuda_tree); - - void LaunchCalcLeafValuesGivenGradStat(CUDATree* cuda_tree, const data_size_t* num_data_in_leaf); - // GPU device ID int gpu_device_id_; // number of threads on CPU @@ -95,8 +90,6 @@ class CUDASingleGPUTreeLearner: public SerialTreeLearner { std::unique_ptr cuda_histogram_constructor_; // for best split information finding, given the histograms std::unique_ptr cuda_best_split_finder_; - // gradient discretizer for quantized training - std::unique_ptr cuda_gradient_discretizer_; std::vector leaf_best_split_feature_; std::vector leaf_best_split_threshold_; @@ -115,8 +108,8 @@ class CUDASingleGPUTreeLearner: public SerialTreeLearner { std::vector categorical_bin_to_value_; std::vector categorical_bin_offsets_; - mutable CUDAVector cuda_leaf_gradient_stat_buffer_; - mutable CUDAVector cuda_leaf_hessian_stat_buffer_; + mutable double* cuda_leaf_gradient_stat_buffer_; + mutable double* cuda_leaf_hessian_stat_buffer_; mutable data_size_t leaf_stat_buffer_size_; mutable data_size_t refit_num_data_; uint32_t* cuda_bitset_; @@ -155,7 +148,7 @@ class CUDASingleGPUTreeLearner: public SerialTreeLearner { #pragma warning(disable : 4702) explicit CUDASingleGPUTreeLearner(const Config* tree_config, const bool /*boosting_on_cuda*/) : SerialTreeLearner(tree_config) { Log::Fatal("CUDA Tree Learner was not enabled in this build.\n" - "Please recompile with CMake option -DUSE_CUDA=1"); + "Please recompile with CMake option -DUSE_CUDAP=1"); } }; diff --git a/src/treelearner/data_parallel_tree_learner.cpp b/src/treelearner/data_parallel_tree_learner.cpp index 64c342e5b01d..2509db5e722a 100644 --- a/src/treelearner/data_parallel_tree_learner.cpp +++ b/src/treelearner/data_parallel_tree_learner.cpp @@ -228,7 +228,7 @@ void DataParallelTreeLearner::FindBestSplits(const Tree* tree) { if (local_data_on_smaller_leaf <= 0) { // clear histogram buffer before synchronizing // otherwise histogram contents from the previous iteration will be sent - #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) + #pragma omp parallel for schedule(static) for (int feature_index = 0; feature_index < this->num_features_; ++feature_index) { if (this->col_sampler_.is_feature_used_bytree()[feature_index] == false) continue; @@ -249,7 +249,7 @@ void DataParallelTreeLearner::FindBestSplits(const Tree* tree) { // construct local histograms global_timer.Start("DataParallelTreeLearner::ReduceHistogram"); global_timer.Start("DataParallelTreeLearner::ReduceHistogram::Copy"); - #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) + #pragma omp parallel for schedule(static) for (int feature_index = 0; feature_index < this->num_features_; ++feature_index) { if (this->col_sampler_.is_feature_used_bytree()[feature_index] == false) continue; @@ -318,7 +318,7 @@ void DataParallelTreeLearner::FindBestSplitsFromHistograms(const if (parent_num_bits > 16 && larger_leaf_num_bits <= 16) { CHECK_LE(smaller_leaf_num_bits, 16); OMP_INIT_EX(); - #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) + #pragma omp parallel for schedule(static) for (int feature_index = 0; feature_index < this->num_features_; ++feature_index) { OMP_LOOP_EX_BEGIN(); if (!is_feature_aggregated_[feature_index]) continue; @@ -330,7 +330,7 @@ void DataParallelTreeLearner::FindBestSplitsFromHistograms(const } OMP_INIT_EX(); - #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) + #pragma omp parallel for schedule(static) for (int feature_index = 0; feature_index < this->num_features_; ++feature_index) { OMP_LOOP_EX_BEGIN(); if (!is_feature_aggregated_[feature_index]) continue; diff --git a/src/treelearner/data_partition.hpp b/src/treelearner/data_partition.hpp index 25c39c3ebde2..7a6ac031e62d 100644 --- a/src/treelearner/data_partition.hpp +++ b/src/treelearner/data_partition.hpp @@ -52,7 +52,7 @@ class DataPartition { if (used_data_indices_ == nullptr) { // if using all data leaf_count_[0] = num_data_; -#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static, 512) if (num_data_ >= 1024) +#pragma omp parallel for schedule(static, 512) if (num_data_ >= 1024) for (data_size_t i = 0; i < num_data_; ++i) { indices_[i] = i; } diff --git a/src/treelearner/feature_histogram.hpp b/src/treelearner/feature_histogram.hpp index bd5bda1e8879..d917ed7917ec 100644 --- a/src/treelearner/feature_histogram.hpp +++ b/src/treelearner/feature_histogram.hpp @@ -1692,7 +1692,7 @@ class HistogramPool { auto& ref_feature_meta = *feature_meta; const int num_feature = train_data->num_features(); ref_feature_meta.resize(num_feature); -#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static, 512) if (num_feature >= 1024) +#pragma omp parallel for schedule(static, 512) if (num_feature >= 1024) for (int i = 0; i < num_feature; ++i) { if (USE_DATA) { ref_feature_meta[i].num_bin = train_data->FeatureNumBin(i); @@ -1749,7 +1749,7 @@ class HistogramPool { if (config->use_quantized_grad) { OMP_INIT_EX(); - #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) + #pragma omp parallel for schedule(static) for (int i = old_cache_size; i < cache_size; ++i) { OMP_LOOP_EX_BEGIN(); pool_[i].reset(new FeatureHistogram[train_data->num_features()]); @@ -1763,7 +1763,7 @@ class HistogramPool { OMP_THROW_EX(); } else { OMP_INIT_EX(); - #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) + #pragma omp parallel for schedule(static) for (int i = old_cache_size; i < cache_size; ++i) { OMP_LOOP_EX_BEGIN(); pool_[i].reset(new FeatureHistogram[train_data->num_features()]); @@ -1787,7 +1787,7 @@ class HistogramPool { old_config->extra_trees != config->extra_trees || old_config->max_delta_step != config->max_delta_step || old_config->path_smooth != config->path_smooth) { -#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) +#pragma omp parallel for schedule(static) for (int i = 0; i < cache_size_; ++i) { for (int j = 0; j < train_data->num_features(); ++j) { pool_[i][j].ResetFunc(); diff --git a/src/treelearner/gpu_tree_learner.cpp b/src/treelearner/gpu_tree_learner.cpp index 0817614483b2..294be28b6f86 100644 --- a/src/treelearner/gpu_tree_learner.cpp +++ b/src/treelearner/gpu_tree_learner.cpp @@ -191,7 +191,7 @@ void GPUTreeLearner::WaitAndGetHistograms(hist_t* histograms) { HistType* hist_outputs = reinterpret_cast(host_histogram_outputs_); // when the output is ready, the computation is done histograms_wait_obj_.wait(); - #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) + #pragma omp parallel for schedule(static) for (int i = 0; i < num_dense_feature_groups_; ++i) { if (!feature_masks_[i]) { continue; @@ -344,7 +344,7 @@ void GPUTreeLearner::AllocateGPUMemory() { // for data transfer time auto start_time = std::chrono::steady_clock::now(); // Now generate new data structure feature4, and copy data to the device - int nthreads = std::min(OMP_NUM_THREADS(), static_cast(dense_feature_group_map_.size()) / dword_features_); + int nthreads = std::min(omp_get_max_threads(), static_cast(dense_feature_group_map_.size()) / dword_features_); nthreads = std::max(nthreads, 1); std::vector host4_vecs(nthreads); std::vector host4_bufs(nthreads); @@ -359,7 +359,7 @@ void GPUTreeLearner::AllocateGPUMemory() { 0, num_data_ * sizeof(Feature4))); } // building Feature4 bundles; each thread handles dword_features_ features - #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) + #pragma omp parallel for schedule(static) for (int i = 0; i < static_cast(dense_feature_group_map_.size() / dword_features_); ++i) { int tid = omp_get_thread_num(); Feature4* host4 = host4_ptrs[tid]; @@ -451,7 +451,7 @@ void GPUTreeLearner::AllocateGPUMemory() { BinIterator* bin_iter = train_data_->FeatureGroupIterator(dense_dword_ind[i]); if (dynamic_cast*>(bin_iter) != 0) { DenseBinIterator iter = *static_cast*>(bin_iter); - #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) + #pragma omp parallel for schedule(static) for (int j = 0; j < num_data_; ++j) { host4[j].s[i >> 1] |= (uint8_t)((iter.RawGet(j) * device_bin_mults_[copied_feature4 * dword_features_ + i] + ((j+i) & (device_bin_mults_[copied_feature4 * dword_features_ + i] - 1))) @@ -464,14 +464,14 @@ void GPUTreeLearner::AllocateGPUMemory() { BinIterator* bin_iter = train_data_->FeatureGroupIterator(dense_dword_ind[i]); if (dynamic_cast*>(bin_iter) != 0) { DenseBinIterator iter = *static_cast*>(bin_iter); - #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) + #pragma omp parallel for schedule(static) for (int j = 0; j < num_data_; ++j) { host4[j].s[i] = (uint8_t)(iter.RawGet(j) * device_bin_mults_[copied_feature4 * dword_features_ + i] + ((j+i) & (device_bin_mults_[copied_feature4 * dword_features_ + i] - 1))); } } else if (dynamic_cast*>(bin_iter) != 0) { DenseBinIterator iter = *static_cast*>(bin_iter); - #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) + #pragma omp parallel for schedule(static) for (int j = 0; j < num_data_; ++j) { host4[j].s[i] = (uint8_t)(iter.RawGet(j) * device_bin_mults_[copied_feature4 * dword_features_ + i] + ((j+i) & (device_bin_mults_[copied_feature4 * dword_features_ + i] - 1))); @@ -485,7 +485,7 @@ void GPUTreeLearner::AllocateGPUMemory() { } // fill the leftover features if (dword_features_ == 8) { - #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) + #pragma omp parallel for schedule(static) for (int j = 0; j < num_data_; ++j) { for (int i = k; i < dword_features_; ++i) { // fill this empty feature with some "random" value @@ -493,7 +493,7 @@ void GPUTreeLearner::AllocateGPUMemory() { } } } else if (dword_features_ == 4) { - #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) + #pragma omp parallel for schedule(static) for (int j = 0; j < num_data_; ++j) { for (int i = k; i < dword_features_; ++i) { // fill this empty feature with some "random" value @@ -572,7 +572,7 @@ void GPUTreeLearner::BuildGPUKernels() { // currently we don't use constant memory int use_constants = 0; OMP_INIT_EX(); - #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(guided) + #pragma omp parallel for schedule(guided) for (int i = 0; i <= kMaxLogWorkgroupsPerFeature; ++i) { OMP_LOOP_EX_BEGIN(); boost::compute::program program; @@ -811,7 +811,7 @@ void GPUTreeLearner::BeforeTrain() { // transfer the indices to GPU indices_future_ = boost::compute::copy_async(indices, indices + cnt, device_data_indices_->begin(), queue_); if (!share_state_->is_constant_hessian) { - #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) + #pragma omp parallel for schedule(static) for (data_size_t i = 0; i < cnt; ++i) { ordered_hessians_[i] = hessians_[indices[i]]; } @@ -827,7 +827,7 @@ void GPUTreeLearner::BeforeTrain() { histogram_fulldata_kernels_[i].set_arg(6, const_hessian); } } - #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) + #pragma omp parallel for schedule(static) for (data_size_t i = 0; i < cnt; ++i) { ordered_gradients_[i] = gradients_[indices[i]]; } @@ -865,7 +865,7 @@ bool GPUTreeLearner::BeforeFindBestSplit(const Tree* tree, int left_leaf, int ri indices_future_ = boost::compute::copy_async(indices + begin, indices + end, device_data_indices_->begin(), queue_); if (!share_state_->is_constant_hessian) { - #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) + #pragma omp parallel for schedule(static) for (data_size_t i = begin; i < end; ++i) { ordered_hessians_[i - begin] = hessians_[indices[i]]; } @@ -873,7 +873,7 @@ bool GPUTreeLearner::BeforeFindBestSplit(const Tree* tree, int left_leaf, int ri hessians_future_ = queue_.enqueue_write_buffer_async(device_hessians_, 0, (end - begin) * sizeof(score_t), ptr_pinned_hessians_); } - #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) + #pragma omp parallel for schedule(static) for (data_size_t i = begin; i < end; ++i) { ordered_gradients_[i - begin] = gradients_[indices[i]]; } @@ -907,7 +907,7 @@ bool GPUTreeLearner::ConstructGPUHistogramsAsync( // generate and copy ordered_gradients if gradients is not null if (gradients != nullptr) { if (num_data != num_data_) { - #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) + #pragma omp parallel for schedule(static) for (data_size_t i = 0; i < num_data; ++i) { ordered_gradients[i] = gradients[data_indices[i]]; } @@ -919,7 +919,7 @@ bool GPUTreeLearner::ConstructGPUHistogramsAsync( // generate and copy ordered_hessians if Hessians is not null if (hessians != nullptr && !share_state_->is_constant_hessian) { if (num_data != num_data_) { - #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) + #pragma omp parallel for schedule(static) for (data_size_t i = 0; i < num_data; ++i) { ordered_hessians[i] = hessians[data_indices[i]]; } @@ -930,7 +930,7 @@ bool GPUTreeLearner::ConstructGPUHistogramsAsync( } // converted indices in is_feature_used to feature-group indices std::vector is_feature_group_used(num_feature_groups_, 0); - #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static, 1024) if (num_features_ >= 2048) + #pragma omp parallel for schedule(static, 1024) if (num_features_ >= 2048) for (int i = 0; i < num_features_; ++i) { if (is_feature_used[i]) { is_feature_group_used[train_data_->Feature2Group(i)] = 1; @@ -938,7 +938,7 @@ bool GPUTreeLearner::ConstructGPUHistogramsAsync( } // construct the feature masks for dense feature-groups int used_dense_feature_groups = 0; - #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static, 1024) reduction(+:used_dense_feature_groups) if (num_dense_feature_groups_ >= 2048) + #pragma omp parallel for schedule(static, 1024) reduction(+:used_dense_feature_groups) if (num_dense_feature_groups_ >= 2048) for (int i = 0; i < num_dense_feature_groups_; ++i) { if (is_feature_group_used[dense_feature_group_map_[i]]) { feature_masks_[i] = 1; @@ -973,7 +973,7 @@ bool GPUTreeLearner::ConstructGPUHistogramsAsync( void GPUTreeLearner::ConstructHistograms(const std::vector& is_feature_used, bool use_subtract) { std::vector is_sparse_feature_used(num_features_, 0); std::vector is_dense_feature_used(num_features_, 0); - #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) + #pragma omp parallel for schedule(static) for (int feature_index = 0; feature_index < num_features_; ++feature_index) { if (!col_sampler_.is_feature_used_bytree()[feature_index]) continue; if (!is_feature_used[feature_index]) continue; diff --git a/src/treelearner/gradient_discretizer.cpp b/src/treelearner/gradient_discretizer.cpp index b7e2e814b74a..4c00f73ab12c 100644 --- a/src/treelearner/gradient_discretizer.cpp +++ b/src/treelearner/gradient_discretizer.cpp @@ -216,7 +216,7 @@ void GradientDiscretizer::RenewIntGradTreeOutput( data_size_t leaf_cnt = 0; const data_size_t* data_indices = data_partition->GetIndexOnLeaf(leaf_id, &leaf_cnt); double sum_gradient = 0.0f, sum_hessian = 0.0f; - #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) reduction(+:sum_gradient, sum_hessian) + #pragma omp parallel for schedule(static) reduction(+:sum_gradient, sum_hessian) for (data_size_t i = 0; i < leaf_cnt; ++i) { const data_size_t index = data_indices[i]; const score_t grad = gradients[index]; @@ -242,7 +242,7 @@ void GradientDiscretizer::RenewIntGradTreeOutput( data_size_t leaf_cnt = 0; const data_size_t* data_indices = data_partition->GetIndexOnLeaf(leaf_id, &leaf_cnt); double sum_gradient = 0.0f, sum_hessian = 0.0f; - #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) reduction(+:sum_gradient, sum_hessian) + #pragma omp parallel for schedule(static) reduction(+:sum_gradient, sum_hessian) for (data_size_t i = 0; i < leaf_cnt; ++i) { const data_size_t index = data_indices[i]; const score_t grad = gradients[index]; diff --git a/src/treelearner/leaf_splits.hpp b/src/treelearner/leaf_splits.hpp index 2db71573e97e..fdf55693a0e9 100644 --- a/src/treelearner/leaf_splits.hpp +++ b/src/treelearner/leaf_splits.hpp @@ -95,7 +95,7 @@ class LeafSplits { data_indices_ = nullptr; double tmp_sum_gradients = 0.0f; double tmp_sum_hessians = 0.0f; -#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static, 512) reduction(+:tmp_sum_gradients, tmp_sum_hessians) if (num_data_in_leaf_ >= 1024 && !deterministic_) +#pragma omp parallel for schedule(static, 512) reduction(+:tmp_sum_gradients, tmp_sum_hessians) if (num_data_in_leaf_ >= 1024 && !deterministic_) for (data_size_t i = 0; i < num_data_in_leaf_; ++i) { tmp_sum_gradients += gradients[i]; tmp_sum_hessians += hessians[i]; @@ -120,7 +120,7 @@ class LeafSplits { double tmp_sum_hessians = 0.0f; const int16_t* packed_int_gradients_and_hessians = reinterpret_cast(int_gradients_and_hessians); int64_t tmp_sum_gradients_and_hessians = 0; -#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static, 512) reduction(+:tmp_sum_gradients, tmp_sum_hessians, tmp_sum_gradients_and_hessians) if (num_data_in_leaf_ >= 1024 && !deterministic_) +#pragma omp parallel for schedule(static, 512) reduction(+:tmp_sum_gradients, tmp_sum_hessians, tmp_sum_gradients_and_hessians) if (num_data_in_leaf_ >= 1024 && !deterministic_) for (data_size_t i = 0; i < num_data_in_leaf_; ++i) { tmp_sum_gradients += int_gradients_and_hessians[2 * i + 1] * grad_scale; tmp_sum_hessians += int_gradients_and_hessians[2 * i] * hess_scale; @@ -149,7 +149,7 @@ class LeafSplits { data_indices_ = data_partition->GetIndexOnLeaf(leaf, &num_data_in_leaf_); double tmp_sum_gradients = 0.0f; double tmp_sum_hessians = 0.0f; -#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static, 512) reduction(+:tmp_sum_gradients, tmp_sum_hessians) if (num_data_in_leaf_ >= 1024 && !deterministic_) +#pragma omp parallel for schedule(static, 512) reduction(+:tmp_sum_gradients, tmp_sum_hessians) if (num_data_in_leaf_ >= 1024 && !deterministic_) for (data_size_t i = 0; i < num_data_in_leaf_; ++i) { const data_size_t idx = data_indices_[i]; tmp_sum_gradients += gradients[idx]; @@ -177,7 +177,7 @@ class LeafSplits { double tmp_sum_hessians = 0.0f; const int16_t* packed_int_gradients_and_hessians = reinterpret_cast(int_gradients_and_hessians); int64_t tmp_sum_gradients_and_hessians = 0; -#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static, 512) reduction(+:tmp_sum_gradients, tmp_sum_hessians, tmp_sum_gradients_and_hessians) if (num_data_in_leaf_ >= 1024 && deterministic_) +#pragma omp parallel for schedule(static, 512) reduction(+:tmp_sum_gradients, tmp_sum_hessians, tmp_sum_gradients_and_hessians) if (num_data_in_leaf_ >= 1024 && deterministic_) for (data_size_t i = 0; i < num_data_in_leaf_; ++i) { const data_size_t idx = data_indices_[i]; tmp_sum_gradients += int_gradients_and_hessians[2 * idx + 1] * grad_scale; diff --git a/src/treelearner/linear_tree_learner.cpp b/src/treelearner/linear_tree_learner.cpp index c96bce64d644..071a6405709c 100644 --- a/src/treelearner/linear_tree_learner.cpp +++ b/src/treelearner/linear_tree_learner.cpp @@ -19,7 +19,7 @@ void LinearTreeLearner::InitLinear(const Dataset* train_data, const int max_leav leaf_map_ = std::vector(train_data->num_data(), -1); contains_nan_ = std::vector(train_data->num_features(), 0); // identify features containing nans -#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) +#pragma omp parallel for schedule(static) for (int feat = 0; feat < train_data->num_features(); ++feat) { auto bin_mapper = train_data_->FeatureBinMapper(feat); if (bin_mapper->bin_type() == BinType::NumericalBin) { @@ -52,7 +52,7 @@ void LinearTreeLearner::InitLinear(const Dataset* train_data, const int max_leav } XTHX_by_thread_.clear(); XTg_by_thread_.clear(); - int max_threads = OMP_NUM_THREADS(); + int max_threads = omp_get_max_threads(); for (int i = 0; i < max_threads; ++i) { XTHX_by_thread_.push_back(XTHX_); XTg_by_thread_.push_back(XTg_); @@ -159,7 +159,7 @@ void LinearTreeLearner::GetLeafMap(Tree* tree) const { std::fill(leaf_map_.begin(), leaf_map_.end(), -1); // map data to leaf number const data_size_t* ind = data_partition_->indices(); -#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(dynamic) +#pragma omp parallel for schedule(dynamic) for (int i = 0; i < tree->num_leaves(); ++i) { data_size_t idx = data_partition_->leaf_begin(i); for (int j = 0; j < data_partition_->leaf_count(i); ++j) { @@ -224,7 +224,7 @@ void LinearTreeLearner::CalculateLinear(Tree* tree, bool is_refit, const score_t } } // clear the coefficient matrices -#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) +#pragma omp parallel for schedule(static) for (int i = 0; i < num_threads; ++i) { for (int leaf_num = 0; leaf_num < num_leaves; ++leaf_num) { size_t num_feat = leaf_features[leaf_num].size(); @@ -232,7 +232,7 @@ void LinearTreeLearner::CalculateLinear(Tree* tree, bool is_refit, const score_t std::fill(XTg_by_thread_[i][leaf_num].begin(), XTg_by_thread_[i][leaf_num].begin() + num_feat + 1, 0.0f); } } -#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) +#pragma omp parallel for schedule(static) for (int leaf_num = 0; leaf_num < num_leaves; ++leaf_num) { size_t num_feat = leaf_features[leaf_num].size(); std::fill(XTHX_[leaf_num].begin(), XTHX_[leaf_num].begin() + (num_feat + 1) * (num_feat + 2) / 2, 0.0f); @@ -245,7 +245,7 @@ void LinearTreeLearner::CalculateLinear(Tree* tree, bool is_refit, const score_t } } OMP_INIT_EX(); -#pragma omp parallel num_threads(OMP_NUM_THREADS()) if (num_data_ > 1024) +#pragma omp parallel if (num_data_ > 1024) { std::vector curr_row(max_num_features + 1); int tid = omp_get_thread_num(); @@ -296,7 +296,7 @@ void LinearTreeLearner::CalculateLinear(Tree* tree, bool is_refit, const score_t auto total_nonzero = std::vector(tree->num_leaves()); // aggregate results from different threads for (int tid = 0; tid < num_threads; ++tid) { -#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) +#pragma omp parallel for schedule(static) for (int leaf_num = 0; leaf_num < num_leaves; ++leaf_num) { size_t num_feat = leaf_features[leaf_num].size(); for (size_t j = 0; j < (num_feat + 1) * (num_feat + 2) / 2; ++j) { @@ -318,7 +318,7 @@ void LinearTreeLearner::CalculateLinear(Tree* tree, bool is_refit, const score_t double shrinkage = tree->shrinkage(); double decay_rate = config_->refit_decay_rate; // copy into eigen matrices and solve -#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) +#pragma omp parallel for schedule(static) for (int leaf_num = 0; leaf_num < num_leaves; ++leaf_num) { if (total_nonzero[leaf_num] < static_cast(leaf_features[leaf_num].size()) + 1) { if (is_refit) { diff --git a/src/treelearner/linear_tree_learner.h b/src/treelearner/linear_tree_learner.h index 770b18d133e5..7e7b1f6d2a8d 100644 --- a/src/treelearner/linear_tree_learner.h +++ b/src/treelearner/linear_tree_learner.h @@ -75,7 +75,7 @@ class LinearTreeLearner: public SerialTreeLearner { leaf_num_features[leaf_num] = static_cast(feat_ptr[leaf_num].size()); } OMP_INIT_EX(); -#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) if (num_data_ > 1024) +#pragma omp parallel for schedule(static) if (num_data_ > 1024) for (int i = 0; i < num_data_; ++i) { OMP_LOOP_EX_BEGIN(); int leaf_num = leaf_map_[i]; diff --git a/src/treelearner/serial_tree_learner.cpp b/src/treelearner/serial_tree_learner.cpp index d5c5cc59ef3a..37d9a2a50713 100644 --- a/src/treelearner/serial_tree_learner.cpp +++ b/src/treelearner/serial_tree_learner.cpp @@ -242,7 +242,7 @@ Tree* SerialTreeLearner::FitByExistingTree(const Tree* old_tree, const score_t* auto tree = std::unique_ptr(new Tree(*old_tree)); CHECK_GE(data_partition_->num_leaves(), tree->num_leaves()); OMP_INIT_EX(); - #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) + #pragma omp parallel for schedule(static) for (int i = 0; i < tree->num_leaves(); ++i) { OMP_LOOP_EX_BEGIN(); data_size_t cnt_leaf_data = 0; @@ -379,7 +379,7 @@ void SerialTreeLearner::FindBestSplits(const Tree* tree) { void SerialTreeLearner::FindBestSplits(const Tree* tree, const std::set* force_features) { std::vector is_feature_used(num_features_, 0); - #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static, 256) if (num_features_ >= 512) + #pragma omp parallel for schedule(static, 256) if (num_features_ >= 512) for (int feature_index = 0; feature_index < num_features_; ++feature_index) { if (!col_sampler_.is_feature_used_bytree()[feature_index] && (force_features == nullptr || force_features->find(feature_index) == force_features->end())) continue; if (parent_leaf_histogram_array_ != nullptr @@ -922,7 +922,7 @@ void SerialTreeLearner::RenewTreeOutput(Tree* tree, const ObjectiveFunction* obj } std::vector n_nozeroworker_perleaf(tree->num_leaves(), 1); int num_machines = Network::num_machines(); - #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) + #pragma omp parallel for schedule(static) for (int i = 0; i < tree->num_leaves(); ++i) { const double output = static_cast(tree->LeafOutput(i)); data_size_t cnt_leaf_data = 0; diff --git a/src/treelearner/serial_tree_learner.h b/src/treelearner/serial_tree_learner.h index 43ff6a4b1e13..93e0787a90cf 100644 --- a/src/treelearner/serial_tree_learner.h +++ b/src/treelearner/serial_tree_learner.h @@ -103,7 +103,7 @@ class SerialTreeLearner: public TreeLearner { if (tree->num_leaves() <= 1) { return; } -#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static, 1) +#pragma omp parallel for schedule(static, 1) for (int i = 0; i < tree->num_leaves(); ++i) { double output = static_cast(tree->LeafOutput(i)); data_size_t cnt_leaf_data = 0; diff --git a/src/treelearner/voting_parallel_tree_learner.cpp b/src/treelearner/voting_parallel_tree_learner.cpp index b88db5a7ba28..0eee032839cd 100644 --- a/src/treelearner/voting_parallel_tree_learner.cpp +++ b/src/treelearner/voting_parallel_tree_learner.cpp @@ -243,7 +243,7 @@ template void VotingParallelTreeLearner::FindBestSplits(const Tree* tree) { // use local data to find local best splits std::vector is_feature_used(this->num_features_, 0); -#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) +#pragma omp parallel for schedule(static) for (int feature_index = 0; feature_index < this->num_features_; ++feature_index) { if (!this->col_sampler_.is_feature_used_bytree()[feature_index]) continue; if (this->parent_leaf_histogram_array_ != nullptr @@ -265,7 +265,7 @@ void VotingParallelTreeLearner::FindBestSplits(const Tree* tree) // clear histogram buffer before synchronizing // otherwise histogram contents from the previous iteration will be sent OMP_INIT_EX(); - #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) + #pragma omp parallel for schedule(static) for (int feature_index = 0; feature_index < this->num_features_; ++feature_index) { OMP_LOOP_EX_BEGIN(); if (!is_feature_used[feature_index]) { continue; } @@ -285,7 +285,7 @@ void VotingParallelTreeLearner::FindBestSplits(const Tree* tree) const data_size_t local_data_on_larger_leaf = this->data_partition_->leaf_count(larger_leaf_index); if (local_data_on_larger_leaf <= 0) { OMP_INIT_EX(); - #pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) + #pragma omp parallel for schedule(static) for (int feature_index = 0; feature_index < this->num_features_; ++feature_index) { OMP_LOOP_EX_BEGIN(); if (!is_feature_used[feature_index]) { continue; } @@ -307,7 +307,7 @@ void VotingParallelTreeLearner::FindBestSplits(const Tree* tree) double larger_leaf_parent_output = this->GetParentOutput(tree, this->larger_leaf_splits_.get()); OMP_INIT_EX(); // find splits -#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) +#pragma omp parallel for schedule(static) for (int feature_index = 0; feature_index < this->num_features_; ++feature_index) { OMP_LOOP_EX_BEGIN(); if (!is_feature_used[feature_index]) { continue; } diff --git a/src/utils/openmp_wrapper.cpp b/src/utils/openmp_wrapper.cpp deleted file mode 100644 index fb6e661eb67c..000000000000 --- a/src/utils/openmp_wrapper.cpp +++ /dev/null @@ -1,44 +0,0 @@ -/*! - * Copyright (c) 2023 Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See LICENSE file in the project root for license information. - */ -#include - -int LGBM_MAX_NUM_THREADS = -1; - -int LGBM_DEFAULT_NUM_THREADS = -1; - -#ifdef _OPENMP - -#include - -int OMP_NUM_THREADS() { - int default_num_threads = 1; - - if (LGBM_DEFAULT_NUM_THREADS > 0) { - // if LightGBM-specific default has been set, ignore OpenMP-global config - default_num_threads = LGBM_DEFAULT_NUM_THREADS; - } else { - // otherwise, default to OpenMP-global config - #pragma omp single - { default_num_threads = omp_get_max_threads(); } - } - - // ensure that if LGBM_SetMaxThreads() was ever called, LightGBM doesn't - // use more than that many threads - if (LGBM_MAX_NUM_THREADS > 0 && default_num_threads > LGBM_MAX_NUM_THREADS) { - return LGBM_MAX_NUM_THREADS; - } - - return default_num_threads; -} - -void OMP_SET_NUM_THREADS(int num_threads) { - if (num_threads <= 0) { - LGBM_DEFAULT_NUM_THREADS = -1; - } else { - LGBM_DEFAULT_NUM_THREADS = num_threads; - } -} - -#endif // _OPENMP diff --git a/tests/c_api_test/test_.py b/tests/c_api_test/test_.py index 6cfec1c445fc..4bb76e4aba19 100644 --- a/tests/c_api_test/test_.py +++ b/tests/c_api_test/test_.py @@ -247,36 +247,3 @@ def test_booster(): c_str(''), c_str('preb.txt')) LIB.LGBM_BoosterFree(booster2) - - -def test_max_thread_control(): - # at initialization, should be -1 - num_threads = ctypes.c_int(0) - ret = LIB.LGBM_GetMaxThreads( - ctypes.byref(num_threads) - ) - assert ret == 0 - assert num_threads.value == -1 - - # updating that value through the C API should work - ret = LIB.LGBM_SetMaxThreads( - ctypes.c_int(6) - ) - assert ret == 0 - - ret = LIB.LGBM_GetMaxThreads( - ctypes.byref(num_threads) - ) - assert ret == 0 - assert num_threads.value == 6 - - # resetting to any negative number should set it to -1 - ret = LIB.LGBM_SetMaxThreads( - ctypes.c_int(-123) - ) - assert ret == 0 - ret = LIB.LGBM_GetMaxThreads( - ctypes.byref(num_threads) - ) - assert ret == 0 - assert num_threads.value == -1 diff --git a/tests/cpp_tests/test_arrow.cpp b/tests/cpp_tests/test_arrow.cpp deleted file mode 100644 index e975b6ba374b..000000000000 --- a/tests/cpp_tests/test_arrow.cpp +++ /dev/null @@ -1,212 +0,0 @@ -/*! - * Copyright (c) 2023 Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See LICENSE file in the project root for license information. - * - * Author: Oliver Borchert - */ - -#include -#include - -#include -#include - -using LightGBM::ArrowChunkedArray; -using LightGBM::ArrowTable; - -class ArrowChunkedArrayTest : public testing::Test { - protected: - void SetUp() override {} - - ArrowArray created_nested_array(const std::vector& arrays) { - ArrowArray arr; - arr.buffers = nullptr; - arr.children = (ArrowArray**)arrays.data(); // NOLINT - arr.dictionary = nullptr; - arr.length = arrays[0]->length; - arr.n_buffers = 0; - arr.n_children = arrays.size(); - arr.null_count = 0; - arr.offset = 0; - arr.private_data = nullptr; - arr.release = nullptr; - return arr; - } - - template - ArrowArray create_primitive_array(const std::vector& values, - int64_t offset = 0, - std::vector null_indices = {}) { - // NOTE: Arrow arrays have 64-bit alignment but we can safely ignore this in tests - // 1) Create validity bitmap - char* validity = nullptr; - if (!null_indices.empty()) { - auto num_bytes = (values.size() + 7) / 8; - validity = static_cast(calloc(num_bytes, sizeof(char))); - memset(validity, 0xff, num_bytes * sizeof(char)); - for (size_t i = 0; i < values.size(); ++i) { - if (std::find(null_indices.begin(), null_indices.end(), i) != null_indices.end()) { - validity[i / 8] &= ~(1 << (i % 8)); - } - } - } - - // 2) Create buffers - const void** buffers = (const void**)malloc(sizeof(void*) * 2); - buffers[0] = validity; - buffers[1] = values.data() + offset; - - // Create arrow array - ArrowArray arr; - arr.buffers = buffers; - arr.children = nullptr; - arr.dictionary = nullptr; - arr.length = values.size() - offset; - arr.null_count = 0; - arr.offset = 0; - arr.private_data = nullptr; - arr.release = [](ArrowArray* arr) { - if (arr->buffers[0] != nullptr) - free((void*)(arr->buffers[0])); // NOLINT - free((void*)(arr->buffers)); // NOLINT - }; - return arr; - } - - ArrowSchema create_nested_schema(const std::vector& arrays) { - ArrowSchema schema; - schema.format = "+s"; - schema.name = nullptr; - schema.metadata = nullptr; - schema.flags = 0; - schema.n_children = arrays.size(); - schema.children = (ArrowSchema**)arrays.data(); // NOLINT - schema.dictionary = nullptr; - schema.private_data = nullptr; - schema.release = nullptr; - return schema; - } - - template - ArrowSchema create_primitive_schema() { - std::logic_error("not implemented"); - } - - template <> - ArrowSchema create_primitive_schema() { - ArrowSchema schema; - schema.format = "f"; - schema.name = nullptr; - schema.metadata = nullptr; - schema.flags = 0; - schema.n_children = 0; - schema.children = nullptr; - schema.dictionary = nullptr; - schema.private_data = nullptr; - schema.release = nullptr; - return schema; - } -}; - -TEST_F(ArrowChunkedArrayTest, GetLength) { - std::vector dat1 = {1, 2}; - auto arr1 = create_primitive_array(dat1); - - ArrowChunkedArray ca1(1, &arr1, nullptr); - ASSERT_EQ(ca1.get_length(), 2); - - std::vector dat2 = {3, 4, 5, 6}; - auto arr2 = create_primitive_array(dat2); - ArrowArray arrs[2] = {arr1, arr2}; - ArrowChunkedArray ca2(2, arrs, nullptr); - ASSERT_EQ(ca2.get_length(), 6); - - arr1.release(&arr1); - arr2.release(&arr2); -} - -TEST_F(ArrowChunkedArrayTest, GetColumns) { - std::vector dat1 = {1, 2, 3}; - auto arr1 = create_primitive_array(dat1); - std::vector dat2 = {4, 5, 6}; - auto arr2 = create_primitive_array(dat2); - std::vector arrs = {&arr1, &arr2}; - auto arr = created_nested_array(arrs); - - auto schema1 = create_primitive_schema(); - auto schema2 = create_primitive_schema(); - std::vector schemas = {&schema1, &schema2}; - auto schema = create_nested_schema(schemas); - - ArrowTable table(1, &arr, &schema); - ASSERT_EQ(table.get_num_rows(), 3); - ASSERT_EQ(table.get_num_columns(), 2); - - auto ca1 = table.get_column(0); - ASSERT_EQ(ca1.get_length(), 3); - ASSERT_EQ(*ca1.begin(), 1); - - auto ca2 = table.get_column(1); - ASSERT_EQ(ca2.get_length(), 3); - ASSERT_EQ(*ca2.begin(), 4); - - arr1.release(&arr1); - arr2.release(&arr2); -} - -TEST_F(ArrowChunkedArrayTest, IteratorArithmetic) { - std::vector dat1 = {1, 2}; - auto arr1 = create_primitive_array(dat1); - std::vector dat2 = {3, 4, 5, 6}; - auto arr2 = create_primitive_array(dat2); - std::vector dat3 = {7}; - auto arr3 = create_primitive_array(dat3); - auto schema = create_primitive_schema(); - - ArrowArray arrs[3] = {arr1, arr2, arr3}; - ArrowChunkedArray ca(3, arrs, &schema); - - // Arithmetic - auto it = ca.begin(); - ASSERT_EQ(*it, 1); - ++it; - ASSERT_EQ(*it, 2); - ++it; - ASSERT_EQ(*it, 3); - it += 2; - ASSERT_EQ(*it, 5); - it += 2; - ASSERT_EQ(*it, 7); - --it; - ASSERT_EQ(*it, 6); - - // Subscripts - ASSERT_EQ(it[0], 1); - ASSERT_EQ(it[1], 2); - ASSERT_EQ(it[2], 3); - ASSERT_EQ(it[6], 7); - - // End - auto end = ca.end(); - ASSERT_EQ(end - it, 2); - ASSERT_EQ(end - ca.begin(), 7); - - arr1.release(&arr1); - arr2.release(&arr2); - arr2.release(&arr3); -} - -TEST_F(ArrowChunkedArrayTest, OffsetAndValidity) { - std::vector dat = {0, 1, 2, 3, 4, 5, 6}; - auto arr = create_primitive_array(dat, 2, {0, 1}); - auto schema = create_primitive_schema(); - ArrowChunkedArray ca(1, &arr, &schema); - - auto it = ca.begin(); - ASSERT_TRUE(std::isnan(*it)); - ASSERT_TRUE(std::isnan(*(++it))); - ASSERT_EQ(it[2], 4); - ASSERT_EQ(it[4], 6); - - arr.release(&arr); -} diff --git a/tests/cpp_tests/test_byte_buffer.cpp b/tests/cpp_tests/test_byte_buffer.cpp index a6c2b660b983..98df661ddd31 100644 --- a/tests/cpp_tests/test_byte_buffer.cpp +++ b/tests/cpp_tests/test_byte_buffer.cpp @@ -30,7 +30,7 @@ TEST(ByteBuffer, JustWorks) { EXPECT_EQ(cumulativeSize, buffer->GetSize()); int16_t serializedInt16 = 0; char* int16Ptr = reinterpret_cast(&serializedInt16); - for (unsigned int i = 0; i < sizeof(int16_t); i++) { + for (int i = 0; i < sizeof(int16_t); i++) { int16Ptr[i] = buffer->GetAt(cumulativeSize - (sizeof(int16_t) - i)); } EXPECT_EQ(int16Val, serializedInt16); @@ -41,7 +41,7 @@ TEST(ByteBuffer, JustWorks) { EXPECT_EQ(cumulativeSize, buffer->GetSize()); int64_t serializedInt64 = 0; char* int64Ptr = reinterpret_cast(&serializedInt64); - for (unsigned int i = 0; i < sizeof(int64_t); i++) { + for (int i = 0; i < sizeof(int64_t); i++) { int64Ptr[i] = buffer->GetAt(cumulativeSize - (sizeof(int64_t) - i)); } EXPECT_EQ(int64Val, serializedInt64); @@ -52,7 +52,7 @@ TEST(ByteBuffer, JustWorks) { EXPECT_EQ(cumulativeSize, buffer->GetSize()); double serializedDouble = 0; char* doublePtr = reinterpret_cast(&serializedDouble); - for (unsigned int i = 0; i < sizeof(double); i++) { + for (int i = 0; i < sizeof(double); i++) { doublePtr[i] = buffer->GetAt(cumulativeSize - (sizeof(double) - i)); } EXPECT_EQ(doubleVal, serializedDouble); diff --git a/tests/cpp_tests/testutils.cpp b/tests/cpp_tests/testutils.cpp index 84acfe5b98a3..f0b3e1c1f206 100644 --- a/tests/cpp_tests/testutils.cpp +++ b/tests/cpp_tests/testutils.cpp @@ -265,7 +265,7 @@ namespace LightGBM { groups_ptr, thread_count, t); - threads.push_back(std::move(th)); + threads.push_back(move(th)); } for (auto& t : threads) t.join(); diff --git a/tests/python_package_test/test_arrow.py b/tests/python_package_test/test_arrow.py deleted file mode 100644 index 593c03d8c7ef..000000000000 --- a/tests/python_package_test/test_arrow.py +++ /dev/null @@ -1,387 +0,0 @@ -# coding: utf-8 -import filecmp -from typing import Any, Dict, Optional - -import numpy as np -import pyarrow as pa -import pytest - -import lightgbm as lgb - -from .utils import np_assert_array_equal - -# ----------------------------------------------------------------------------------------------- # -# UTILITIES # -# ----------------------------------------------------------------------------------------------- # - -_INTEGER_TYPES = [ - pa.int8(), - pa.int16(), - pa.int32(), - pa.int64(), - pa.uint8(), - pa.uint16(), - pa.uint32(), - pa.uint64(), -] -_FLOAT_TYPES = [ - pa.float32(), - pa.float64(), -] - - -def generate_simple_arrow_table(empty_chunks: bool = False) -> pa.Table: - c: list[list[int]] = [[]] if empty_chunks else [] - columns = [ - pa.chunked_array(c + [[1, 2, 3]] + c + [[4, 5]] + c, type=pa.uint8()), - pa.chunked_array(c + [[1, 2, 3]] + c + [[4, 5]] + c, type=pa.int8()), - pa.chunked_array(c + [[1, 2, 3]] + c + [[4, 5]] + c, type=pa.uint16()), - pa.chunked_array(c + [[1, 2, 3]] + c + [[4, 5]] + c, type=pa.int16()), - pa.chunked_array(c + [[1, 2, 3]] + c + [[4, 5]] + c, type=pa.uint32()), - pa.chunked_array(c + [[1, 2, 3]] + c + [[4, 5]] + c, type=pa.int32()), - pa.chunked_array(c + [[1, 2, 3]] + c + [[4, 5]] + c, type=pa.uint64()), - pa.chunked_array(c + [[1, 2, 3]] + c + [[4, 5]] + c, type=pa.int64()), - pa.chunked_array(c + [[1, 2, 3]] + c + [[4, 5]] + c, type=pa.float32()), - pa.chunked_array(c + [[1, 2, 3]] + c + [[4, 5]] + c, type=pa.float64()), - ] - return pa.Table.from_arrays(columns, names=[f"col_{i}" for i in range(len(columns))]) - - -def generate_nullable_arrow_table() -> pa.Table: - columns = [ - pa.chunked_array([[1, None, 3, 4, 5]], type=pa.float32()), - pa.chunked_array([[None, 2, 3, 4, 5]], type=pa.float32()), - pa.chunked_array([[1, 2, 3, 4, None]], type=pa.float32()), - pa.chunked_array([[None, None, None, None, None]], type=pa.float32()), - ] - return pa.Table.from_arrays(columns, names=[f"col_{i}" for i in range(len(columns))]) - - -def generate_dummy_arrow_table() -> pa.Table: - col1 = pa.chunked_array([[1, 2, 3], [4, 5]], type=pa.uint8()) - col2 = pa.chunked_array([[0.5, 0.6], [0.1, 0.8, 1.5]], type=pa.float32()) - return pa.Table.from_arrays([col1, col2], names=["a", "b"]) - - -def generate_random_arrow_table( - num_columns: int, - num_datapoints: int, - seed: int, - generate_nulls: bool = True, - values: Optional[np.ndarray] = None, -) -> pa.Table: - columns = [ - generate_random_arrow_array( - num_datapoints, seed + i, generate_nulls=generate_nulls, values=values - ) - for i in range(num_columns) - ] - names = [f"col_{i}" for i in range(num_columns)] - return pa.Table.from_arrays(columns, names=names) - - -def generate_random_arrow_array( - num_datapoints: int, - seed: int, - generate_nulls: bool = True, - values: Optional[np.ndarray] = None, -) -> pa.ChunkedArray: - generator = np.random.default_rng(seed) - data = ( - generator.standard_normal(num_datapoints) - if values is None - else generator.choice(values, size=num_datapoints, replace=True) - ) - - # Set random nulls - if generate_nulls: - indices = generator.choice(len(data), size=num_datapoints // 10) - data[indices] = None - - # Split data into <=2 random chunks - split_points = np.sort(generator.choice(np.arange(1, num_datapoints), 2, replace=False)) - split_points = np.concatenate([[0], split_points, [num_datapoints]]) - chunks = [data[split_points[i] : split_points[i + 1]] for i in range(len(split_points) - 1)] - chunks = [chunk for chunk in chunks if len(chunk) > 0] - - # Turn chunks into array - return pa.chunked_array([data], type=pa.float32()) - - -def dummy_dataset_params() -> Dict[str, Any]: - return { - "min_data_in_bin": 1, - "min_data_in_leaf": 1, - } - - -# ----------------------------------------------------------------------------------------------- # -# UNIT TESTS # -# ----------------------------------------------------------------------------------------------- # - -# ------------------------------------------- DATASET ------------------------------------------- # - - -@pytest.mark.parametrize( - ("arrow_table_fn", "dataset_params"), - [ # Use lambda functions here to minimize memory consumption - (lambda: generate_simple_arrow_table(), dummy_dataset_params()), - (lambda: generate_simple_arrow_table(empty_chunks=True), dummy_dataset_params()), - (lambda: generate_dummy_arrow_table(), dummy_dataset_params()), - (lambda: generate_nullable_arrow_table(), dummy_dataset_params()), - (lambda: generate_random_arrow_table(3, 1000, 42), {}), - (lambda: generate_random_arrow_table(100, 10000, 43), {}), - ], -) -def test_dataset_construct_fuzzy(tmp_path, arrow_table_fn, dataset_params): - arrow_table = arrow_table_fn() - - arrow_dataset = lgb.Dataset(arrow_table, params=dataset_params) - arrow_dataset.construct() - - pandas_dataset = lgb.Dataset(arrow_table.to_pandas(), params=dataset_params) - pandas_dataset.construct() - - arrow_dataset._dump_text(tmp_path / "arrow.txt") - pandas_dataset._dump_text(tmp_path / "pandas.txt") - assert filecmp.cmp(tmp_path / "arrow.txt", tmp_path / "pandas.txt") - - -# -------------------------------------------- FIELDS ------------------------------------------- # - - -def test_dataset_construct_fields_fuzzy(): - arrow_table = generate_random_arrow_table(3, 1000, 42) - arrow_labels = generate_random_arrow_array(1000, 42, generate_nulls=False) - arrow_weights = generate_random_arrow_array(1000, 42, generate_nulls=False) - arrow_groups = pa.chunked_array([[300, 400, 50], [250]], type=pa.int32()) - - arrow_dataset = lgb.Dataset( - arrow_table, label=arrow_labels, weight=arrow_weights, group=arrow_groups - ) - arrow_dataset.construct() - - pandas_dataset = lgb.Dataset( - arrow_table.to_pandas(), - label=arrow_labels.to_numpy(), - weight=arrow_weights.to_numpy(), - group=arrow_groups.to_numpy(), - ) - pandas_dataset.construct() - - # Check for equality - for field in ("label", "weight", "group"): - np_assert_array_equal( - arrow_dataset.get_field(field), pandas_dataset.get_field(field), strict=True - ) - np_assert_array_equal(arrow_dataset.get_label(), pandas_dataset.get_label(), strict=True) - np_assert_array_equal(arrow_dataset.get_weight(), pandas_dataset.get_weight(), strict=True) - - -# -------------------------------------------- LABELS ------------------------------------------- # - - -@pytest.mark.parametrize( - ["array_type", "label_data"], - [ - (pa.array, [0, 1, 0, 0, 1]), - (pa.chunked_array, [[0], [1, 0, 0, 1]]), - (pa.chunked_array, [[], [0], [1, 0, 0, 1]]), - (pa.chunked_array, [[0], [], [1, 0], [], [], [0, 1], []]), - ], -) -@pytest.mark.parametrize("arrow_type", _INTEGER_TYPES + _FLOAT_TYPES) -def test_dataset_construct_labels(array_type, label_data, arrow_type): - data = generate_dummy_arrow_table() - labels = array_type(label_data, type=arrow_type) - dataset = lgb.Dataset(data, label=labels, params=dummy_dataset_params()) - dataset.construct() - - expected = np.array([0, 1, 0, 0, 1], dtype=np.float32) - np_assert_array_equal(expected, dataset.get_label(), strict=True) - - -# ------------------------------------------- WEIGHTS ------------------------------------------- # - - -def test_dataset_construct_weights_none(): - data = generate_dummy_arrow_table() - weight = pa.array([1, 1, 1, 1, 1]) - dataset = lgb.Dataset(data, weight=weight, params=dummy_dataset_params()) - dataset.construct() - assert dataset.get_weight() is None - assert dataset.get_field("weight") is None - - -@pytest.mark.parametrize( - ["array_type", "weight_data"], - [ - (pa.array, [3, 0.7, 1.5, 0.5, 0.1]), - (pa.chunked_array, [[3], [0.7, 1.5, 0.5, 0.1]]), - (pa.chunked_array, [[], [3], [0.7, 1.5, 0.5, 0.1]]), - (pa.chunked_array, [[3], [0.7], [], [], [1.5, 0.5, 0.1], []]), - ], -) -@pytest.mark.parametrize("arrow_type", _FLOAT_TYPES) -def test_dataset_construct_weights(array_type, weight_data, arrow_type): - data = generate_dummy_arrow_table() - weights = array_type(weight_data, type=arrow_type) - dataset = lgb.Dataset(data, weight=weights, params=dummy_dataset_params()) - dataset.construct() - - expected = np.array([3, 0.7, 1.5, 0.5, 0.1], dtype=np.float32) - np_assert_array_equal(expected, dataset.get_weight(), strict=True) - - -# -------------------------------------------- GROUPS ------------------------------------------- # - - -@pytest.mark.parametrize( - ["array_type", "group_data"], - [ - (pa.array, [2, 3]), - (pa.chunked_array, [[2], [3]]), - (pa.chunked_array, [[], [2, 3]]), - (pa.chunked_array, [[2], [], [3], []]), - ], -) -@pytest.mark.parametrize("arrow_type", _INTEGER_TYPES) -def test_dataset_construct_groups(array_type, group_data, arrow_type): - data = generate_dummy_arrow_table() - groups = array_type(group_data, type=arrow_type) - dataset = lgb.Dataset(data, group=groups, params=dummy_dataset_params()) - dataset.construct() - - expected = np.array([0, 2, 5], dtype=np.int32) - np_assert_array_equal(expected, dataset.get_field("group"), strict=True) - - -# ----------------------------------------- INIT SCORES ----------------------------------------- # - - -@pytest.mark.parametrize( - ["array_type", "init_score_data"], - [ - (pa.array, [0, 1, 2, 3, 3]), - (pa.chunked_array, [[0, 1, 2], [3, 3]]), - (pa.chunked_array, [[], [0, 1, 2], [3, 3]]), - (pa.chunked_array, [[0, 1], [], [], [2], [3, 3], []]), - ], -) -@pytest.mark.parametrize("arrow_type", _INTEGER_TYPES + _FLOAT_TYPES) -def test_dataset_construct_init_scores_array( - array_type: Any, init_score_data: Any, arrow_type: Any -): - data = generate_dummy_arrow_table() - init_scores = array_type(init_score_data, type=arrow_type) - dataset = lgb.Dataset(data, init_score=init_scores, params=dummy_dataset_params()) - dataset.construct() - - expected = np.array([0, 1, 2, 3, 3], dtype=np.float64) - np_assert_array_equal(expected, dataset.get_init_score(), strict=True) - - -def test_dataset_construct_init_scores_table(): - data = generate_dummy_arrow_table() - init_scores = pa.Table.from_arrays( - [ - generate_random_arrow_array(5, seed=1, generate_nulls=False), - generate_random_arrow_array(5, seed=2, generate_nulls=False), - generate_random_arrow_array(5, seed=3, generate_nulls=False), - ], - names=["a", "b", "c"], - ) - dataset = lgb.Dataset(data, init_score=init_scores, params=dummy_dataset_params()) - dataset.construct() - - actual = dataset.get_init_score() - expected = init_scores.to_pandas().to_numpy().astype(np.float64) - np_assert_array_equal(expected, actual, strict=True) - - -# ------------------------------------------ PREDICTION ----------------------------------------- # - - -def assert_equal_predict_arrow_pandas(booster: lgb.Booster, data: pa.Table): - p_arrow = booster.predict(data) - p_pandas = booster.predict(data.to_pandas()) - np_assert_array_equal(p_arrow, p_pandas, strict=True) - - p_raw_arrow = booster.predict(data, raw_score=True) - p_raw_pandas = booster.predict(data.to_pandas(), raw_score=True) - np_assert_array_equal(p_raw_arrow, p_raw_pandas, strict=True) - - p_leaf_arrow = booster.predict(data, pred_leaf=True) - p_leaf_pandas = booster.predict(data.to_pandas(), pred_leaf=True) - np_assert_array_equal(p_leaf_arrow, p_leaf_pandas, strict=True) - - p_pred_contrib_arrow = booster.predict(data, pred_contrib=True) - p_pred_contrib_pandas = booster.predict(data.to_pandas(), pred_contrib=True) - np_assert_array_equal(p_pred_contrib_arrow, p_pred_contrib_pandas, strict=True) - - p_first_iter_arrow = booster.predict(data, start_iteration=0, num_iteration=1, raw_score=True) - p_first_iter_pandas = booster.predict( - data.to_pandas(), start_iteration=0, num_iteration=1, raw_score=True - ) - np_assert_array_equal(p_first_iter_arrow, p_first_iter_pandas, strict=True) - - -def test_predict_regression(): - data = generate_random_arrow_table(10, 10000, 42) - dataset = lgb.Dataset( - data, - label=generate_random_arrow_array(10000, 43, generate_nulls=False), - params=dummy_dataset_params(), - ) - booster = lgb.train( - {"objective": "regression", "num_leaves": 7}, - dataset, - num_boost_round=5, - ) - assert_equal_predict_arrow_pandas(booster, data) - - -def test_predict_binary_classification(): - data = generate_random_arrow_table(10, 10000, 42) - dataset = lgb.Dataset( - data, - label=generate_random_arrow_array(10000, 43, generate_nulls=False, values=np.arange(2)), - params=dummy_dataset_params(), - ) - booster = lgb.train( - {"objective": "binary", "num_leaves": 7}, - dataset, - num_boost_round=5, - ) - assert_equal_predict_arrow_pandas(booster, data) - - -def test_predict_multiclass_classification(): - data = generate_random_arrow_table(10, 10000, 42) - dataset = lgb.Dataset( - data, - label=generate_random_arrow_array(10000, 43, generate_nulls=False, values=np.arange(5)), - params=dummy_dataset_params(), - ) - booster = lgb.train( - {"objective": "multiclass", "num_leaves": 7, "num_class": 5}, - dataset, - num_boost_round=5, - ) - assert_equal_predict_arrow_pandas(booster, data) - - -def test_predict_ranking(): - data = generate_random_arrow_table(10, 10000, 42) - dataset = lgb.Dataset( - data, - label=generate_random_arrow_array(10000, 43, generate_nulls=False, values=np.arange(4)), - group=np.array([1000, 2000, 3000, 4000]), - params=dummy_dataset_params(), - ) - booster = lgb.train( - {"objective": "lambdarank", "num_leaves": 7}, - dataset, - num_boost_round=5, - ) - assert_equal_predict_arrow_pandas(booster, data) diff --git a/tests/python_package_test/test_basic.py b/tests/python_package_test/test_basic.py index b8ef43e41397..7f8980c271f7 100644 --- a/tests/python_package_test/test_basic.py +++ b/tests/python_package_test/test_basic.py @@ -15,7 +15,7 @@ import lightgbm as lgb from lightgbm.compat import PANDAS_INSTALLED, pd_DataFrame, pd_Series -from .utils import dummy_obj, load_breast_cancer, mse_obj, np_assert_array_equal +from .utils import dummy_obj, load_breast_cancer, mse_obj def test_basic(tmp_path): @@ -499,94 +499,6 @@ def check_asserts(data): check_asserts(lgb_data) -def test_dataset_construction_overwrites_user_provided_metadata_fields(): - - X = np.array([[1.0, 2.0], [3.0, 4.0]]) - - position = np.array([0.0, 1.0], dtype=np.float32) - if getenv('TASK', '') == 'cuda': - position = None - - dtrain = lgb.Dataset( - X, - params={ - "min_data_in_bin": 1, - "min_data_in_leaf": 1, - "verbosity": -1 - }, - group=[1, 1], - init_score=[0.312, 0.708], - label=[1, 2], - position=position, - weight=[0.5, 1.5], - ) - - # unconstructed, get_* methods should return whatever was provided - assert dtrain.group == [1, 1] - assert dtrain.get_group() == [1, 1] - assert dtrain.init_score == [0.312, 0.708] - assert dtrain.get_init_score() == [0.312, 0.708] - assert dtrain.label == [1, 2] - assert dtrain.get_label() == [1, 2] - if getenv('TASK', '') != 'cuda': - np_assert_array_equal( - dtrain.position, - np.array([0.0, 1.0], dtype=np.float32), - strict=True - ) - np_assert_array_equal( - dtrain.get_position(), - np.array([0.0, 1.0], dtype=np.float32), - strict=True - ) - assert dtrain.weight == [0.5, 1.5] - assert dtrain.get_weight() == [0.5, 1.5] - - # before construction, get_field() should raise an exception - for field_name in ["group", "init_score", "label", "position", "weight"]: - with pytest.raises(Exception, match=f"Cannot get {field_name} before construct Dataset"): - dtrain.get_field(field_name) - - # constructed, get_* methods should return numpy arrays, even when the provided - # input was a list of floats or ints - dtrain.construct() - expected_group = np.array([1, 1], dtype=np.int32) - np_assert_array_equal(dtrain.group, expected_group, strict=True) - np_assert_array_equal(dtrain.get_group(), expected_group, strict=True) - # get_field("group") returns a numpy array with boundaries, instead of size - np_assert_array_equal( - dtrain.get_field("group"), - np.array([0, 1, 2], dtype=np.int32), - strict=True - ) - - expected_init_score = np.array([0.312, 0.708],) - np_assert_array_equal(dtrain.init_score, expected_init_score, strict=True) - np_assert_array_equal(dtrain.get_init_score(), expected_init_score, strict=True) - np_assert_array_equal(dtrain.get_field("init_score"), expected_init_score, strict=True) - - expected_label = np.array([1, 2], dtype=np.float32) - np_assert_array_equal(dtrain.label, expected_label, strict=True) - np_assert_array_equal(dtrain.get_label(), expected_label, strict=True) - np_assert_array_equal(dtrain.get_field("label"), expected_label, strict=True) - - if getenv('TASK', '') != 'cuda': - expected_position = np.array([0.0, 1.0], dtype=np.float32) - np_assert_array_equal(dtrain.position, expected_position, strict=True) - np_assert_array_equal(dtrain.get_position(), expected_position, strict=True) - # NOTE: "position" is converted to int32 on the C++ side - np_assert_array_equal( - dtrain.get_field("position"), - np.array([0.0, 1.0], dtype=np.int32), - strict=True - ) - - expected_weight = np.array([0.5, 1.5], dtype=np.float32) - np_assert_array_equal(dtrain.weight, expected_weight, strict=True) - np_assert_array_equal(dtrain.get_weight(), expected_weight, strict=True) - np_assert_array_equal(dtrain.get_field("weight"), expected_weight, strict=True) - - def test_choose_param_value(): original_params = { @@ -822,34 +734,21 @@ def test_no_copy_when_single_float_dtype_dataframe(dtype, feature_name): @pytest.mark.parametrize('feature_name', [['x1'], [42], 'auto']) -@pytest.mark.parametrize('categories', ['seen', 'unseen']) -def test_categorical_code_conversion_doesnt_modify_original_data(feature_name, categories): +def test_categorical_code_conversion_doesnt_modify_original_data(feature_name): pd = pytest.importorskip('pandas') X = np.random.choice(['a', 'b'], 100).reshape(-1, 1) column_name = 'a' if feature_name == 'auto' else feature_name[0] df = pd.DataFrame(X.copy(), columns=[column_name], dtype='category') - if categories == 'seen': - pandas_categorical = [['a', 'b']] - else: - pandas_categorical = [['a']] data = lgb.basic._data_from_pandas( data=df, feature_name=feature_name, categorical_feature="auto", - pandas_categorical=pandas_categorical, + pandas_categorical=None )[0] # check that the original data wasn't modified np.testing.assert_equal(df[column_name], X[:, 0]) # check that the built data has the codes - if categories == 'seen': - # if all categories were seen during training we just take the codes - codes = df[column_name].cat.codes - else: - # if we only saw 'a' during training we just replace its code - # and leave the rest as nan - a_code = df[column_name].cat.categories.get_loc('a') - codes = np.where(df[column_name] == 'a', a_code, np.nan) - np.testing.assert_equal(codes, data[:, 0]) + np.testing.assert_equal(df[column_name].cat.codes, data[:, 0]) @pytest.mark.parametrize('min_data_in_bin', [2, 10]) diff --git a/tests/python_package_test/test_callback.py b/tests/python_package_test/test_callback.py index f93ca837f8b9..cb5dc707bf43 100644 --- a/tests/python_package_test/test_callback.py +++ b/tests/python_package_test/test_callback.py @@ -21,17 +21,6 @@ def test_early_stopping_callback_is_picklable(serializer): assert callback.stopping_rounds == rounds -def test_early_stopping_callback_rejects_invalid_stopping_rounds_with_informative_errors(): - with pytest.raises(ValueError, match="stopping_rounds should be an integer and greater than 0. got: 0"): - lgb.early_stopping(stopping_rounds=0) - - with pytest.raises(ValueError, match="stopping_rounds should be an integer and greater than 0. got: -1"): - lgb.early_stopping(stopping_rounds=-1) - - with pytest.raises(ValueError, match="stopping_rounds should be an integer and greater than 0. got: neverrrr"): - lgb.early_stopping(stopping_rounds="neverrrr") - - @pytest.mark.parametrize('serializer', SERIALIZERS) def test_log_evaluation_callback_is_picklable(serializer): periods = 42 diff --git a/tests/python_package_test/test_engine.py b/tests/python_package_test/test_engine.py index e355e5ab074a..b46526bcfaf6 100644 --- a/tests/python_package_test/test_engine.py +++ b/tests/python_package_test/test_engine.py @@ -755,7 +755,7 @@ def test_ranking_prediction_early_stopping(): # (in our example it is simply the ordering by some feature correlated with relevance, e.g., 34) # and clicks on that document (new_label=1) with some probability 'pclick' depending on its true relevance; # at each position the user may stop the traversal with some probability pstop. For the non-clicked documents, -# new_label=0. Thus the generated new labels are biased towards the baseline ranker. +# new_label=0. Thus the generated new labels are biased towards the baseline ranker. # The positions of the documents in the ranked lists produced by the baseline, are returned. def simulate_position_bias(file_dataset_in, file_query_in, file_dataset_out, baseline_feature): # a mapping of a document's true relevance (defined on a 5-grade scale) into the probability of clicking it @@ -772,7 +772,7 @@ def get_pclick(label): return 0.9 # an instantiation of a cascade model where the user stops with probability 0.2 after observing each document pstop = 0.2 - + f_dataset_in = open(file_dataset_in, 'r') f_dataset_out = open(file_dataset_out, 'w') random.seed(10) @@ -780,19 +780,19 @@ def get_pclick(label): for line in open(file_query_in): docs_num = int (line) lines = [] - index_values = [] + index_values = [] positions = [0] * docs_num for index in range(docs_num): features = f_dataset_in.readline().split() lines.append(features) val = 0.0 for feature_val in features: - feature_val_split = feature_val.split(":") + feature_val_split = feature_val.split(":") if int(feature_val_split[0]) == baseline_feature: val = float(feature_val_split[1]) index_values.append([index, val]) index_values.sort(key=lambda x: -x[1]) - stop = False + stop = False for pos in range(docs_num): index = index_values[pos][0] new_label = 0 @@ -800,7 +800,7 @@ def get_pclick(label): label = int(lines[index][0]) pclick = get_pclick(label) if random.random() < pclick: - new_label = 1 + new_label = 1 stop = random.random() < pstop lines[index][0] = str(new_label) positions[index] = pos @@ -843,7 +843,7 @@ def test_ranking_with_position_information_with_file(tmp_path): lgb_train = lgb.Dataset(str(tmp_path / 'rank.train'), params=params) lgb_valid = [lgb_train.create_valid(str(tmp_path / 'rank.test'))] gbm_unbiased_with_file = lgb.train(params, lgb_train, valid_sets = lgb_valid, num_boost_round=50) - + # the performance of the unbiased LambdaMART should outperform the plain LambdaMART on the dataset with position bias assert gbm_baseline.best_score['valid_0']['ndcg@3'] + 0.03 <= gbm_unbiased_with_file.best_score['valid_0']['ndcg@3'] @@ -853,7 +853,7 @@ def test_ranking_with_position_information_with_file(tmp_path): file.close() lgb_train = lgb.Dataset(str(tmp_path / 'rank.train'), params=params) lgb_valid = [lgb_train.create_valid(str(tmp_path / 'rank.test'))] - with pytest.raises(lgb.basic.LightGBMError, match=r"Positions size \(3006\) doesn't match data size"): + with pytest.raises(lgb.basic.LightGBMError, match="Positions size \(3006\) doesn't match data size"): lgb.train(params, lgb_train, valid_sets = lgb_valid, num_boost_round=50) @@ -1470,7 +1470,7 @@ def test_feature_name_with_non_ascii(): assert feature_names == gbm2.feature_name() -def test_parameters_are_loaded_from_model_file(tmp_path, capsys): +def test_parameters_are_loaded_from_model_file(tmp_path): X = np.hstack([np.random.rand(100, 1), np.random.randint(0, 5, (100, 2))]) y = np.random.rand(100) ds = lgb.Dataset(X, y) @@ -1487,18 +1487,8 @@ def test_parameters_are_loaded_from_model_file(tmp_path, capsys): 'num_threads': 1, } model_file = tmp_path / 'model.txt' - orig_bst = lgb.train(params, ds, num_boost_round=1, categorical_feature=[1, 2]) - orig_bst.save_model(model_file) - with model_file.open('rt') as f: - model_contents = f.readlines() - params_start = model_contents.index('parameters:\n') - model_contents.insert(params_start + 1, '[max_conflict_rate: 0]\n') - with model_file.open('wt') as f: - f.writelines(model_contents) + lgb.train(params, ds, num_boost_round=1, categorical_feature=[1, 2]).save_model(model_file) bst = lgb.Booster(model_file=model_file) - expected_msg = "[LightGBM] [Warning] Ignoring unrecognized parameter 'max_conflict_rate' found in model string." - stdout = capsys.readouterr().out - assert expected_msg in stdout set_params = {k: bst.params[k] for k in params.keys()} assert set_params == params assert bst.params['categorical_feature'] == [1, 2] @@ -1508,11 +1498,6 @@ def test_parameters_are_loaded_from_model_file(tmp_path, capsys): bst2 = lgb.Booster(params={'num_leaves': 7}, model_file=model_file) assert bst.params == bst2.params - # check inference isn't affected by unknown parameter - orig_preds = orig_bst.predict(X) - preds = bst.predict(X) - np.testing.assert_allclose(preds, orig_preds) - def test_save_load_copy_pickle(): def train_and_predict(init_model=None, return_model=False): @@ -4516,9 +4501,9 @@ def test_train_raises_informative_error_if_any_valid_sets_are_not_dataset_object def test_train_raises_informative_error_for_params_of_wrong_type(): X, y = make_synthetic_regression() - params = {"num_leaves": "too-many"} + params = {"early_stopping_round": "too-many"} dtrain = lgb.Dataset(X, label=y) - with pytest.raises(lgb.basic.LightGBMError, match="Parameter num_leaves should be of type int, got \"too-many\""): + with pytest.raises(lgb.basic.LightGBMError, match="Parameter early_stopping_round should be of type int, got \"too-many\""): lgb.train(params, dtrain) diff --git a/tests/python_package_test/test_sklearn.py b/tests/python_package_test/test_sklearn.py index f54fe5236d79..9c6036ad510c 100644 --- a/tests/python_package_test/test_sklearn.py +++ b/tests/python_package_test/test_sklearn.py @@ -640,12 +640,11 @@ def test_non_serializable_objects_in_callbacks(tmp_path): assert gbm.booster_.attr_set_inside_callback == 40 -@pytest.mark.parametrize("rng_constructor", [np.random.RandomState, np.random.default_rng]) -def test_random_state_object(rng_constructor): +def test_random_state_object(): X, y = load_iris(return_X_y=True) X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.1, random_state=42) - state1 = rng_constructor(123) - state2 = rng_constructor(123) + state1 = np.random.RandomState(123) + state2 = np.random.RandomState(123) clf1 = lgb.LGBMClassifier(n_estimators=10, subsample=0.5, subsample_freq=1, random_state=state1) clf2 = lgb.LGBMClassifier(n_estimators=10, subsample=0.5, subsample_freq=1, random_state=state2) # Test if random_state is properly stored diff --git a/tests/python_package_test/utils.py b/tests/python_package_test/utils.py index 7eae62b14369..df01e29852e7 100644 --- a/tests/python_package_test/utils.py +++ b/tests/python_package_test/utils.py @@ -1,7 +1,6 @@ # coding: utf-8 import pickle from functools import lru_cache -from inspect import getfullargspec import cloudpickle import joblib @@ -194,22 +193,3 @@ def pickle_and_unpickle_object(obj, serializer): serializer=serializer ) return obj_from_disk # noqa: RET504 - - -# doing this here, at import time, to ensure it only runs once_per import -# instead of once per assertion -_numpy_testing_supports_strict_kwarg = ( - "strict" in getfullargspec(np.testing.assert_array_equal).kwonlyargs -) - - -def np_assert_array_equal(*args, **kwargs): - """ - np.testing.assert_array_equal() only got the kwarg ``strict`` in June 2022: - https://github.com/numpy/numpy/pull/21595 - - This function is here for testing on older Python (and therefore ``numpy``) - """ - if not _numpy_testing_supports_strict_kwarg: - kwargs.pop("strict") - np.testing.assert_array_equal(*args, **kwargs) diff --git a/windows/LightGBM.vcxproj b/windows/LightGBM.vcxproj index b7848b4cab5a..96fe017e96b8 100644 --- a/windows/LightGBM.vcxproj +++ b/windows/LightGBM.vcxproj @@ -343,7 +343,6 @@ - diff --git a/windows/LightGBM.vcxproj.filters b/windows/LightGBM.vcxproj.filters index 7010926799b7..27b445893c0f 100644 --- a/windows/LightGBM.vcxproj.filters +++ b/windows/LightGBM.vcxproj.filters @@ -344,8 +344,5 @@ src\treelearner - - src\utils - \ No newline at end of file From d10ca549e96b2dbc1987bd884144455f8a39a4ff Mon Sep 17 00:00:00 2001 From: Claudio Salvatore Arcidiacono <22871978+ClaudioSalvatoreArcidiacono@users.noreply.github.com> Date: Tue, 16 Jan 2024 17:11:08 +0100 Subject: [PATCH 05/24] Add missing import --- python-package/lightgbm/sklearn.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python-package/lightgbm/sklearn.py b/python-package/lightgbm/sklearn.py index 3817189b9e1f..4d2cbb295b8f 100644 --- a/python-package/lightgbm/sklearn.py +++ b/python-package/lightgbm/sklearn.py @@ -16,7 +16,7 @@ _LGBMCheckClassificationTargets, _LGBMCheckSampleWeight, _LGBMCheckXY, _LGBMClassifierBase, _LGBMComputeSampleWeight, _LGBMCpuCount, _LGBMLabelEncoder, _LGBMModelBase, _LGBMRegressorBase, dt_DataTable, np_random_Generator, pd_DataFrame) -from .engine import train +from .engine import _make_n_folds, train __all__ = [ 'LGBMClassifier', From 3b8eb0af57ce0a49547d2d4a035afec30eca7233 Mon Sep 17 00:00:00 2001 From: Claudio Salvatore Arcidiacono <22871978+ClaudioSalvatoreArcidiacono@users.noreply.github.com> Date: Wed, 17 Jan 2024 09:38:20 +0100 Subject: [PATCH 06/24] Remove added extra new line --- python-package/lightgbm/sklearn.py | 1 - 1 file changed, 1 deletion(-) diff --git a/python-package/lightgbm/sklearn.py b/python-package/lightgbm/sklearn.py index 4d2cbb295b8f..c82499b8c232 100644 --- a/python-package/lightgbm/sklearn.py +++ b/python-package/lightgbm/sklearn.py @@ -717,7 +717,6 @@ def _process_params(self, stage: str) -> Dict[str, Any]: if isinstance(params['random_state'], np.random.RandomState): params['random_state'] = params['random_state'].randint(np.iinfo(np.int32).max) - elif isinstance(params['random_state'], np_random_Generator): params['random_state'] = int( params['random_state'].integers(np.iinfo(np.int32).max) From d54c96a1ab88aaa1b5415937aee8d84645dded72 Mon Sep 17 00:00:00 2001 From: Claudio Salvatore Arcidiacono <22871978+ClaudioSalvatoreArcidiacono@users.noreply.github.com> Date: Thu, 28 Nov 2024 16:41:52 +0100 Subject: [PATCH 07/24] Improve documentation, check default behavior of early stopping --- docs/Python-Intro.rst | 8 +++--- tests/python_package_test/test_sklearn.py | 35 +++++++++++++++++++++++ 2 files changed, 39 insertions(+), 4 deletions(-) diff --git a/docs/Python-Intro.rst b/docs/Python-Intro.rst index 7da6cdb504df..f984189ece84 100644 --- a/docs/Python-Intro.rst +++ b/docs/Python-Intro.rst @@ -244,10 +244,10 @@ This works with both metrics to minimize (L2, log loss, etc.) and to maximize (N Note that if you specify more than one evaluation metric, all of them will be used for early stopping. However, you can change this behavior and make LightGBM check only the first metric for early stopping by passing ``first_metric_only=True`` in ``early_stopping`` callback constructor. -In the scikit-learn API of lightgbm, early stopping is enabled by default if the training set passed during ``fit()`` has more than 10000 rows. -This behavior can be controlled by explicitly setting the parameter ``early_stopping`` to ``True`` or ``False`` in the class constructor. -When auto early stopping is enabled, a portion of the training data will be used as validation set. The amount of data to use for validation -is controlled by the parameter ``validation_fraction`` and defaults to 0.1. +In the scikit-learn API of lightgbm, early stopping can also be enabled by setting the parameter ``early_stopping`` to ``True`` +or by setting the parameter ``early_stopping_round`` to an integer greater than 0. +When early stopping is enabled and no validation set is provided, a portion of the training data will be used as validation set. +The amount of data to use for validation is controlled by the parameter ``validation_fraction`` and defaults to 0.1. Prediction ---------- diff --git a/tests/python_package_test/test_sklearn.py b/tests/python_package_test/test_sklearn.py index 091bd9a8d87f..0519264a9dfa 100644 --- a/tests/python_package_test/test_sklearn.py +++ b/tests/python_package_test/test_sklearn.py @@ -349,6 +349,41 @@ def test_auto_early_stopping_is_triggered_correctly(n_samples, expected_n_trees, assert gbm._Booster.num_trees() == n_estimators +def test_early_stopping_is_deactivated_by_default_regression(): + X, y = make_synthetic_regression(n_samples=100) + n_estimators = 5 + gbm = lgb.LGBMRegressor(n_estimators=n_estimators, random_state=42, verbose=-1) + gbm.fit(X, y) + + # Check that early stopping did not kick in + assert gbm._Booster.params.get("early_stopping_round") is None + assert gbm._Booster.num_trees() == n_estimators + + +def test_early_stopping_is_deactivated_by_default_classification(): + X, y = load_breast_cancer(return_X_y=True) + n_estimators = 5 + gbm = lgb.LGBMClassifier(n_estimators=n_estimators, random_state=42, verbose=-1) + gbm.fit(X, y) + + # Check that early stopping did not kick in + assert gbm._Booster.params.get("early_stopping_round") is None + assert gbm._Booster.num_trees() == n_estimators + + +def test_early_stopping_is_deactivated_by_default_lambdarank(): + rank_example_dir = Path(__file__).absolute().parents[2] / "examples" / "lambdarank" + X_train, y_train = load_svmlight_file(str(rank_example_dir / "rank.train")) + q_train = np.loadtxt(str(rank_example_dir / "rank.train.query")) + n_estimators = 5 + gbm = lgb.LGBMRanker(n_estimators=n_estimators, random_state=42, verbose=-1) + gbm.fit(X_train, y_train, group=q_train) # Assuming 10 samples in one group + + # Check that early stopping did not kick in + assert gbm._Booster.params.get("early_stopping_round") is None + assert gbm._Booster.num_trees() == n_estimators + + @pytest.mark.skipif( getenv("TASK", "") == "cuda", reason="Skip due to differences in implementation details of CUDA version" ) From 9c1c8b47230a6f3691909b6087a81c5e50b18e51 Mon Sep 17 00:00:00 2001 From: Claudio Salvatore Arcidiacono <22871978+ClaudioSalvatoreArcidiacono@users.noreply.github.com> Date: Thu, 28 Nov 2024 16:48:06 +0100 Subject: [PATCH 08/24] Solve python 3.8 compatibility issue --- python-package/lightgbm/engine.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python-package/lightgbm/engine.py b/python-package/lightgbm/engine.py index 6f62dacb8e63..1bba09ab3f0f 100644 --- a/python-package/lightgbm/engine.py +++ b/python-package/lightgbm/engine.py @@ -512,7 +512,7 @@ def _make_n_folds( seed: int, stratified: bool, shuffle: bool, -) -> Iterable[Tuple[np.ndarray[Any, Any], np.ndarray[Any, Any]]]: +) -> Iterable[Tuple[np.ndarray, np.ndarray]]: """Make a n-fold list of Booster from random indices.""" full_data = full_data.construct() num_data = full_data.num_data() From 724c7fe163c725b41dbbb724080c0bcf1d59858f Mon Sep 17 00:00:00 2001 From: Claudio Salvatore Arcidiacono <22871978+ClaudioSalvatoreArcidiacono@users.noreply.github.com> Date: Fri, 29 Nov 2024 09:37:56 +0100 Subject: [PATCH 09/24] Remove default to auto --- python-package/lightgbm/sklearn.py | 2 +- tests/python_package_test/test_sklearn.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/python-package/lightgbm/sklearn.py b/python-package/lightgbm/sklearn.py index 696fa3a2334d..90a620b391e2 100644 --- a/python-package/lightgbm/sklearn.py +++ b/python-package/lightgbm/sklearn.py @@ -828,7 +828,7 @@ def _process_params(self, stage: str) -> Dict[str, Any]: params = _choose_param_value( main_param_name="early_stopping_round", params=params, - default_value="auto", + default_value=None, ) if params["early_stopping_round"] == "auto": if hasattr(self, "_n_rows_train") and self._n_rows_train > 10_000: diff --git a/tests/python_package_test/test_sklearn.py b/tests/python_package_test/test_sklearn.py index 0519264a9dfa..e6c5423375c3 100644 --- a/tests/python_package_test/test_sklearn.py +++ b/tests/python_package_test/test_sklearn.py @@ -350,7 +350,7 @@ def test_auto_early_stopping_is_triggered_correctly(n_samples, expected_n_trees, def test_early_stopping_is_deactivated_by_default_regression(): - X, y = make_synthetic_regression(n_samples=100) + X, y = make_synthetic_regression(n_samples=10_001) n_estimators = 5 gbm = lgb.LGBMRegressor(n_estimators=n_estimators, random_state=42, verbose=-1) gbm.fit(X, y) From c957fce839a13d9cfa13f430eebb698cf00da94e Mon Sep 17 00:00:00 2001 From: Claudio Salvatore Arcidiacono <22871978+ClaudioSalvatoreArcidiacono@users.noreply.github.com> Date: Fri, 29 Nov 2024 09:53:35 +0100 Subject: [PATCH 10/24] Revert changes in fit top part --- python-package/lightgbm/sklearn.py | 53 +++++++++-------------- tests/python_package_test/test_sklearn.py | 4 +- 2 files changed, 23 insertions(+), 34 deletions(-) diff --git a/python-package/lightgbm/sklearn.py b/python-package/lightgbm/sklearn.py index 90a620b391e2..9d30fe0844fa 100644 --- a/python-package/lightgbm/sklearn.py +++ b/python-package/lightgbm/sklearn.py @@ -830,11 +830,6 @@ def _process_params(self, stage: str) -> Dict[str, Any]: params=params, default_value=None, ) - if params["early_stopping_round"] == "auto": - if hasattr(self, "_n_rows_train") and self._n_rows_train > 10_000: - params["early_stopping_round"] = 10 - else: - params["early_stopping_round"] = None if params["early_stopping_round"] is True: params["early_stopping_round"] = 10 @@ -915,33 +910,6 @@ def fit( init_model: Optional[Union[str, Path, Booster, "LGBMModel"]] = None, ) -> "LGBMModel": """Docstring is set after definition, using a template.""" - if not isinstance(X, (pd_DataFrame, dt_DataTable)): - _X, _y = _LGBMValidateData( - self, - X, - y, - reset=True, - # allow any input type (this validation is done further down, in lgb.Dataset()) - accept_sparse=True, - # do not raise an error if Inf of NaN values are found (LightGBM handles these internally) - ensure_all_finite=False, - # raise an error on 0-row and 1-row inputs - ensure_min_samples=2, - ) - if sample_weight is not None: - sample_weight = _LGBMCheckSampleWeight(sample_weight, _X) - else: - _X, _y = X, y - - # for other data types, setting n_features_in_ is handled by _LGBMValidateData() in the branch above - self.n_features_in_ = _X.shape[1] - - self._n_features = _X.shape[1] - # copy for consistency - self._n_features_in = self._n_features - - self._n_rows_train = _X.shape[0] - params = self._process_params(stage="fit") # Do not modify original args in fit function @@ -963,6 +931,27 @@ def fit( params["metric"] = [e for e in eval_metrics_builtin if e not in params["metric"]] + params["metric"] params["metric"] = [metric for metric in params["metric"] if metric is not None] + if not isinstance(X, (pd_DataFrame, dt_DataTable)): + _X, _y = _LGBMValidateData( + self, + X, + y, + reset=True, + # allow any input type (this validation is done further down, in lgb.Dataset()) + accept_sparse=True, + # do not raise an error if Inf of NaN values are found (LightGBM handles these internally) + ensure_all_finite=False, + # raise an error on 0-row and 1-row inputs + ensure_min_samples=2, + ) + if sample_weight is not None: + sample_weight = _LGBMCheckSampleWeight(sample_weight, _X) + else: + _X, _y = X, y + + # for other data types, setting n_features_in_ is handled by _LGBMValidateData() in the branch above + self.n_features_in_ = _X.shape[1] + if self._class_weight is None: self._class_weight = self.class_weight if self._class_weight is not None: diff --git a/tests/python_package_test/test_sklearn.py b/tests/python_package_test/test_sklearn.py index e6c5423375c3..b26636575a7b 100644 --- a/tests/python_package_test/test_sklearn.py +++ b/tests/python_package_test/test_sklearn.py @@ -327,7 +327,7 @@ def test_auto_early_stopping_categorical_features_set_during_fit(rng_fixed_seed) @pytest.mark.parametrize("n_samples, expected_n_trees", [(100, 25), (10_001, 92)]) -@pytest.mark.parametrize("early_stopping", [True, False, "auto", 10]) +@pytest.mark.parametrize("early_stopping", [True, False, 10, None]) def test_auto_early_stopping_is_triggered_correctly(n_samples, expected_n_trees, early_stopping): X, y = make_synthetic_regression(n_samples=n_samples) n_estimators = 100 @@ -338,7 +338,7 @@ def test_auto_early_stopping_is_triggered_correctly(n_samples, expected_n_trees, verbose=-1, ) gbm.fit(X, y) - if early_stopping == "auto" and n_samples > 10_000 or early_stopping in (True, 10): + if early_stopping in (True, 10): # Check that early stopping actually kicked in assert gbm._Booster.params["early_stopping_round"] == 10 assert gbm._Booster.num_trees() == expected_n_trees From 2d7da788b1ec5858c3b43b5993ee9dd8aebfed83 Mon Sep 17 00:00:00 2001 From: Claudio Salvatore Arcidiacono <22871978+ClaudioSalvatoreArcidiacono@users.noreply.github.com> Date: Fri, 29 Nov 2024 11:08:12 +0100 Subject: [PATCH 11/24] Make interface as similar as possible to sklearn --- python-package/lightgbm/sklearn.py | 25 +++++++------ tests/python_package_test/test_sklearn.py | 45 ++++++----------------- 2 files changed, 26 insertions(+), 44 deletions(-) diff --git a/python-package/lightgbm/sklearn.py b/python-package/lightgbm/sklearn.py index 9d30fe0844fa..a4a2a591c4c2 100644 --- a/python-package/lightgbm/sklearn.py +++ b/python-package/lightgbm/sklearn.py @@ -509,6 +509,8 @@ def __init__( random_state: Optional[Union[int, np.random.RandomState, np.random.Generator]] = None, n_jobs: Optional[int] = None, importance_type: str = "split", + early_stopping: bool = False, + n_iter_no_change: int = 10, validation_fraction: Optional[float] = 0.1, **kwargs, ): @@ -590,6 +592,11 @@ def __init__( The type of feature importance to be filled into ``feature_importances_``. If 'split', result contains numbers of times the feature is used in a model. If 'gain', result contains total gains of splits which use the feature. + early_stopping : bool, optional (default=False) + Whether to enable early stopping. If set to True, training will stop if the validation score does not improve + for a specified number of rounds (controlled by `n_iter_no_change`). + n_iter_no_change : int, optional (default=10) + The number of iterations with no improvement after which training will be stopped. validation_fraction : float or None, optional (default=0.1) Proportion of training data to set aside as validation data for early stopping. If None, early stopping is done on @@ -672,6 +679,8 @@ def __init__( self._n_features_in: int = -1 self._classes: Optional[np.ndarray] = None self._n_classes: int = -1 + self.early_stopping = early_stopping + self.n_iter_no_change = n_iter_no_change self.set_params(**kwargs) # scikit-learn 1.6 introduced an __sklearn__tags() method intended to replace _more_tags(). @@ -825,17 +834,11 @@ def _process_params(self, stage: str) -> Dict[str, Any]: elif isinstance(params["random_state"], np.random.Generator): params["random_state"] = int(params["random_state"].integers(np.iinfo(np.int32).max)) - params = _choose_param_value( - main_param_name="early_stopping_round", - params=params, - default_value=None, - ) - - if params["early_stopping_round"] is True: - params["early_stopping_round"] = 10 - elif params["early_stopping_round"] is False: + params.pop("early_stopping", False) + params.pop("n_iter_no_change", None) + params = _choose_param_value("early_stopping_round", params, self.n_iter_no_change) + if not self.early_stopping: params["early_stopping_round"] = None - if self._n_classes > 2: for alias in _ConfigAliases.get("num_class"): params.pop(alias, None) @@ -972,7 +975,7 @@ def fit( params=params, ) - if params["early_stopping_round"] is not None and eval_set is None: + if self.early_stopping and eval_set is None: if self.validation_fraction is not None: n_splits = max(int(np.ceil(1 / self.validation_fraction)), 2) stratified = isinstance(self, LGBMClassifier) diff --git a/tests/python_package_test/test_sklearn.py b/tests/python_package_test/test_sklearn.py index b26636575a7b..adcbf8e5a9f1 100644 --- a/tests/python_package_test/test_sklearn.py +++ b/tests/python_package_test/test_sklearn.py @@ -288,20 +288,22 @@ def test_auto_early_stopping_binary_classification(): assert gbm.best_iteration_ < n_estimators -def test_auto_early_stopping_setting_early_stopping_round(): - early_stopping_round = 15 - X, y = make_synthetic_regression(n_samples=100) - n_estimators = 50 - gbm = lgb.LGBMRegressor( +def test_auto_early_stopping_compatibility_with_histgradientboostingclassifier(): + X, y = load_breast_cancer(return_X_y=True) + n_estimators = 200 + n_iter_no_change = 5 + gbm = lgb.LGBMClassifier( n_estimators=n_estimators, random_state=42, - early_stopping=early_stopping_round, verbose=-1, + early_stopping=True, + num_leaves=5, + n_iter_no_change=n_iter_no_change ) gbm.fit(X, y) - assert gbm._Booster.params["early_stopping_round"] == early_stopping_round - assert gbm._Booster.num_trees() == 25 - assert gbm.best_iteration_ == 25 + assert gbm._Booster.params["early_stopping_round"] == n_iter_no_change + assert gbm._Booster.num_trees() < n_estimators + assert gbm.best_iteration_ < n_estimators def test_auto_early_stopping_categorical_features_set_during_fit(rng_fixed_seed): @@ -325,30 +327,6 @@ def test_auto_early_stopping_categorical_features_set_during_fit(rng_fixed_seed) assert gbm._Booster.num_trees() < 5 assert gbm.best_iteration_ < 5 - -@pytest.mark.parametrize("n_samples, expected_n_trees", [(100, 25), (10_001, 92)]) -@pytest.mark.parametrize("early_stopping", [True, False, 10, None]) -def test_auto_early_stopping_is_triggered_correctly(n_samples, expected_n_trees, early_stopping): - X, y = make_synthetic_regression(n_samples=n_samples) - n_estimators = 100 - gbm = lgb.LGBMRegressor( - n_estimators=n_estimators, - random_state=42, - early_stopping=early_stopping, - verbose=-1, - ) - gbm.fit(X, y) - if early_stopping in (True, 10): - # Check that early stopping actually kicked in - assert gbm._Booster.params["early_stopping_round"] == 10 - assert gbm._Booster.num_trees() == expected_n_trees - assert gbm.best_iteration_ == expected_n_trees - else: - # Check that early stopping did not kick in - assert gbm.best_iteration_ == 0 - assert gbm._Booster.num_trees() == n_estimators - - def test_early_stopping_is_deactivated_by_default_regression(): X, y = make_synthetic_regression(n_samples=10_001) n_estimators = 5 @@ -1295,6 +1273,7 @@ def fit_and_check(eval_set_names, metric_names, assumed_iteration, first_metric_ "verbose": -1, "seed": 123, "early_stopping_rounds": 5, + "early_stopping": True, } # early stop should be supported via global LightGBM parameter params_fit = {"X": X_train, "y": y_train} From 069a84ecd27fe9ac9c89f214b30bbd9a87dfc22d Mon Sep 17 00:00:00 2001 From: Claudio Salvatore Arcidiacono <22871978+ClaudioSalvatoreArcidiacono@users.noreply.github.com> Date: Fri, 29 Nov 2024 11:14:54 +0100 Subject: [PATCH 12/24] Add parameters to dask interface --- python-package/lightgbm/dask.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/python-package/lightgbm/dask.py b/python-package/lightgbm/dask.py index 5c61345e4a5a..0e98f3cdf914 100644 --- a/python-package/lightgbm/dask.py +++ b/python-package/lightgbm/dask.py @@ -1134,6 +1134,8 @@ def __init__( random_state: Optional[Union[int, np.random.RandomState, "np.random.Generator"]] = None, n_jobs: Optional[int] = None, importance_type: str = "split", + early_stopping: bool = False, + n_iter_no_change: int = 10, validation_fraction: Optional[float] = 0.1, client: Optional[Client] = None, **kwargs: Any, @@ -1338,6 +1340,8 @@ def __init__( random_state: Optional[Union[int, np.random.RandomState, "np.random.Generator"]] = None, n_jobs: Optional[int] = None, importance_type: str = "split", + early_stopping: bool = False, + n_iter_no_change: int = 10, validation_fraction: Optional[float] = 0.1, client: Optional[Client] = None, **kwargs: Any, @@ -1506,6 +1510,8 @@ def __init__( random_state: Optional[Union[int, np.random.RandomState, "np.random.Generator"]] = None, n_jobs: Optional[int] = None, importance_type: str = "split", + early_stopping: bool = False, + n_iter_no_change: int = 10, validation_fraction: Optional[float] = 0.1, client: Optional[Client] = None, **kwargs: Any, From c430ec18f1dc44d072db2f22a607cc5e7dac7562 Mon Sep 17 00:00:00 2001 From: Claudio Salvatore Arcidiacono <22871978+ClaudioSalvatoreArcidiacono@users.noreply.github.com> Date: Fri, 29 Nov 2024 11:28:12 +0100 Subject: [PATCH 13/24] Improve documentation --- docs/Python-Intro.rst | 1 - python-package/lightgbm/sklearn.py | 12 +++++++----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/docs/Python-Intro.rst b/docs/Python-Intro.rst index f984189ece84..8cbd0d42c7e8 100644 --- a/docs/Python-Intro.rst +++ b/docs/Python-Intro.rst @@ -245,7 +245,6 @@ Note that if you specify more than one evaluation metric, all of them will be us However, you can change this behavior and make LightGBM check only the first metric for early stopping by passing ``first_metric_only=True`` in ``early_stopping`` callback constructor. In the scikit-learn API of lightgbm, early stopping can also be enabled by setting the parameter ``early_stopping`` to ``True`` -or by setting the parameter ``early_stopping_round`` to an integer greater than 0. When early stopping is enabled and no validation set is provided, a portion of the training data will be used as validation set. The amount of data to use for validation is controlled by the parameter ``validation_fraction`` and defaults to 0.1. diff --git a/python-package/lightgbm/sklearn.py b/python-package/lightgbm/sklearn.py index a4a2a591c4c2..6d5420d16c0f 100644 --- a/python-package/lightgbm/sklearn.py +++ b/python-package/lightgbm/sklearn.py @@ -596,7 +596,8 @@ def __init__( Whether to enable early stopping. If set to True, training will stop if the validation score does not improve for a specified number of rounds (controlled by `n_iter_no_change`). n_iter_no_change : int, optional (default=10) - The number of iterations with no improvement after which training will be stopped. + If early stopping is enabled, this parameter specifies the number of iterations with no + improvement after which training will be stopped. validation_fraction : float or None, optional (default=0.1) Proportion of training data to set aside as validation data for early stopping. If None, early stopping is done on @@ -665,6 +666,8 @@ def __init__( self.random_state = random_state self.n_jobs = n_jobs self.importance_type = importance_type + self.early_stopping = early_stopping + self.n_iter_no_change = n_iter_no_change self.validation_fraction = validation_fraction self._Booster: Optional[Booster] = None self._evals_result: _EvalResultDict = {} @@ -679,8 +682,6 @@ def __init__( self._n_features_in: int = -1 self._classes: Optional[np.ndarray] = None self._n_classes: int = -1 - self.early_stopping = early_stopping - self.n_iter_no_change = n_iter_no_change self.set_params(**kwargs) # scikit-learn 1.6 introduced an __sklearn__tags() method intended to replace _more_tags(). @@ -828,17 +829,18 @@ def _process_params(self, stage: str) -> Dict[str, Any]: params.pop("n_estimators", None) params.pop("class_weight", None) params.pop("validation_fraction", None) + params.pop("early_stopping", None) + params.pop("n_iter_no_change", None) if isinstance(params["random_state"], np.random.RandomState): params["random_state"] = params["random_state"].randint(np.iinfo(np.int32).max) elif isinstance(params["random_state"], np.random.Generator): params["random_state"] = int(params["random_state"].integers(np.iinfo(np.int32).max)) - params.pop("early_stopping", False) - params.pop("n_iter_no_change", None) params = _choose_param_value("early_stopping_round", params, self.n_iter_no_change) if not self.early_stopping: params["early_stopping_round"] = None + if self._n_classes > 2: for alias in _ConfigAliases.get("num_class"): params.pop(alias, None) From 416323a1362c96f7cf2a7ead2ff1318f472cc89b Mon Sep 17 00:00:00 2001 From: Claudio Salvatore Arcidiacono <22871978+ClaudioSalvatoreArcidiacono@users.noreply.github.com> Date: Fri, 29 Nov 2024 11:31:11 +0100 Subject: [PATCH 14/24] Linting --- tests/python_package_test/test_sklearn.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/python_package_test/test_sklearn.py b/tests/python_package_test/test_sklearn.py index adcbf8e5a9f1..68f17c3240be 100644 --- a/tests/python_package_test/test_sklearn.py +++ b/tests/python_package_test/test_sklearn.py @@ -298,7 +298,7 @@ def test_auto_early_stopping_compatibility_with_histgradientboostingclassifier() verbose=-1, early_stopping=True, num_leaves=5, - n_iter_no_change=n_iter_no_change + n_iter_no_change=n_iter_no_change, ) gbm.fit(X, y) assert gbm._Booster.params["early_stopping_round"] == n_iter_no_change @@ -327,6 +327,7 @@ def test_auto_early_stopping_categorical_features_set_during_fit(rng_fixed_seed) assert gbm._Booster.num_trees() < 5 assert gbm.best_iteration_ < 5 + def test_early_stopping_is_deactivated_by_default_regression(): X, y = make_synthetic_regression(n_samples=10_001) n_estimators = 5 From 73562ff673585049b3df274c974737e49e17696e Mon Sep 17 00:00:00 2001 From: Claudio Salvatore Arcidiacono <22871978+ClaudioSalvatoreArcidiacono@users.noreply.github.com> Date: Fri, 29 Nov 2024 11:41:35 +0100 Subject: [PATCH 15/24] Check for exact value equal true for early stopping --- python-package/lightgbm/sklearn.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/python-package/lightgbm/sklearn.py b/python-package/lightgbm/sklearn.py index 6d5420d16c0f..6be06728940e 100644 --- a/python-package/lightgbm/sklearn.py +++ b/python-package/lightgbm/sklearn.py @@ -838,7 +838,7 @@ def _process_params(self, stage: str) -> Dict[str, Any]: params["random_state"] = int(params["random_state"].integers(np.iinfo(np.int32).max)) params = _choose_param_value("early_stopping_round", params, self.n_iter_no_change) - if not self.early_stopping: + if self.early_stopping is not True: params["early_stopping_round"] = None if self._n_classes > 2: @@ -977,7 +977,7 @@ def fit( params=params, ) - if self.early_stopping and eval_set is None: + if self.early_stopping is True and eval_set is None: if self.validation_fraction is not None: n_splits = max(int(np.ceil(1 / self.validation_fraction)), 2) stratified = isinstance(self, LGBMClassifier) From 9a32376856b85d8532923bb074455f41cf9e1157 Mon Sep 17 00:00:00 2001 From: Claudio Salvatore Arcidiacono <22871978+ClaudioSalvatoreArcidiacono@users.noreply.github.com> Date: Wed, 18 Dec 2024 11:47:05 +0100 Subject: [PATCH 16/24] Switch if/else conditions order in fit --- python-package/lightgbm/sklearn.py | 100 ++++++++++++++--------------- 1 file changed, 50 insertions(+), 50 deletions(-) diff --git a/python-package/lightgbm/sklearn.py b/python-package/lightgbm/sklearn.py index 083d6a1b7246..1f9c4850a747 100644 --- a/python-package/lightgbm/sklearn.py +++ b/python-package/lightgbm/sklearn.py @@ -981,7 +981,56 @@ def fit( params=params, ) - if self.early_stopping is True and eval_set is None: + valid_sets: List[Dataset] = [] + if eval_set is not None: + if isinstance(eval_set, tuple): + eval_set = [eval_set] + for i, valid_data in enumerate(eval_set): + # reduce cost for prediction training data + if valid_data[0] is X and valid_data[1] is y: + valid_set = train_set + else: + valid_weight = _extract_evaluation_meta_data( + collection=eval_sample_weight, + name="eval_sample_weight", + i=i, + ) + valid_class_weight = _extract_evaluation_meta_data( + collection=eval_class_weight, + name="eval_class_weight", + i=i, + ) + if valid_class_weight is not None: + if isinstance(valid_class_weight, dict) and self._class_map is not None: + valid_class_weight = {self._class_map[k]: v for k, v in valid_class_weight.items()} + valid_class_sample_weight = _LGBMComputeSampleWeight(valid_class_weight, valid_data[1]) + if valid_weight is None or len(valid_weight) == 0: + valid_weight = valid_class_sample_weight + else: + valid_weight = np.multiply(valid_weight, valid_class_sample_weight) + valid_init_score = _extract_evaluation_meta_data( + collection=eval_init_score, + name="eval_init_score", + i=i, + ) + valid_group = _extract_evaluation_meta_data( + collection=eval_group, + name="eval_group", + i=i, + ) + valid_set = Dataset( + data=valid_data[0], + label=valid_data[1], + weight=valid_weight, + group=valid_group, + init_score=valid_init_score, + categorical_feature="auto", + params=params, + ) + + valid_sets.append(valid_set) + + elif self.early_stopping is True: if self.validation_fraction is not None: n_splits = max(int(np.ceil(1 / self.validation_fraction)), 2) stratified = isinstance(self, LGBMClassifier) @@ -1001,55 +1050,6 @@ def fit( valid_set = train_set valid_set = valid_set.construct() valid_sets = [valid_set] - else: - valid_sets: List[Dataset] = [] - if eval_set is not None: - if isinstance(eval_set, tuple): - eval_set = [eval_set] - for i, valid_data in enumerate(eval_set): - # reduce cost for prediction training data - if valid_data[0] is X and valid_data[1] is y: - valid_set = train_set - else: - valid_weight = _extract_evaluation_meta_data( - collection=eval_sample_weight, - name="eval_sample_weight", - i=i, - ) - valid_class_weight = _extract_evaluation_meta_data( - collection=eval_class_weight, - name="eval_class_weight", - i=i, - ) - if valid_class_weight is not None: - if isinstance(valid_class_weight, dict) and self._class_map is not None: - valid_class_weight = {self._class_map[k]: v for k, v in valid_class_weight.items()} - valid_class_sample_weight = _LGBMComputeSampleWeight(valid_class_weight, valid_data[1]) - if valid_weight is None or len(valid_weight) == 0: - valid_weight = valid_class_sample_weight - else: - valid_weight = np.multiply(valid_weight, valid_class_sample_weight) - valid_init_score = _extract_evaluation_meta_data( - collection=eval_init_score, - name="eval_init_score", - i=i, - ) - valid_group = _extract_evaluation_meta_data( - collection=eval_group, - name="eval_group", - i=i, - ) - valid_set = Dataset( - data=valid_data[0], - label=valid_data[1], - weight=valid_weight, - group=valid_group, - init_score=valid_init_score, - categorical_feature="auto", - params=params, - ) - - valid_sets.append(valid_set) if isinstance(init_model, LGBMModel): init_model = init_model.booster_ From a61726f02ff6eb62e8d0d9c4c7de3953bb7edfca Mon Sep 17 00:00:00 2001 From: Claudio Salvatore Arcidiacono <22871978+ClaudioSalvatoreArcidiacono@users.noreply.github.com> Date: Wed, 18 Dec 2024 13:20:30 +0100 Subject: [PATCH 17/24] fix issues in engine.py --- python-package/lightgbm/engine.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python-package/lightgbm/engine.py b/python-package/lightgbm/engine.py index 5382afe9bf52..1bba09ab3f0f 100644 --- a/python-package/lightgbm/engine.py +++ b/python-package/lightgbm/engine.py @@ -765,7 +765,7 @@ def cv( train_set._update_params(params)._set_predictor(predictor) results = defaultdict(list) - cvbooster = _make_n_folds( + cvfolds = _make_n_folds( full_data=train_set, folds=folds, nfold=nfold, From 44316d7c1cad635b26a281456c7c9ac84d626882 Mon Sep 17 00:00:00 2001 From: Claudio Salvatore Arcidiacono <22871978+ClaudioSalvatoreArcidiacono@users.noreply.github.com> Date: Wed, 18 Dec 2024 13:57:18 +0100 Subject: [PATCH 18/24] make new early stopping parameters keyword-only --- python-package/lightgbm/dask.py | 9 ++++++--- python-package/lightgbm/sklearn.py | 3 ++- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/python-package/lightgbm/dask.py b/python-package/lightgbm/dask.py index c462f06f7801..43b4b1854967 100644 --- a/python-package/lightgbm/dask.py +++ b/python-package/lightgbm/dask.py @@ -1134,10 +1134,11 @@ def __init__( random_state: Optional[Union[int, np.random.RandomState, "np.random.Generator"]] = None, n_jobs: Optional[int] = None, importance_type: str = "split", + client: Optional[Client] = None, + *, early_stopping: bool = False, n_iter_no_change: int = 10, validation_fraction: Optional[float] = 0.1, - client: Optional[Client] = None, **kwargs: Any, ): """Docstring is inherited from the lightgbm.LGBMClassifier.__init__.""" @@ -1340,10 +1341,11 @@ def __init__( random_state: Optional[Union[int, np.random.RandomState, "np.random.Generator"]] = None, n_jobs: Optional[int] = None, importance_type: str = "split", + client: Optional[Client] = None, + *, early_stopping: bool = False, n_iter_no_change: int = 10, validation_fraction: Optional[float] = 0.1, - client: Optional[Client] = None, **kwargs: Any, ): """Docstring is inherited from the lightgbm.LGBMRegressor.__init__.""" @@ -1510,10 +1512,11 @@ def __init__( random_state: Optional[Union[int, np.random.RandomState, "np.random.Generator"]] = None, n_jobs: Optional[int] = None, importance_type: str = "split", + client: Optional[Client] = None, + *, early_stopping: bool = False, n_iter_no_change: int = 10, validation_fraction: Optional[float] = 0.1, - client: Optional[Client] = None, **kwargs: Any, ): """Docstring is inherited from the lightgbm.LGBMRanker.__init__.""" diff --git a/python-package/lightgbm/sklearn.py b/python-package/lightgbm/sklearn.py index 1f9c4850a747..8d7f0f71c206 100644 --- a/python-package/lightgbm/sklearn.py +++ b/python-package/lightgbm/sklearn.py @@ -507,10 +507,11 @@ def __init__( random_state: Optional[Union[int, np.random.RandomState, np.random.Generator]] = None, n_jobs: Optional[int] = None, importance_type: str = "split", + *, early_stopping: bool = False, n_iter_no_change: int = 10, validation_fraction: Optional[float] = 0.1, - **kwargs, + **kwargs: Any, ): r"""Construct a gradient boosting model. From 4cbfc843c2004b4b4f5280a2ebc28697e644e6e9 Mon Sep 17 00:00:00 2001 From: Claudio Salvatore Arcidiacono <22871978+ClaudioSalvatoreArcidiacono@users.noreply.github.com> Date: Wed, 18 Dec 2024 16:02:18 +0100 Subject: [PATCH 19/24] Remove n_iter_no_change parameter --- python-package/lightgbm/dask.py | 3 -- python-package/lightgbm/sklearn.py | 48 +++++++++++++++-------- tests/python_package_test/test_sklearn.py | 1 - 3 files changed, 32 insertions(+), 20 deletions(-) diff --git a/python-package/lightgbm/dask.py b/python-package/lightgbm/dask.py index 43b4b1854967..cc73ef9e751a 100644 --- a/python-package/lightgbm/dask.py +++ b/python-package/lightgbm/dask.py @@ -1137,7 +1137,6 @@ def __init__( client: Optional[Client] = None, *, early_stopping: bool = False, - n_iter_no_change: int = 10, validation_fraction: Optional[float] = 0.1, **kwargs: Any, ): @@ -1344,7 +1343,6 @@ def __init__( client: Optional[Client] = None, *, early_stopping: bool = False, - n_iter_no_change: int = 10, validation_fraction: Optional[float] = 0.1, **kwargs: Any, ): @@ -1515,7 +1513,6 @@ def __init__( client: Optional[Client] = None, *, early_stopping: bool = False, - n_iter_no_change: int = 10, validation_fraction: Optional[float] = 0.1, **kwargs: Any, ): diff --git a/python-package/lightgbm/sklearn.py b/python-package/lightgbm/sklearn.py index 8d7f0f71c206..ff65bf657c5c 100644 --- a/python-package/lightgbm/sklearn.py +++ b/python-package/lightgbm/sklearn.py @@ -508,8 +508,7 @@ def __init__( n_jobs: Optional[int] = None, importance_type: str = "split", *, - early_stopping: bool = False, - n_iter_no_change: int = 10, + early_stopping: Union[bool, int] = False, validation_fraction: Optional[float] = 0.1, **kwargs: Any, ): @@ -591,12 +590,15 @@ def __init__( The type of feature importance to be filled into ``feature_importances_``. If 'split', result contains numbers of times the feature is used in a model. If 'gain', result contains total gains of splits which use the feature. - early_stopping : bool, optional (default=False) - Whether to enable early stopping. If set to True, training will stop if the validation score does not improve - for a specified number of rounds (controlled by `n_iter_no_change`). - n_iter_no_change : int, optional (default=10) - If early stopping is enabled, this parameter specifies the number of iterations with no - improvement after which training will be stopped. + early_stopping : bool, optional (default=False) Whether to enable scikit-learn-style early + stopping. If set to ``True` and no other validation set is passed to ``fit()``, a new + validation set will be created by randomly sampling ``validation_fraction`` rows from + the training data ``X`` passed to ``fit()``. Training will stop if the validation score + does not improve for a specific number of rounds (controlled by ``n_iter_no_change``). + This parameter is here for compatibility with ``scikit-learn``'s + ``HistGradientBoosting`` estimators. it does not affect other ``lightgbm``-specific + early stopping mechanisms, like passing the ``lgb.early_stopping`` callback and + validation sets to the ``eval_set`` argument of `fit()`. validation_fraction : float or None, optional (default=0.1) Proportion of training data to set aside as validation data for early stopping. If None, early stopping is done on @@ -666,7 +668,6 @@ def __init__( self.n_jobs = n_jobs self.importance_type = importance_type self.early_stopping = early_stopping - self.n_iter_no_change = n_iter_no_change self.validation_fraction = validation_fraction self._Booster: Optional[Booster] = None self._evals_result: _EvalResultDict = {} @@ -834,18 +835,12 @@ def _process_params(self, stage: str) -> Dict[str, Any]: params.pop("n_estimators", None) params.pop("class_weight", None) params.pop("validation_fraction", None) - params.pop("early_stopping", None) - params.pop("n_iter_no_change", None) if isinstance(params["random_state"], np.random.RandomState): params["random_state"] = params["random_state"].randint(np.iinfo(np.int32).max) elif isinstance(params["random_state"], np.random.Generator): params["random_state"] = int(params["random_state"].integers(np.iinfo(np.int32).max)) - params = _choose_param_value("early_stopping_round", params, self.n_iter_no_change) - if self.early_stopping is not True: - params["early_stopping_round"] = None - if self._n_classes > 2: for alias in _ConfigAliases.get("num_class"): params.pop(alias, None) @@ -878,7 +873,28 @@ def _process_params(self, stage: str) -> Dict[str, Any]: params = _choose_param_value("num_threads", params, self.n_jobs) params["num_threads"] = self._process_n_jobs(params["num_threads"]) - return params + if not isinstance(self.early_stopping, bool) and isinstance(self.early_stopping, int): + _log_warning( + f"Found 'early_stopping={self.early_stopping}' passed through keyword arguments. " + "Future versions of 'lightgbm' will not allow this, as scikit-learn expects keyword argument " + "'early_stopping' to be a boolean indicating whether or not to perform early stopping with " + "a randomly-sampled validation set. To set the number of early stopping rounds, and suppress " + f"this warning, pass early_stopping_rounds={self.early_stopping} instead." + ) + params = _choose_param_value( + main_param_name="early_stopping_round", params=params, default_value=self.early_stopping + ) + + params.pop("early_stopping", None) + + if isinstance(self.early_stopping, bool) and self.early_stopping is True: + default_early_stopping_round = 10 + else: + default_early_stopping_round = None + + return _choose_param_value( + main_param_name="early_stopping_round", params=params, default_value=default_early_stopping_round + ) def _process_n_jobs(self, n_jobs: Optional[int]) -> int: """Convert special values of n_jobs to their actual values according to the formulas that apply. diff --git a/tests/python_package_test/test_sklearn.py b/tests/python_package_test/test_sklearn.py index 4366ba2c9b37..cfbb0e3d9f68 100644 --- a/tests/python_package_test/test_sklearn.py +++ b/tests/python_package_test/test_sklearn.py @@ -1274,7 +1274,6 @@ def fit_and_check(eval_set_names, metric_names, assumed_iteration, first_metric_ "verbose": -1, "seed": 123, "early_stopping_rounds": 5, - "early_stopping": True, } # early stop should be supported via global LightGBM parameter params_fit = {"X": X_train, "y": y_train} From 93acf6aa247058ad551dece7d582fc06c4022ab4 Mon Sep 17 00:00:00 2001 From: Claudio Salvatore Arcidiacono <22871978+ClaudioSalvatoreArcidiacono@users.noreply.github.com> Date: Wed, 18 Dec 2024 16:18:11 +0100 Subject: [PATCH 20/24] Address comments in tests --- python-package/lightgbm/sklearn.py | 1 - tests/python_package_test/test_sklearn.py | 3 +-- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/python-package/lightgbm/sklearn.py b/python-package/lightgbm/sklearn.py index ff65bf657c5c..796bde3717aa 100644 --- a/python-package/lightgbm/sklearn.py +++ b/python-package/lightgbm/sklearn.py @@ -840,7 +840,6 @@ def _process_params(self, stage: str) -> Dict[str, Any]: params["random_state"] = params["random_state"].randint(np.iinfo(np.int32).max) elif isinstance(params["random_state"], np.random.Generator): params["random_state"] = int(params["random_state"].integers(np.iinfo(np.int32).max)) - if self._n_classes > 2: for alias in _ConfigAliases.get("num_class"): params.pop(alias, None) diff --git a/tests/python_package_test/test_sklearn.py b/tests/python_package_test/test_sklearn.py index cfbb0e3d9f68..03ac406d4fec 100644 --- a/tests/python_package_test/test_sklearn.py +++ b/tests/python_package_test/test_sklearn.py @@ -356,7 +356,7 @@ def test_early_stopping_is_deactivated_by_default_lambdarank(): q_train = np.loadtxt(str(rank_example_dir / "rank.train.query")) n_estimators = 5 gbm = lgb.LGBMRanker(n_estimators=n_estimators, random_state=42, verbose=-1) - gbm.fit(X_train, y_train, group=q_train) # Assuming 10 samples in one group + gbm.fit(X_train, y_train, group=q_train) # Check that early stopping did not kick in assert gbm._Booster.params.get("early_stopping_round") is None @@ -377,7 +377,6 @@ def test_auto_early_stopping_lambdarank(): y_train, group=q_train, eval_at=[1, 3], - callbacks=[lgb.reset_parameter(learning_rate=lambda x: max(0.01, 0.1 - 0.01 * x))], ) assert gbm._Booster.params["early_stopping_round"] == 10 assert gbm._Booster.num_trees() < n_estimators From 2b049c9baf209bebb57501358105303e516f151b Mon Sep 17 00:00:00 2001 From: Claudio Salvatore Arcidiacono <22871978+ClaudioSalvatoreArcidiacono@users.noreply.github.com> Date: Wed, 18 Dec 2024 16:38:40 +0100 Subject: [PATCH 21/24] Improve tests --- tests/python_package_test/test_sklearn.py | 64 +++++++++++++++-------- 1 file changed, 41 insertions(+), 23 deletions(-) diff --git a/tests/python_package_test/test_sklearn.py b/tests/python_package_test/test_sklearn.py index 03ac406d4fec..f6979769f232 100644 --- a/tests/python_package_test/test_sklearn.py +++ b/tests/python_package_test/test_sklearn.py @@ -288,7 +288,45 @@ def test_auto_early_stopping_binary_classification(): assert gbm.best_iteration_ < n_estimators -def test_auto_early_stopping_compatibility_with_histgradientboostingclassifier(): +def test_auto_early_stopping_regression(): + X, y = make_synthetic_regression(n_samples=30) + n_estimators = 20 + early_stopping_rounds = 2 + gbm = lgb.LGBMRegressor( + n_estimators=n_estimators, + random_state=42, + verbose=-1, + early_stopping=True, + num_leaves=5, + early_stopping_rounds=early_stopping_rounds, + ) + gbm.fit(X, y) + assert gbm._Booster.params["early_stopping_round"] == early_stopping_rounds + assert gbm._Booster.num_trees() < n_estimators + assert gbm.best_iteration_ < n_estimators + + +@pytest.mark.skipif( + getenv("TASK", "") == "cuda", reason="Skip due to differences in implementation details of CUDA version" +) +def test_auto_early_stopping_lambdarank(): + rank_example_dir = Path(__file__).absolute().parents[2] / "examples" / "lambdarank" + X_train, y_train = load_svmlight_file(str(rank_example_dir / "rank.train")) + q_train = np.loadtxt(str(rank_example_dir / "rank.train.query")) + n_estimators = 5 + gbm = lgb.LGBMRanker(n_estimators=n_estimators, random_state=42, early_stopping=True, num_leaves=5) + gbm.fit( + X_train, + y_train, + group=q_train, + eval_at=[1, 3], + ) + assert gbm._Booster.params["early_stopping_round"] == 10 + assert gbm._Booster.num_trees() < n_estimators + assert gbm.best_iteration_ < n_estimators + + +def test_auto_early_stopping_n_iter_no_change(): X, y = load_breast_cancer(return_X_y=True) n_estimators = 200 n_iter_no_change = 5 @@ -324,8 +362,8 @@ def test_auto_early_stopping_categorical_features_set_during_fit(rng_fixed_seed) gbm = lgb.LGBMClassifier(n_estimators=n_estimators, random_state=42, verbose=-1, early_stopping=True, num_leaves=5) gbm.fit(X, y, categorical_feature=cat_cols_actual) assert gbm._Booster.params["early_stopping_round"] == 10 - assert gbm._Booster.num_trees() < 5 - assert gbm.best_iteration_ < 5 + assert gbm._Booster.num_trees() < n_estimators + assert gbm.best_iteration_ < n_estimators def test_early_stopping_is_deactivated_by_default_regression(): @@ -363,26 +401,6 @@ def test_early_stopping_is_deactivated_by_default_lambdarank(): assert gbm._Booster.num_trees() == n_estimators -@pytest.mark.skipif( - getenv("TASK", "") == "cuda", reason="Skip due to differences in implementation details of CUDA version" -) -def test_auto_early_stopping_lambdarank(): - rank_example_dir = Path(__file__).absolute().parents[2] / "examples" / "lambdarank" - X_train, y_train = load_svmlight_file(str(rank_example_dir / "rank.train")) - q_train = np.loadtxt(str(rank_example_dir / "rank.train.query")) - n_estimators = 5 - gbm = lgb.LGBMRanker(n_estimators=n_estimators, random_state=42, early_stopping=True, num_leaves=5) - gbm.fit( - X_train, - y_train, - group=q_train, - eval_at=[1, 3], - ) - assert gbm._Booster.params["early_stopping_round"] == 10 - assert gbm._Booster.num_trees() < n_estimators - assert gbm.best_iteration_ < n_estimators - - def test_dart(): X, y = make_synthetic_regression() X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.1, random_state=42) From 61371cb53eb0fb212ce6a28741f2fd7290c909b7 Mon Sep 17 00:00:00 2001 From: Claudio Salvatore Arcidiacono <22871978+ClaudioSalvatoreArcidiacono@users.noreply.github.com> Date: Wed, 18 Dec 2024 17:12:46 +0100 Subject: [PATCH 22/24] Add tests to check for validation fraction --- tests/python_package_test/test_sklearn.py | 47 +++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/tests/python_package_test/test_sklearn.py b/tests/python_package_test/test_sklearn.py index f6979769f232..869cd449afd4 100644 --- a/tests/python_package_test/test_sklearn.py +++ b/tests/python_package_test/test_sklearn.py @@ -5,6 +5,7 @@ from functools import partial from os import getenv from pathlib import Path +from unittest.mock import patch import joblib import numpy as np @@ -306,6 +307,52 @@ def test_auto_early_stopping_regression(): assert gbm.best_iteration_ < n_estimators +def test_auto_early_stopping_check_validation_fraction_default_value(): + n_samples = 30 + X, y = make_synthetic_regression(n_samples=n_samples) + n_estimators = 20 + early_stopping_rounds = 2 + gbm = lgb.LGBMRegressor( + n_estimators=n_estimators, + random_state=42, + verbose=-1, + early_stopping=True, + num_leaves=5, + early_stopping_rounds=early_stopping_rounds, + ) + with patch("lightgbm.sklearn.train", side_effect=lgb.sklearn.train) as mock_train: + gbm.fit(X, y) + + valid_sets = mock_train.call_args.kwargs["valid_sets"] + assert len(valid_sets) == 1 + assert valid_sets[0].num_data() == n_samples * 0.1 + assert mock_train.call_args.kwargs["train_set"].num_data() == n_samples * 0.9 + + +def test_auto_early_stopping_check_set_validation_fraction(): + n_samples = 30 + validation_fraction = 0.2 + X, y = make_synthetic_regression(n_samples=n_samples) + n_estimators = 20 + early_stopping_rounds = 2 + gbm = lgb.LGBMRegressor( + n_estimators=n_estimators, + random_state=42, + verbose=-1, + early_stopping=True, + num_leaves=5, + early_stopping_rounds=early_stopping_rounds, + validation_fraction=validation_fraction, + ) + with patch("lightgbm.sklearn.train", side_effect=lgb.sklearn.train) as mock_train: + gbm.fit(X, y) + + valid_sets = mock_train.call_args.kwargs["valid_sets"] + assert len(valid_sets) == 1 + assert valid_sets[0].num_data() == n_samples * validation_fraction + assert mock_train.call_args.kwargs["train_set"].num_data() == n_samples * (1 - validation_fraction) + + @pytest.mark.skipif( getenv("TASK", "") == "cuda", reason="Skip due to differences in implementation details of CUDA version" ) From 65c4e2fd6c22706417c194c8f0d9cf949a197da1 Mon Sep 17 00:00:00 2001 From: Claudio Salvatore Arcidiacono <22871978+ClaudioSalvatoreArcidiacono@users.noreply.github.com> Date: Wed, 18 Dec 2024 17:16:12 +0100 Subject: [PATCH 23/24] Remove validation_fraction=None option --- python-package/lightgbm/sklearn.py | 39 +++++++++++++----------------- 1 file changed, 17 insertions(+), 22 deletions(-) diff --git a/python-package/lightgbm/sklearn.py b/python-package/lightgbm/sklearn.py index 796bde3717aa..20714f7cae4e 100644 --- a/python-package/lightgbm/sklearn.py +++ b/python-package/lightgbm/sklearn.py @@ -509,7 +509,7 @@ def __init__( importance_type: str = "split", *, early_stopping: Union[bool, int] = False, - validation_fraction: Optional[float] = 0.1, + validation_fraction: float = 0.1, **kwargs: Any, ): r"""Construct a gradient boosting model. @@ -599,10 +599,8 @@ def __init__( ``HistGradientBoosting`` estimators. it does not affect other ``lightgbm``-specific early stopping mechanisms, like passing the ``lgb.early_stopping`` callback and validation sets to the ``eval_set`` argument of `fit()`. - validation_fraction : float or None, optional (default=0.1) - Proportion of training data to set aside as - validation data for early stopping. If None, early stopping is done on - the training data. Only used if early stopping is performed. + validation_fraction : float (default=0.1) + Proportion of training data to set aside as validation data for early stopping. **kwargs Other parameters for the model. Check http://lightgbm.readthedocs.io/en/latest/Parameters.html for more parameters. @@ -1047,23 +1045,20 @@ def fit( valid_sets.append(valid_set) elif self.early_stopping is True: - if self.validation_fraction is not None: - n_splits = max(int(np.ceil(1 / self.validation_fraction)), 2) - stratified = isinstance(self, LGBMClassifier) - cvfolds = _make_n_folds( - full_data=train_set, - folds=None, - nfold=n_splits, - params=params, - seed=self.random_state, - stratified=stratified, - shuffle=True, - ) - train_idx, val_idx = next(cvfolds) - valid_set = train_set.subset(sorted(val_idx)) - train_set = train_set.subset(sorted(train_idx)) - else: - valid_set = train_set + n_splits = max(int(np.ceil(1 / self.validation_fraction)), 2) + stratified = isinstance(self, LGBMClassifier) + cvfolds = _make_n_folds( + full_data=train_set, + folds=None, + nfold=n_splits, + params=params, + seed=self.random_state, + stratified=stratified, + shuffle=True, + ) + train_idx, val_idx = next(cvfolds) + valid_set = train_set.subset(sorted(val_idx)) + train_set = train_set.subset(sorted(train_idx)) valid_set = valid_set.construct() valid_sets = [valid_set] From 0a8e84305e9743035cac8c5d95351aab5cbc508d Mon Sep 17 00:00:00 2001 From: Claudio Salvatore Arcidiacono <22871978+ClaudioSalvatoreArcidiacono@users.noreply.github.com> Date: Wed, 18 Dec 2024 17:19:08 +0100 Subject: [PATCH 24/24] Remove validation_fraction=None option also in dask --- python-package/lightgbm/dask.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/python-package/lightgbm/dask.py b/python-package/lightgbm/dask.py index cc73ef9e751a..6f3b8f2f548a 100644 --- a/python-package/lightgbm/dask.py +++ b/python-package/lightgbm/dask.py @@ -1137,7 +1137,7 @@ def __init__( client: Optional[Client] = None, *, early_stopping: bool = False, - validation_fraction: Optional[float] = 0.1, + validation_fraction: float = 0.1, **kwargs: Any, ): """Docstring is inherited from the lightgbm.LGBMClassifier.__init__.""" @@ -1343,7 +1343,7 @@ def __init__( client: Optional[Client] = None, *, early_stopping: bool = False, - validation_fraction: Optional[float] = 0.1, + validation_fraction: float = 0.1, **kwargs: Any, ): """Docstring is inherited from the lightgbm.LGBMRegressor.__init__.""" @@ -1513,7 +1513,7 @@ def __init__( client: Optional[Client] = None, *, early_stopping: bool = False, - validation_fraction: Optional[float] = 0.1, + validation_fraction: float = 0.1, **kwargs: Any, ): """Docstring is inherited from the lightgbm.LGBMRanker.__init__."""