[cuda] implement feature_contri per-feature gain scaling on the CUDA tree learner#19
[cuda] implement feature_contri per-feature gain scaling on the CUDA tree learner#19maxwbuckley wants to merge 1 commit into
Conversation
|
Thank you, Max — and thank you, Claude Code (separately 🙂). Verdict: SOLID, no blocking bug. Reviewed against the CPU
Two non-blocking follow-ups (neither is introduced by this PR):
Blocker is just the required P.S. — smallest diff of the five (+124/−6) and it still failed lint. 😄 At this point I'm convinced |
|
Thank you, Max — and thank you, Claude Code (independently 🙂). Rebase nudge 👉 This one reviewed out SOLID — it's only CONFLICTING because You have write access now (accept the repo invite if you haven't), so once it's rebased and green you can merge it yourself. 🚀 |
c3a2898 to
e33d84a
Compare
|
Rebased onto current Please re-review — the branch was rebuilt as What changed. The old branch carried ~28 commits of work that has since landed on The only real conflict was in Good news: unlike #20, this one needed no math re-implementation. It scales gains in the split finder and never touches Validation (RTX 5090, CUDA 13.2, built from source): 5 Rebased and validated with Claude Code; real GPU runs, not CI. |
e33d84a to
e9791de
Compare
…tree learner Implements feature_contri (per-feature split-gain scaling) in the CUDA best split finder, matching the CPU FeatureHistogram behavior: - a per-task `double penalty` field is added to SplitFindTask, computed on host from config->feature_contri indexed by the real feature index (mirroring FeatureMetainfo::penalty in SetFeatureInfo), set when tasks are built and refreshed (with a device re-copy) in ResetConfig - each kernel multiplies the finalized per-feature gain by the task penalty after the per-feature best threshold is found, exactly where CPU applies output->gain *= meta_->penalty: within-feature threshold selection and the min_gain checks use the unscaled gain, only the cross-feature/cross-leaf comparison sees the scaled gain This replaces the previous Log::Fatal guard: feature_contri now works on CUDA instead of being rejected. Before this change CUDA silently ignored feature_contri: with feature_contri=[0,1,1,1] on data dominated by feature 0, CPU excluded feature 0 from all splits while CUDA still split on it almost exclusively, with prediction divergence up to 1.22. After this change both backends select identical features and predictions match bit-for-bit (max delta 8.9e-16 across all test cases). Adds a parametrized regression test (5 contri vectors) to test_dual.py asserting identical feature selection and prediction parity at atol=1e-10. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> (cherry picked from commit 30e1591)
e9791de to
52cb3b8
Compare
Problem: the delta
When training with
device_type="cuda",feature_contri(per-feature gain scaling, documented asgain[i] = feature_contri[i] * gain[i]) was silently ignored. The scaling is applied only in the CPUFeatureHistogram(output->gain *= meta_->penaltyafter the per-feature best threshold is found); the CUDA best-split finder compared raw gains with no per-feature multiplier.Measured delta before this PR (500×4 synthetic data dominated by feature 0;
learning_rate=0.1, 5 rounds,gpu_use_dp=true):CPU honors the scaling (a zero-contri feature is never split on, a down-weighted feature is used less); CUDA ignored it entirely — different features selected, predictions diverging by up to 1.22.
Fix
Implements per-feature gain scaling in the CUDA best-split finder:
SplitFindTaskgains adouble penaltyfield, computed on host fromconfig->feature_contriindexed by the real feature index — mirroringFeatureMetainfo::penaltyin the CPUSetFeatureInfo. Set when the task array is built; refreshed (with a device re-copy) inResetConfig.output->gain *= meta_->penalty. Within-feature threshold selection and themin_gain_to_splitchecks use the unscaled gain; only the cross-feature / cross-leaf comparison sees the scaled gain. All six gain-finalization sites are covered (numerical, categorical, global-memory, and discretized variants).The first commit's
Log::Fatalguard is removed:feature_contrinow works on CUDA instead of being rejected.Result: the delta after the fix
Same measurement, post-fix:
Identical feature selection on every case, and predictions match bit-for-bit (≤ 8.9e-16 = one fp64 ULP).
Tests (in
test_dual.py, gated onTASK=cuda)test_cuda_feature_contri_matches_cpu— 5 parametrized contri vectors, asserting (a) identical feature-selection sets, (b) zero-contri features excluded on both backends, (c) prediction parity atatol=1e-10. All 5 pass with the fix; the [0,1,1,1] and [0.5,1,1,1] cases fail on the unpatched build. Fulltest_dual.pysuite (59 tests) passes.Notes
feature_contrivalue to the penalty (nomax(0, ·)despite the parameter docs); the CUDA implementation mirrors the actual CPU code so both backends stay in lockstep.gainat all — that's an upstream issue independent offeature_contri.🤖 Generated with Claude Code