scm2cpp-lasso 0.5.3.1__tar.gz → 0.7.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {scm2cpp_lasso-0.5.3.1 → scm2cpp_lasso-0.7.0}/PKG-INFO +85 -23
- {scm2cpp_lasso-0.5.3.1 → scm2cpp_lasso-0.7.0}/README.md +84 -22
- {scm2cpp_lasso-0.5.3.1 → scm2cpp_lasso-0.7.0}/pyproject.toml +1 -1
- {scm2cpp_lasso-0.5.3.1 → scm2cpp_lasso-0.7.0}/src/scm2cpp_lasso/__init__.py +344 -70
- {scm2cpp_lasso-0.5.3.1 → scm2cpp_lasso-0.7.0}/src/scm2cpp_lasso/_generated/_loader.py +8 -26
- {scm2cpp_lasso-0.5.3.1 → scm2cpp_lasso-0.7.0}/src/scm2cpp_lasso/_generated/lasso_cov.cpp +0 -2
- {scm2cpp_lasso-0.5.3.1 → scm2cpp_lasso-0.7.0}/src/scm2cpp_lasso/_generated/lasso_cov.hpp +24 -45
- {scm2cpp_lasso-0.5.3.1 → scm2cpp_lasso-0.7.0}/src/scm2cpp_lasso/_generated/lasso_cov_capi.cpp +3 -9
- {scm2cpp_lasso-0.5.3.1 → scm2cpp_lasso-0.7.0}/src/scm2cpp_lasso/_libfind.py +11 -0
- scm2cpp_lasso-0.7.0/src/scm2cpp_lasso/batch_capi.cu +428 -0
- {scm2cpp_lasso-0.5.3.1 → scm2cpp_lasso-0.7.0}/src/scm2cpp_lasso.egg-info/PKG-INFO +85 -23
- scm2cpp_lasso-0.5.3.1/src/scm2cpp_lasso/batch_capi.cu +0 -80
- {scm2cpp_lasso-0.5.3.1 → scm2cpp_lasso-0.7.0}/setup.cfg +0 -0
- {scm2cpp_lasso-0.5.3.1 → scm2cpp_lasso-0.7.0}/setup.py +0 -0
- {scm2cpp_lasso-0.5.3.1 → scm2cpp_lasso-0.7.0}/src/scm2cpp_lasso/_generated/__init__.py +0 -0
- {scm2cpp_lasso-0.5.3.1 → scm2cpp_lasso-0.7.0}/src/scm2cpp_lasso/_generated/scm2cpp.hpp +0 -0
- {scm2cpp_lasso-0.5.3.1 → scm2cpp_lasso-0.7.0}/src/scm2cpp_lasso.egg-info/SOURCES.txt +0 -0
- {scm2cpp_lasso-0.5.3.1 → scm2cpp_lasso-0.7.0}/src/scm2cpp_lasso.egg-info/dependency_links.txt +0 -0
- {scm2cpp_lasso-0.5.3.1 → scm2cpp_lasso-0.7.0}/src/scm2cpp_lasso.egg-info/requires.txt +0 -0
- {scm2cpp_lasso-0.5.3.1 → scm2cpp_lasso-0.7.0}/src/scm2cpp_lasso.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: scm2cpp-lasso
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.7.0
|
|
4
4
|
Summary: Lasso by covariance-update coordinate descent, translated from Scheme to C++ by scm2cpp; GPU batch solver included
|
|
5
5
|
Author: Hirotaka Niitsuma
|
|
6
6
|
License: MIT
|
|
@@ -114,8 +114,8 @@ celer and skglm are built for very large sparse designs, where their
|
|
|
114
114
|
screening rules dominate; at this size they pay their setup per fit.
|
|
115
115
|
cuML parallelises *within* one fit, which wins when a fit is large; at
|
|
116
116
|
this size its launch overhead dominates, while our GPU row
|
|
117
|
-
parallelises *across* lambdas -- one CUDA
|
|
118
|
-
a cross-validation grid actually offers.
|
|
117
|
+
parallelises *across* lambdas -- one block of CUDA threads per
|
|
118
|
+
penalty, the axis a cross-validation grid actually offers.
|
|
119
119
|
|
|
120
120
|
`CovLassoCV` is scikit-learn's `LassoCV` over this machinery -- same
|
|
121
121
|
grid construction, same contiguous folds, same minimum-mean-MSE choice
|
|
@@ -123,23 +123,80 @@ grid construction, same contiguous folds, same minimum-mean-MSE choice
|
|
|
123
123
|
*subtraction* (the Gram is additive over rows, so G - Xf'Xf costs one
|
|
124
124
|
fold's work where rebuilding costs four folds'), and once the Grams
|
|
125
125
|
exist nothing downstream ever touches the n rows again -- which is why
|
|
126
|
-
the gap grows with n.
|
|
127
|
-
|
|
128
|
-
Gram
|
|
129
|
-
|
|
130
|
-
100
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
126
|
+
the gap grows with n. With a GPU the whole grid is one launch: one
|
|
127
|
+
*block* of threads per (fold, alpha) cell, the fold's Gram read in
|
|
128
|
+
place (cv Gram matrices on the device, not cv x 100), thread 0 of the
|
|
129
|
+
block doing the coordinate step and the block spreading the O(p)
|
|
130
|
+
update of the correlations. cv x 100 = 500 cells are too few
|
|
131
|
+
problems to fill a device one thread each, and a block per problem is
|
|
132
|
+
what turns that count into enough parallel work; `bench/cv-grid-designs.py`
|
|
133
|
+
times the alternatives (one thread per cell, warm runs of several
|
|
134
|
+
alphas per thread or block) side by side, and the block per cell wins
|
|
135
|
+
at every size tried. The GPU is the default when a device answers
|
|
136
|
+
(`force_cpu` takes the warm CPU path). Measured at cv=5, 100 alphas,
|
|
137
|
+
one CPU core, sklearn 1.9.0, best of three runs, whole estimator
|
|
138
|
+
including the Grams:
|
|
139
|
+
|
|
140
|
+
| n | p | CPU | CUDA | sklearn `LassoCV` |
|
|
141
|
+
|---------|------|--------|--------|-------------------|
|
|
142
|
+
| 1,800 | 200 | 0.36 s | 0.03 s | 0.23 s |
|
|
143
|
+
| 5,000 | 1000 | 3.9 s | 0.39 s | 2.7 s |
|
|
144
|
+
| 100,000 | 200 | 0.75 s | 0.60 s | 3.9 s |
|
|
145
|
+
| 100,000 | 500 | 2.0 s | 1.8 s | 12.6 s |
|
|
146
|
+
|
|
147
|
+
(This table was taken on a machine sharing its cores with other jobs,
|
|
148
|
+
load 14 on 20 threads, so its absolute times run some 2-3x above the
|
|
149
|
+
tables earlier in this README; the ratios are the point.) The grid
|
|
150
|
+
launch itself takes 9 ms at n=1,800 and 64 ms at n=5,000, p=1000; at
|
|
151
|
+
n=100,000 the CUDA column is nearly all Gram products, which the CPU
|
|
152
|
+
path pays as well. Large n is where the machinery pays either way:
|
|
153
|
+
nothing downstream of the Grams touches the 100,000 rows again. At
|
|
154
|
+
n=5,000, p=1000 sklearn's raw-X descent beats our Gram-heavy CPU path,
|
|
155
|
+
and the GPU makes up the difference seven times over. CPU and CUDA
|
|
156
|
+
choose the same alpha everywhere and agree to 1e-14; against sklearn
|
|
157
|
+
the pick is exact at n=5,000 and one grid step apart on a near-tie
|
|
158
|
+
elsewhere (mean-MSE relative gap at most 2.6e-4), and sklearn at
|
|
159
|
+
tol=1e-10 picks our alpha at every size.
|
|
160
|
+
|
|
161
|
+
`CovMultiTaskLasso` and `CovMultiTaskLassoCV` are the multi-task
|
|
162
|
+
family over the same machinery: scikit-learn's `MultiTaskLasso`,
|
|
163
|
+
`MultiTaskElasticNet` (through `l1_ratio`), `MultiTaskLassoCV` and
|
|
164
|
+
`MultiTaskElasticNetCV`. The penalty ties each feature's row of W
|
|
165
|
+
together across tasks, so the coordinate update becomes a block soft
|
|
166
|
+
threshold on the row's L2 norm; C = X'Y - GW is maintained per task
|
|
167
|
+
exactly as the single-task C is, and nothing downstream of the Grams
|
|
168
|
+
touches the n rows -- the kernel is the same translated Scheme
|
|
169
|
+
(`mt-descend`). Folds are independent and both the descent (ctypes)
|
|
170
|
+
and the products (BLAS) release the GIL, so `n_jobs` threads scale the
|
|
171
|
+
CV across folds; the default is sequential, as scikit-learn's
|
|
172
|
+
`n_jobs=None` is, and the benchmark pins BLAS to one thread on both
|
|
173
|
+
sides either way. With a GPU the CV grid is one launch exactly as
|
|
174
|
+
`CovLassoCV`'s is, a block per (fold, alpha) cell with the p x T
|
|
175
|
+
coefficient block in shared memory (`force_cpu` opts out). Same
|
|
176
|
+
protocol as above, 8 tasks; single fits at alpha = 0.1 lambda_max;
|
|
177
|
+
the CV pairs share the 100-alpha grid:
|
|
178
|
+
|
|
179
|
+
| estimator | n | p | ours | ours `n_jobs=5` | ours CUDA | sklearn |
|
|
180
|
+
|-----------------------|---------|-----|--------|-----------------|-----------|---------|
|
|
181
|
+
| MultiTaskLasso | 100,000 | 200 | 0.15 s | -- | -- | 0.80 s |
|
|
182
|
+
| MultiTaskLasso | 100,000 | 500 | 0.53 s | -- | -- | 1.9 s |
|
|
183
|
+
| MultiTaskLassoCV | 1,800 | 200 | 1.5 s | 0.33 s | 0.08 s | 0.51 s |
|
|
184
|
+
| MultiTaskLassoCV | 100,000 | 200 | 1.6 s | 0.62 s | 1.7 s | 76 s |
|
|
185
|
+
| MultiTaskElasticNetCV | 1,800 | 200 | 1.6 s | 0.51 s | 0.10 s | 1.0 s |
|
|
186
|
+
| MultiTaskElasticNetCV | 100,000 | 200 | 1.6 s | 0.68 s | 1.9 s | 164 s |
|
|
187
|
+
|
|
188
|
+
(`MultiTaskElasticNet` single fits time the same as `MultiTaskLasso`'s.
|
|
189
|
+
The CUDA column was timed on the busier day of the `CovLassoCV` table
|
|
190
|
+
above, when the sequential CPU rows read 1.5 / 2.0 / 1.6 / 2.1 s and
|
|
191
|
+
sklearn's 0.56 / 114 / 0.99 / 241 s.) At n=1,800 our sequential CV
|
|
192
|
+
is slower than sklearn's: the default tol=1e-8 runs more sweeps than
|
|
193
|
+
sklearn's 1e-4 dual-gap stop, and at that size there are no rows to
|
|
194
|
+
save -- and the GPU grid takes the whole thing in a tenth of a second.
|
|
195
|
+
At n=100,000 the Gram route is 48x and 100x ahead, essentially
|
|
196
|
+
unchanged from n=1,800 in absolute time; the CUDA column there is
|
|
197
|
+
the Gram products, the same ones the CPU path forms. Coefficients agree with scikit-learn's to 1e-15 at tight
|
|
198
|
+
tolerance, and the CV pair picks the same alpha with coefficients to
|
|
199
|
+
1e-13.
|
|
143
200
|
|
|
144
201
|
## Which method
|
|
145
202
|
|
|
@@ -206,9 +263,14 @@ groups it reduces exactly to the lasso -- verified against sklearn to
|
|
|
206
263
|
|
|
207
264
|
`bootstrap` draws pairs-bootstrap resamples and refits them all at
|
|
208
265
|
one lambda. Each resample's Gram matrix is `X' diag(m) X` for its
|
|
209
|
-
multiplicity counts `m`
|
|
210
|
-
|
|
211
|
-
|
|
266
|
+
multiplicity counts `m`, written as `Xs' Xs` with `Xs = diag(sqrt m) X`
|
|
267
|
+
so that numpy hands it to the symmetric rank-k product (`dsyrk`,
|
|
268
|
+
half the flops of `X' (diag(m) X)`) -- one BLAS call -- and because
|
|
269
|
+
the problems are independent, the descents run as one batch: on the
|
|
270
|
+
GPU, one block of threads per resample, each reading its own Gram
|
|
271
|
+
matrix. The products are the cost: at n=5000, p=1000 a hundred
|
|
272
|
+
resamples took 18.1 s as general products and 11.2 s as `dsyrk`,
|
|
273
|
+
descents included.
|
|
212
274
|
|
|
213
275
|
```python
|
|
214
276
|
betas = model.bootstrap(lam, n_boot=500, seed=0) # (500, p)
|
|
@@ -96,8 +96,8 @@ celer and skglm are built for very large sparse designs, where their
|
|
|
96
96
|
screening rules dominate; at this size they pay their setup per fit.
|
|
97
97
|
cuML parallelises *within* one fit, which wins when a fit is large; at
|
|
98
98
|
this size its launch overhead dominates, while our GPU row
|
|
99
|
-
parallelises *across* lambdas -- one CUDA
|
|
100
|
-
a cross-validation grid actually offers.
|
|
99
|
+
parallelises *across* lambdas -- one block of CUDA threads per
|
|
100
|
+
penalty, the axis a cross-validation grid actually offers.
|
|
101
101
|
|
|
102
102
|
`CovLassoCV` is scikit-learn's `LassoCV` over this machinery -- same
|
|
103
103
|
grid construction, same contiguous folds, same minimum-mean-MSE choice
|
|
@@ -105,23 +105,80 @@ grid construction, same contiguous folds, same minimum-mean-MSE choice
|
|
|
105
105
|
*subtraction* (the Gram is additive over rows, so G - Xf'Xf costs one
|
|
106
106
|
fold's work where rebuilding costs four folds'), and once the Grams
|
|
107
107
|
exist nothing downstream ever touches the n rows again -- which is why
|
|
108
|
-
the gap grows with n.
|
|
109
|
-
|
|
110
|
-
Gram
|
|
111
|
-
|
|
112
|
-
100
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
108
|
+
the gap grows with n. With a GPU the whole grid is one launch: one
|
|
109
|
+
*block* of threads per (fold, alpha) cell, the fold's Gram read in
|
|
110
|
+
place (cv Gram matrices on the device, not cv x 100), thread 0 of the
|
|
111
|
+
block doing the coordinate step and the block spreading the O(p)
|
|
112
|
+
update of the correlations. cv x 100 = 500 cells are too few
|
|
113
|
+
problems to fill a device one thread each, and a block per problem is
|
|
114
|
+
what turns that count into enough parallel work; `bench/cv-grid-designs.py`
|
|
115
|
+
times the alternatives (one thread per cell, warm runs of several
|
|
116
|
+
alphas per thread or block) side by side, and the block per cell wins
|
|
117
|
+
at every size tried. The GPU is the default when a device answers
|
|
118
|
+
(`force_cpu` takes the warm CPU path). Measured at cv=5, 100 alphas,
|
|
119
|
+
one CPU core, sklearn 1.9.0, best of three runs, whole estimator
|
|
120
|
+
including the Grams:
|
|
121
|
+
|
|
122
|
+
| n | p | CPU | CUDA | sklearn `LassoCV` |
|
|
123
|
+
|---------|------|--------|--------|-------------------|
|
|
124
|
+
| 1,800 | 200 | 0.36 s | 0.03 s | 0.23 s |
|
|
125
|
+
| 5,000 | 1000 | 3.9 s | 0.39 s | 2.7 s |
|
|
126
|
+
| 100,000 | 200 | 0.75 s | 0.60 s | 3.9 s |
|
|
127
|
+
| 100,000 | 500 | 2.0 s | 1.8 s | 12.6 s |
|
|
128
|
+
|
|
129
|
+
(This table was taken on a machine sharing its cores with other jobs,
|
|
130
|
+
load 14 on 20 threads, so its absolute times run some 2-3x above the
|
|
131
|
+
tables earlier in this README; the ratios are the point.) The grid
|
|
132
|
+
launch itself takes 9 ms at n=1,800 and 64 ms at n=5,000, p=1000; at
|
|
133
|
+
n=100,000 the CUDA column is nearly all Gram products, which the CPU
|
|
134
|
+
path pays as well. Large n is where the machinery pays either way:
|
|
135
|
+
nothing downstream of the Grams touches the 100,000 rows again. At
|
|
136
|
+
n=5,000, p=1000 sklearn's raw-X descent beats our Gram-heavy CPU path,
|
|
137
|
+
and the GPU makes up the difference seven times over. CPU and CUDA
|
|
138
|
+
choose the same alpha everywhere and agree to 1e-14; against sklearn
|
|
139
|
+
the pick is exact at n=5,000 and one grid step apart on a near-tie
|
|
140
|
+
elsewhere (mean-MSE relative gap at most 2.6e-4), and sklearn at
|
|
141
|
+
tol=1e-10 picks our alpha at every size.
|
|
142
|
+
|
|
143
|
+
`CovMultiTaskLasso` and `CovMultiTaskLassoCV` are the multi-task
|
|
144
|
+
family over the same machinery: scikit-learn's `MultiTaskLasso`,
|
|
145
|
+
`MultiTaskElasticNet` (through `l1_ratio`), `MultiTaskLassoCV` and
|
|
146
|
+
`MultiTaskElasticNetCV`. The penalty ties each feature's row of W
|
|
147
|
+
together across tasks, so the coordinate update becomes a block soft
|
|
148
|
+
threshold on the row's L2 norm; C = X'Y - GW is maintained per task
|
|
149
|
+
exactly as the single-task C is, and nothing downstream of the Grams
|
|
150
|
+
touches the n rows -- the kernel is the same translated Scheme
|
|
151
|
+
(`mt-descend`). Folds are independent and both the descent (ctypes)
|
|
152
|
+
and the products (BLAS) release the GIL, so `n_jobs` threads scale the
|
|
153
|
+
CV across folds; the default is sequential, as scikit-learn's
|
|
154
|
+
`n_jobs=None` is, and the benchmark pins BLAS to one thread on both
|
|
155
|
+
sides either way. With a GPU the CV grid is one launch exactly as
|
|
156
|
+
`CovLassoCV`'s is, a block per (fold, alpha) cell with the p x T
|
|
157
|
+
coefficient block in shared memory (`force_cpu` opts out). Same
|
|
158
|
+
protocol as above, 8 tasks; single fits at alpha = 0.1 lambda_max;
|
|
159
|
+
the CV pairs share the 100-alpha grid:
|
|
160
|
+
|
|
161
|
+
| estimator | n | p | ours | ours `n_jobs=5` | ours CUDA | sklearn |
|
|
162
|
+
|-----------------------|---------|-----|--------|-----------------|-----------|---------|
|
|
163
|
+
| MultiTaskLasso | 100,000 | 200 | 0.15 s | -- | -- | 0.80 s |
|
|
164
|
+
| MultiTaskLasso | 100,000 | 500 | 0.53 s | -- | -- | 1.9 s |
|
|
165
|
+
| MultiTaskLassoCV | 1,800 | 200 | 1.5 s | 0.33 s | 0.08 s | 0.51 s |
|
|
166
|
+
| MultiTaskLassoCV | 100,000 | 200 | 1.6 s | 0.62 s | 1.7 s | 76 s |
|
|
167
|
+
| MultiTaskElasticNetCV | 1,800 | 200 | 1.6 s | 0.51 s | 0.10 s | 1.0 s |
|
|
168
|
+
| MultiTaskElasticNetCV | 100,000 | 200 | 1.6 s | 0.68 s | 1.9 s | 164 s |
|
|
169
|
+
|
|
170
|
+
(`MultiTaskElasticNet` single fits time the same as `MultiTaskLasso`'s.
|
|
171
|
+
The CUDA column was timed on the busier day of the `CovLassoCV` table
|
|
172
|
+
above, when the sequential CPU rows read 1.5 / 2.0 / 1.6 / 2.1 s and
|
|
173
|
+
sklearn's 0.56 / 114 / 0.99 / 241 s.) At n=1,800 our sequential CV
|
|
174
|
+
is slower than sklearn's: the default tol=1e-8 runs more sweeps than
|
|
175
|
+
sklearn's 1e-4 dual-gap stop, and at that size there are no rows to
|
|
176
|
+
save -- and the GPU grid takes the whole thing in a tenth of a second.
|
|
177
|
+
At n=100,000 the Gram route is 48x and 100x ahead, essentially
|
|
178
|
+
unchanged from n=1,800 in absolute time; the CUDA column there is
|
|
179
|
+
the Gram products, the same ones the CPU path forms. Coefficients agree with scikit-learn's to 1e-15 at tight
|
|
180
|
+
tolerance, and the CV pair picks the same alpha with coefficients to
|
|
181
|
+
1e-13.
|
|
125
182
|
|
|
126
183
|
## Which method
|
|
127
184
|
|
|
@@ -188,9 +245,14 @@ groups it reduces exactly to the lasso -- verified against sklearn to
|
|
|
188
245
|
|
|
189
246
|
`bootstrap` draws pairs-bootstrap resamples and refits them all at
|
|
190
247
|
one lambda. Each resample's Gram matrix is `X' diag(m) X` for its
|
|
191
|
-
multiplicity counts `m`
|
|
192
|
-
|
|
193
|
-
|
|
248
|
+
multiplicity counts `m`, written as `Xs' Xs` with `Xs = diag(sqrt m) X`
|
|
249
|
+
so that numpy hands it to the symmetric rank-k product (`dsyrk`,
|
|
250
|
+
half the flops of `X' (diag(m) X)`) -- one BLAS call -- and because
|
|
251
|
+
the problems are independent, the descents run as one batch: on the
|
|
252
|
+
GPU, one block of threads per resample, each reading its own Gram
|
|
253
|
+
matrix. The products are the cost: at n=5000, p=1000 a hundred
|
|
254
|
+
resamples took 18.1 s as general products and 11.2 s as `dsyrk`,
|
|
255
|
+
descents included.
|
|
194
256
|
|
|
195
257
|
```python
|
|
196
258
|
betas = model.bootstrap(lam, n_boot=500, seed=0) # (500, p)
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "scm2cpp-lasso"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.7.0"
|
|
8
8
|
description = "Lasso by covariance-update coordinate descent, translated from Scheme to C++ by scm2cpp; GPU batch solver included"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.8"
|