dmlc / dmlc/tl2cgen

Multi-threaded prediction for treelite4j

Open
#5 2 comments 0 reactions 0 assignees View on GitHub
Dominant language
C++
Stars
55
Forks
11
PR merge metrics
No merged PRs in 30d

Description

Hello,

I'm running some benchmarks for treelite4j, testing out different batch sizes (splitting up a dataset into batches and predicting for each batch in sequence) and the number of threads passed to the `Predictor` object.

One thing I'm observing is that the number of threads set in the Predictor only seems to matter when my batch size is larger than 1, i.e. if I create a `DMatrix` with only a single row and call Predict on it, the number of threads the Predictor object was created with doesn't seem to matter.

Also, batch size doesn't seem to have a large effect when prediction is single threaded, is that expected as well?

Is it the case that multi-threading is only relevant when there's more than one row in the input DMatrix?

Would it be possible to use multi-threading for single-instance prediction as well, using each thread to predict for a single tree and merging the result in the end?

JMH results:
```
Benchmark (batchSize) (datapointNumber) (treeliteThreads) Mode Cnt Score Error Units
treelitePrediction 1 100000 1 thrpt 3 0.053 ± 0.009 ops/s
treelitePrediction 1 100000 8 thrpt 3 0.053 ± 0.005 ops/s
treelitePrediction 10 100000 1 thrpt 3 0.060 ± 0.007 ops/s
treelitePrediction 10 100000 8 thrpt 3 0.156 ± 0.515 ops/s
treelitePrediction 100 100000 1 thrpt 3 0.064 ± 0.016 ops/s
treelitePrediction 100 100000 8 thrpt 3 0.228 ± 0.667 ops/s
```

Some example code:

```java
package me.tvas.benchmark

import java.io.IOException;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.stream.DoubleStream;
import java.util.stream.Collectors;
import java.util.Iterator;

import ml.dmlc.treelite4j.java.*;
import ml.dmlc.treelite4j.DataPoint;
import ml.dmlc.treelite4j.DataPointFloat64;

import org.nd4j.linalg.api.ndarray.INDArray;
import org.nd4j.linalg.dataset.DataSet;
import org.nd4j.linalg.dataset.api.iterator.DataSetIterator;
import org.nd4j.linalg.factory.Nd4j;
import org.nd4j.linalg.api.rng.Random;

import org.openjdk.jmh.annotations.*;
import org.openjdk.jmh.infra.Blackhole;
import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.RunnerException;
import org.openjdk.jmh.runner.options.Options;
import org.openjdk.jmh.runner.options.OptionsBuilder;
import org.openjdk.jmh.results.format.ResultFormatType;

@State(Scope.Benchmark)
public class PredictionBenchmarks {

@Param({""})
public String treeliteModelPath;

@Param({"1000"})
public int datapointNumber;

@Param({"1"})
public int batchSize;

@Param({"1"})
public int treeliteThreads;

@Param({"/tmp"})
public static String destinationFolder;

Predictor treelitePredictor;
DataSet randomDataSet;
long numFeature;
int numBatches;

@Setup
public void prepare() throws IOException, TreeliteError {
Predictor treelitePredictor = new Predictor(this.treeliteModelPath, this.treeliteThreads, false);
// Create random data for prediction.
long numFeature = treelitePredictor.GetNumFeature();
long rngSeed = 42;
long[] shape = new long[]{datapointNumber, numFeature};
Nd4j.getRandom().setSeed(rngSeed);;
INDArray randomDoubles = Nd4j.rand(0.0, 32000.0, Nd4j.getRandom(), shape);
INDArray dummyLabels = Nd4j.ones(new long[]{datapointNumber, 1});

this.treelitePredictor = treelitePredictor;
this.randomDataSet = new DataSet(randomDoubles, dummyLabels);
this.numFeature = numFeature;
}

@Benchmark
public void treelitePrediction(Blackhole blackhole) throws TreeliteError {
List treelitePreds = new ArrayList(datapointNumber);
List batches = this.randomDataSet.batchBy(batchSize);
Iterator datasetIterator = batches.iterator();

while(datasetIterator.hasNext()) {
DataSet batch = datasetIterator.next();
INDArray features = batch.getFeatures();
long currentBatchSize = features.shape()[0];

double[] doubleVector = features.data().asDouble();
DMatrix dmat = new DMatrix(doubleVector, Double.NaN, currentBatchSize, this.numFeature);
INDArray preds = treelitePredictor.predict(dmat, false, false);
blackhole.consume(preds);
}
}

public static void main(String[] args) throws Exception {

Options opt = new OptionsBuilder()
.include(PredictionBenchmarks.class.getSimpleName())
.result(destinationFolder + "/" + "benchmarkResults.csv")
.resultFormat(ResultFormatType.CSV)
.forks(1)
.threads(1)
.jvmArgs("-ea")
.build();

new Runner(opt).run();
}
}

```

Contributor guide

No contributing guide indexed for this repository

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.