Uh oh!
There was an error while loading. Please reload this page.
[SPARK-30659][ML][PYSPARK] LogisticRegression blockify input vectors - #27374
[SPARK-30659][ML][PYSPARK] LogisticRegression blockify input vectors#27374zhengruifeng wants to merge 7 commits into
Conversation
zhengruifeng
commented
Jan 28, 2020
env: bin/spark-shell --driver-memory=32G testCode: importorg.apache.spark.ml.classification._importorg.apache.spark.storage.StorageLevelvardf= spark.read.format("libsvm").load("/data1/Datasets/a9a/a9a").withColumn("label", (col("label")+1)/2)
df.persist(StorageLevel.MEMORY_AND_DISK)
df.count
(0 until 8).foreach{ _ => df = df.union(df) }
df.count
newLogisticRegression().setMaxIter(10).fit(df)
vallr1=newLogisticRegression().setMaxIter(100).setFamily("binomial")
valstart=System.currentTimeMillis; valmodel1= lr1.fit(df); valend=System.currentTimeMillis; end - start
vallr2=newLogisticRegression().setMaxIter(100).setFitIntercept(false).setFamily("binomial")
valstart=System.currentTimeMillis; valmodel2= lr2.fit(df); valend=System.currentTimeMillis; end - start
vallr3=newLogisticRegression().setMaxIter(100).setFamily("multinomial")
valstart=System.currentTimeMillis; valmodel3= lr3.fit(df); valend=System.currentTimeMillis; end - start
vallr4=newLogisticRegression().setMaxIter(100).setFitIntercept(false).setFamily("multinomial")
valstart=System.currentTimeMillis; valmodel4= lr4.fit(df); valend=System.currentTimeMillis; end - startresult: this PR: Master: |
| // If fitIntercept==false, gradientSumArray += mat.T X matrix | ||
| // GEMM requires block.matrix is dense | ||
| val gradSumMat = new DenseMatrix(numClasses, numFeatures, localGradientSumArray) | ||
| BLAS.gemm(1.0, mat.transpose, dm, 1.0, gradSumMat) |
There was a problem hiding this comment.
Since gradientSumArray is for Matrix of shape CXFPI, and BLAS.gemm requires the output matrix is not transposed. So only if F(numFeature) == FPI(numFeaturesPlusIntercept) and input block is dense, can I use BLAS.gemm to directly update gradientSumArray.
Otherwise, I need to output the result to a temp matrix multinomialLinearGradSumMat, and then add elements to gradientSumArray
zhengruifeng
commented
Jan 28, 2020
ping @srowen |
| } | ||
| // Helper vectors and matrices for binary: | ||
| @transient private lazy val binaryLinear = { |
There was a problem hiding this comment.
So, are these lazy just to deal with recreating them after deserialization? they don't seem big, so can they just be non-transient, non-lazy? unless it's a material problem, might be simpler and faster.
Or how much do you need to hold on to scratch vectors like auxiliaryVec vs just locals?
There was a problem hiding this comment.
binaryLinear, binaryIntercept, multinomialLinear, multinomialIntercept are the linear and bias part of coefficients, repectively.
binaryLinearGradSumVec (numFeatures) and multinomialLinearGradSumMat (numClassXnumFeatures) are used to store result of gemv/gemm if fitIntercept==True, since gradientSumArray contains gradient sums of intercepts and can not be used directly in gemv/gemm.
auxiliaryVec (blockSize) and multinomialAuxiliaryMat (blockSizeXnumClasses) are used to store the intermediate multiplication(margins) and multipliers.
they can be used among blocks, and if they are used multi-times in one call we can assign them to local variables.
However I am OK to make them local variables, since I guess they are not the bottleneck.
There was a problem hiding this comment.
OK up to your judgment. It'd be simpler to not even make them members, if it's not much difference to performance
Uh oh!
There was an error while loading. Please reload this page.
srowen
commented
Jan 28, 2020
Also, does this cause any appreciable slowdown at smaller scale? it's not a big deal if something that's fast is a little slower, to make things that are slow much faster, but just want to get a sense of what you know about the scale implications. |
SparkQA
commented
Jan 28, 2020
Test build #117483 has finished for PR 27374 at commit
|
zhengruifeng
commented
Jan 28, 2020
@srowen I had made other performance tests, it seems that the performance is related to Thanks for reviewing! |
srowen
commented
Jan 28, 2020
Yeah that's the question... Level 1 BLAS often isn't a win. L2/L3 yes. It's probably a win, but just dont' want to take a perf hit on most use cases to help large ones. Even that's arguable. |
zhengruifeng
commented
Jan 29, 2020
Ok, I will test on small datasets. |
SparkQA
commented
Jan 29, 2020
Test build #117501 has finished for PR 27374 at commit
|
@srowen I found that on small datasets, the speed up is even more significant. data: a9a, numFeatures=123, numInstances=32,561 testCode: importorg.apache.spark.ml.classification._importorg.apache.spark.storage.StorageLevelvaldf= spark.read.format("libsvm").load("/data1/Datasets/a9a/a9a").withColumn("label", (col("label")+1)/2)
df.persist(StorageLevel.MEMORY_AND_DISK)
df.count
vallr4=newLogisticRegression().setMaxIter(100).setFitIntercept(false).setFamily("multinomial")
valstart=System.currentTimeMillis; valmodel4= lr4.fit(df); valend=System.currentTimeMillis; end - start
Seq(64, 256, 1024, 4096, 8192).map { b =>valstart=System.currentTimeMillis; valmodel1=newLogisticRegression().setBlockSize(b).fit(df); valend=System.currentTimeMillis; end - start } // this PRSeq(64, 256, 1024, 4096, 8192).map { b =>valstart=System.currentTimeMillis; valmodel1=newLogisticRegression().fit(df); valend=System.currentTimeMillis; end - start } // Masterresult: about 77%~92% faster. I think that is beacuse on big dataset, the communiation overhead has a bigger impact on the whole procedure; while on small datasets like a9a, high-level BLAS dominates the performance. But the way, I set default value to 1024 base on above result. However, the best blocksize will depend on many factors like whether native-BLAS is used, numFetaures, sparsity, numInstances, etc. |
SparkQA
commented
Jan 29, 2020
Test build #117507 has finished for PR 27374 at commit
|
zhengruifeng
commented
Jan 29, 2020
Param |
SparkQA
commented
Jan 29, 2020
Test build #117510 has finished for PR 27374 at commit
|
Uh oh!
There was an error while loading. Please reload this page.
| } | ||
| // Helper vectors and matrices for binary: | ||
| @transient private lazy val binaryLinear = { |
There was a problem hiding this comment.
OK up to your judgment. It'd be simpler to not even make them members, if it's not much difference to performance
| if (fitIntercept) { | ||
| val intercept = coefficientsArray.last | ||
| var i = 0 | ||
| while (i < size) { |
There was a problem hiding this comment.
Would it be faster to fill an array with this value and then make a DenseVector? maybe I'm missing why not
There was a problem hiding this comment.
OK, I will update it.
srowen
commented
Jan 30, 2020
I'll merge soon to unblock #27389 , but if you have any final thoughts on the above soon, that would be good to check. |
zhengruifeng
commented
Jan 30, 2020
since |
SparkQA
commented
Jan 30, 2020
Test build #117538 has finished for PR 27374 at commit
|
srowen
commented
Jan 30, 2020
Merged to master |
@zhengruifeng Could you provide detail benchmark results separately for:
Thanks! |
mengxr
commented
Feb 6, 2020
+1 on @WeichenXu123 's suggestion and I would suggest temporarily reverting this change before we have a good solution. @zhengruifeng@srowen This new approach will introduce significant performance regression on sparse datasets with large number of features, e.g., https://www.csie.ntu.edu.tw/~cjlin/libsvmtools/datasets/binary.html#webspam (16,609,143 features). With block size 1024, it requires ~130GB RAM. |
zhengruifeng
commented
Feb 6, 2020
@mengxr@WeichenXu123 |
WeichenXu123
commented
Feb 6, 2020
@zhengruifeng Thanks! Also note your ongoing PR blockify GMM #27473 which do similar thing should also suspend for now. |
zhengruifeng
commented
Feb 6, 2020
@WeichenXu123 Yes, I just mark GMM WIP. |
zhengruifeng
commented
Feb 6, 2020
@WeichenXu123@mengxr@srowen This PR will fail dure to OOM in standardization, so I use a patch: valvec= features match {
casedv: DenseVector=>vari=0while (i < dv.size) {
valstd= featuresStd(i)
if (std !=0) {
dv.values(i) /= std
} else {
dv.values(i) =0.0
}
i +=1
}
dv
casesv: SparseVector=>varj=0while (j < sv.numActives) {
vali= sv.indices(j)
valstd= featuresStd(i)
if (std !=0) {
sv.values(j) /= std
} else {
sv.values(j) =0.0
}
j +=1
}
sv
}
After that, I use following code to test performance: importorg.apache.spark.ml.classification._importorg.apache.spark.storage.StorageLevelvaldf= spark.read.format("libsvm").load("/data1/Datasets/webspam/webspam_wc_normalized_trigram.svm.10k").withColumn("label", (col("label")+1)/2)
vallr1=newLogisticRegression().setMaxIter(100).setFamily("binomial").setBlockSize(128) // this PRvalstart=System.currentTimeMillis; valmodel1= lr1.fit(df); valend=System.currentTimeMillis; end - start
vallr2=newLogisticRegression().setMaxIter(100).setFamily("binomial").setBlockSize(1024) // this PRvalstart=System.currentTimeMillis; valmodel2= lr2.fit(df); valend=System.currentTimeMillis; end - start
vallr=newLogisticRegression().setMaxIter(100).setFamily("binomial") // 2.4.4valstart=System.currentTimeMillis; valmodel= lr.fit(df); valend=System.currentTimeMillis; end - start
Result:
For this sparse dataset, this PR (with updated standardization) is about 23% slower, and use 7% more RAM. So I aggre with you to revert this PR and relative PRs LinearSVC, LinearRegression. |
srowen
commented
Feb 6, 2020
Ahhh, OK. I didn't think enough about whether sparse vectors would behave significantly differently. Of course, should have been checked. I agree. I'm happy to merge a revert PR or @zhengruifeng you can too. |
huaxingao
commented
Feb 6, 2020
I can revert my PR #27389. But before I revert, I want to check with you folks. My PR only has API changes:
It seems to me it's OK to keep these changes. Of course, there is no |
mengxr
commented
Feb 6, 2020
I think we can keep the API only change and figure out a way to let the implementation automatically decides whether to blockify+densify for performance. |
zhengruifeng
commented
Feb 7, 2020
OK, I will revert those PRs, and then @huaxingao can add back ALS/MLP extend HasBlockSize as a separate PR. |
### What changes were proposed in this pull request? Revert #27360#27396#27374#27389 ### Why are the changes needed? BLAS need more performace tests, specially on sparse datasets. Perfermance test of LogisticRegression (#27374) on sparse dataset shows that blockify vectors to matrices and use BLAS will cause performance regression. LinearSVC and LinearRegression were also updated in the same way as LogisticRegression, so we need to revert them to make sure no regression. ### Does this PR introduce any user-facing change? remove newly added param blockSize ### How was this patch tested? reverted testsuites Closes#27487 from zhengruifeng/revert_blockify_ii. Authored-by: zhengruifeng <ruifengz@foxmail.com> Signed-off-by: zhengruifeng <ruifengz@foxmail.com>
### What changes were proposed in this pull request? Revert #27360#27396#27374#27389 ### Why are the changes needed? BLAS need more performace tests, specially on sparse datasets. Perfermance test of LogisticRegression (#27374) on sparse dataset shows that blockify vectors to matrices and use BLAS will cause performance regression. LinearSVC and LinearRegression were also updated in the same way as LogisticRegression, so we need to revert them to make sure no regression. ### Does this PR introduce any user-facing change? remove newly added param blockSize ### How was this patch tested? reverted testsuites Closes#27487 from zhengruifeng/revert_blockify_ii. Authored-by: zhengruifeng <ruifengz@foxmail.com> Signed-off-by: zhengruifeng <ruifengz@foxmail.com>
### What changes were proposed in this pull request? Revert apache#27360apache#27396apache#27374apache#27389 ### Why are the changes needed? BLAS need more performace tests, specially on sparse datasets. Perfermance test of LogisticRegression (apache#27374) on sparse dataset shows that blockify vectors to matrices and use BLAS will cause performance regression. LinearSVC and LinearRegression were also updated in the same way as LogisticRegression, so we need to revert them to make sure no regression. ### Does this PR introduce any user-facing change? remove newly added param blockSize ### How was this patch tested? reverted testsuites Closesapache#27487 from zhengruifeng/revert_blockify_ii. Authored-by: zhengruifeng <ruifengz@foxmail.com> Signed-off-by: zhengruifeng <ruifengz@foxmail.com>
What changes were proposed in this pull request?
1, use blocks instead of vectors
2, use Level-2 BLAS for binary, use Level-3 BLAS for multinomial
Why are the changes needed?
1, less RAM to persist training data; (save ~40%)
2, faster than existing impl; (40% ~ 92%)
Does this PR introduce any user-facing change?
add a new expert param
blockSizeHow was this patch tested?
updated testsuites