Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@
/PWGUD @alibuild @amatyja @rolavick
/PWGJE @alibuild @nzardosh @fjonasALICE @jaimenorman @mhemmer-cern
/Tools/PIDML @alibuild @saganatt
/Tools/ML @alibuild @fcatalan92 @fmazzasc
/Tools/ML @alibuild @fcatalan92 @fmazzasc @ChSonnabend
/Tutorials/PWGCF @alibuild @jgrosseo @victor-gonzalez @zchochul
/Tutorials/PWGDQ @alibuild @iarsene @mcoquet642 @XiaozhiBai @mguilbau
/Tutorials/PWGEM @alibuild @mikesas @rbailhac @dsekihat @ivorobye @feisenhu
Expand Down
11 changes: 6 additions & 5 deletions Common/Tools/PID/pidTPCModule.h
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
#include <TRandom.h>
#include <TString.h>

#include <algorithm>
#include <chrono>
#include <cstddef>
#include <cstdint>
Expand Down Expand Up @@ -92,10 +93,10 @@

struct pidTPCConfigurables : o2::framework::ConfigurableGroup {
std::string prefix = "pidTPC";
o2::framework::Configurable<std::string> paramfile{"param-file", "", "Path to the parametrization object, if empty the parametrization is not taken from file"};

Check failure on line 96 in Common/Tools/PID/pidTPCModule.h

View workflow job for this annotation

GitHub Actions / O2 linter

[name/configurable]

Use lowerCamelCase for names of configurables and use the same name for the struct member as for the JSON string. (Declare the type and names on the same line.)
o2::framework::Configurable<std::string> ccdbPath{"ccdbPath", "Analysis/PID/TPC/Response", "Path of the TPC parametrization on the CCDB"};
o2::framework::Configurable<std::string> recoPass{"recoPass", "", "Reconstruction pass name for CCDB query (automatically takes latest object for timestamp if blank)"};
o2::framework::Configurable<int64_t> ccdbTimestamp{"ccdb-timestamp", 0, "timestamp of the object used to query in CCDB the detector response. Exceptions: -1 gets the latest object, 0 gets the run dependent timestamp"};

Check failure on line 99 in Common/Tools/PID/pidTPCModule.h

View workflow job for this annotation

GitHub Actions / O2 linter

[name/configurable]

Use lowerCamelCase for names of configurables and use the same name for the struct member as for the JSON string. (Declare the type and names on the same line.)
// Parameters for loading network from a file / downloading the file
o2::framework::Configurable<bool> useNetworkCorrection{"useNetworkCorrection", 0, "(bool) Wether or not to use the network correction for the TPC dE/dx signal"};
o2::framework::Configurable<bool> autofetchNetworks{"autofetchNetworks", 1, "(bool) Automatically fetches networks from CCDB for the correct run number"};
Expand All @@ -109,13 +110,13 @@
o2::framework::Configurable<int> savedEdxsCorrected{"savedEdxsCorrected", -1, {"Save table with corrected dE/dx calculated on the spot. 0: off, 1: on, -1: auto"}};
o2::framework::Configurable<bool> useCorrecteddEdx{"useCorrecteddEdx", false, "(bool) If true, use corrected dEdx value in Nsigma calculation instead of the one in the AO2D"};

o2::framework::Configurable<int> pidFullEl{"pid-full-el", -1, {"Produce PID information for the Electron mass hypothesis, overrides the automatic setup: the corresponding table can be set off (0) or on (1)"}};

Check failure on line 113 in Common/Tools/PID/pidTPCModule.h

View workflow job for this annotation

GitHub Actions / O2 linter

[name/configurable]

Use lowerCamelCase for names of configurables and use the same name for the struct member as for the JSON string. (Declare the type and names on the same line.)
o2::framework::Configurable<int> pidFullMu{"pid-full-mu", -1, {"Produce PID information for the Muon mass hypothesis, overrides the automatic setup: the corresponding table can be set off (0) or on (1)"}};

Check failure on line 114 in Common/Tools/PID/pidTPCModule.h

View workflow job for this annotation

GitHub Actions / O2 linter

[name/configurable]

Use lowerCamelCase for names of configurables and use the same name for the struct member as for the JSON string. (Declare the type and names on the same line.)
o2::framework::Configurable<int> pidFullPi{"pid-full-pi", -1, {"Produce PID information for the Pion mass hypothesis, overrides the automatic setup: the corresponding table can be set off (0) or on (1)"}};

Check failure on line 115 in Common/Tools/PID/pidTPCModule.h

View workflow job for this annotation

GitHub Actions / O2 linter

[name/configurable]

Use lowerCamelCase for names of configurables and use the same name for the struct member as for the JSON string. (Declare the type and names on the same line.)
o2::framework::Configurable<int> pidFullKa{"pid-full-ka", -1, {"Produce PID information for the Kaon mass hypothesis, overrides the automatic setup: the corresponding table can be set off (0) or on (1)"}};

Check failure on line 116 in Common/Tools/PID/pidTPCModule.h

View workflow job for this annotation

GitHub Actions / O2 linter

[name/configurable]

Use lowerCamelCase for names of configurables and use the same name for the struct member as for the JSON string. (Declare the type and names on the same line.)
o2::framework::Configurable<int> pidFullPr{"pid-full-pr", -1, {"Produce PID information for the Proton mass hypothesis, overrides the automatic setup: the corresponding table can be set off (0) or on (1)"}};

Check failure on line 117 in Common/Tools/PID/pidTPCModule.h

View workflow job for this annotation

GitHub Actions / O2 linter

[name/configurable]

Use lowerCamelCase for names of configurables and use the same name for the struct member as for the JSON string. (Declare the type and names on the same line.)
o2::framework::Configurable<int> pidFullDe{"pid-full-de", -1, {"Produce PID information for the Deuterons mass hypothesis, overrides the automatic setup: the corresponding table can be set off (0) or on (1)"}};

Check failure on line 118 in Common/Tools/PID/pidTPCModule.h

View workflow job for this annotation

GitHub Actions / O2 linter

[name/configurable]

Use lowerCamelCase for names of configurables and use the same name for the struct member as for the JSON string. (Declare the type and names on the same line.)
o2::framework::Configurable<int> pidFullTr{"pid-full-tr", -1, {"Produce PID information for the Triton mass hypothesis, overrides the automatic setup: the corresponding table can be set off (0) or on (1)"}};

Check failure on line 119 in Common/Tools/PID/pidTPCModule.h

View workflow job for this annotation

GitHub Actions / O2 linter

[name/configurable]

Use lowerCamelCase for names of configurables and use the same name for the struct member as for the JSON string. (Declare the type and names on the same line.)
o2::framework::Configurable<int> pidFullHe{"pid-full-he", -1, {"Produce PID information for the Helium3 mass hypothesis, overrides the automatic setup: the corresponding table can be set off (0) or on (1)"}};
o2::framework::Configurable<int> pidFullAl{"pid-full-al", -1, {"Produce PID information for the Alpha mass hypothesis, overrides the automatic setup: the corresponding table can be set off (0) or on (1)"}};
o2::framework::Configurable<int> pidTinyEl{"pid-tiny-el", -1, {"Produce PID information for the Electron mass hypothesis, overrides the automatic setup: the corresponding table can be set off (0) or on (1)"}};
Expand Down Expand Up @@ -510,6 +511,7 @@
float duration_network = 0;

std::vector<float> track_properties(track_prop_size);
std::vector<float> output_network; // output buffer, allocation is reused for all mass hypotheses
uint64_t counter_track_props = 0;
int loop_counter = 0;

Expand Down Expand Up @@ -595,20 +597,19 @@
track_properties[counter_track_props + 7] = hadronicRateBegin / 50.;
}
}
track_properties[counter_track_props + 8] = std::fmod(std::fmod(trk.phi(), 2 * M_PI) + 2 * M_PI, M_PI / 9.0);

Check failure on line 600 in Common/Tools/PID/pidTPCModule.h

View workflow job for this annotation

GitHub Actions / O2 linter

[two-pi-add-subtract]

Use RecoDecay::constrainAngle to restrict angle to a given range.
}
counter_track_props += input_dimensions;
}

auto start_network_eval = std::chrono::high_resolution_clock::now();
float* output_network = network.evalModel(track_properties);
network.evalModel(track_properties, output_network);
auto stop_network_eval = std::chrono::high_resolution_clock::now();
duration_network += std::chrono::duration<float, std::ratio<1, 1000000000>>(stop_network_eval - start_network_eval).count();
for (uint64_t k = 0; k < prediction_size; k += output_dimensions) {
for (int l = 0; l < output_dimensions; l++) {
network_prediction[k + l + prediction_size * loop_counter] = output_network[k + l];
}
if (output_network.size() != prediction_size) {
LOG(fatal) << "Network output size (" << output_network.size() << ") does not match the expected prediction size (" << prediction_size << ")";
}
std::copy(output_network.begin(), output_network.end(), network_prediction.begin() + prediction_size * loop_counter);

counter_track_props = 0;
loop_counter += 1;
Expand Down
6 changes: 3 additions & 3 deletions PWGDQ/Tasks/quarkoniaToHyperons.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -1773,7 +1773,7 @@ struct QuarkoniaToHyperons {
float k0shortScore = -1;
if (mlConfigurations.calculateK0ShortScores) {
// evaluate machine-learning scores
float* k0shortProbability = mlCustomModelK0Short.evalModel(inputFeatures);
const std::vector<float> k0shortProbability = mlCustomModelK0Short.evalModel(inputFeatures);
k0shortScore = k0shortProbability[1];
} else {
k0shortScore = v0.k0ShortBDTScore();
Expand All @@ -1788,7 +1788,7 @@ struct QuarkoniaToHyperons {
float lambdaScore = -1;
if (mlConfigurations.calculateLambdaScores) {
// evaluate machine-learning scores
float* lambdaProbability = mlCustomModelLambda.evalModel(inputFeatures);
const std::vector<float> lambdaProbability = mlCustomModelLambda.evalModel(inputFeatures);
lambdaScore = lambdaProbability[1];
} else {
lambdaScore = v0.lambdaBDTScore();
Expand All @@ -1803,7 +1803,7 @@ struct QuarkoniaToHyperons {
float antiLambdaScore = -1;
if (mlConfigurations.calculateAntiLambdaScores) {
// evaluate machine-learning scores
float* antilambdaProbability = mlCustomModelAntiLambda.evalModel(inputFeatures);
const std::vector<float> antilambdaProbability = mlCustomModelAntiLambda.evalModel(inputFeatures);
antiLambdaScore = antilambdaProbability[1];
} else {
antiLambdaScore = v0.antiLambdaBDTScore();
Expand Down
4 changes: 2 additions & 2 deletions PWGHF/TableProducer/candidateSelectorLcPidMl.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -307,12 +307,12 @@ struct HfCandidateSelectorLcPidMl {
std::vector<double> inputFeaturesD{trackParPos1.getPt(), trackPos1.dcaXY(), trackPos1.dcaZ(), trackParNeg.getPt(), trackNeg.dcaXY(), trackNeg.dcaZ(), trackParPos2.getPt(), trackPos2.dcaXY(), trackPos2.dcaZ()};
float scores[3] = {-1.f, -1.f, -1.f};
if (dataTypeML == 1) {
auto* scoresRaw = model.evalModel(inputFeaturesF);
const auto scoresRaw = model.evalModel(inputFeaturesF);
for (int iScore = 0; iScore < 3; ++iScore) {
scores[iScore] = scoresRaw[iScore];
}
} else if (dataTypeML == 11) {
auto* scoresRaw = model.evalModel(inputFeaturesD);
const auto scoresRaw = model.evalModel(inputFeaturesD);
for (int iScore = 0; iScore < 3; ++iScore) {
scores[iScore] = scoresRaw[iScore];
}
Expand Down
8 changes: 4 additions & 4 deletions PWGLF/TableProducer/Strangeness/lambdakzeromlselection.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -210,19 +210,19 @@ struct lambdakzeromlselection {

// calculate classifier output
if (PredictLambda) {
float* LambdaProbability = lambda_bdt.evalModel(inputFeatures);
const std::vector<float> LambdaProbability = lambda_bdt.evalModel(inputFeatures);
lambdaMLSelections(LambdaProbability[1]);
}
if (PredictGamma) {
float* GammaProbability = gamma_bdt.evalModel(inputFeatures);
const std::vector<float> GammaProbability = gamma_bdt.evalModel(inputFeatures);
gammaMLSelections(GammaProbability[1]);
}
if (PredictAntiLambda) {
float* AntiLambdaProbability = antilambda_bdt.evalModel(inputFeatures);
const std::vector<float> AntiLambdaProbability = antilambda_bdt.evalModel(inputFeatures);
antiLambdaMLSelections(AntiLambdaProbability[1]);
}
if (PredictKZeroShort) {
float* KZeroShortProbability = kzeroshort_bdt.evalModel(inputFeatures);
const std::vector<float> KZeroShortProbability = kzeroshort_bdt.evalModel(inputFeatures);
kzeroShortMLSelections(KZeroShortProbability[1]);
}
}
Expand Down
2 changes: 1 addition & 1 deletion PWGLF/TableProducer/Strangeness/strangenessbuilder.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -1006,7 +1006,7 @@ struct StrangenessBuilder {
AvgPA, // 6. Avg Pointing Angle
static_cast<float>(v0zRanks[ic])}; // 7. V0 Vtx z Rank

float* BDTProbability = deduplication_bdt.evalModel(inputFeatures);
const std::vector<float> BDTProbability = deduplication_bdt.evalModel(inputFeatures);

if (BDTProbability[1] > bestMLScore) {
bestMLScore = BDTProbability[1];
Expand Down
6 changes: 3 additions & 3 deletions PWGLF/Tasks/Strangeness/derivedlambdakzeroanalysis.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -1877,7 +1877,7 @@ struct derivedlambdakzeroanalysis {
float k0shortScore = -1;
if (mlConfigurations.calculateK0ShortScores) {
// evaluate machine-learning scores
float* k0shortProbability = mlCustomModelK0Short.evalModel(inputFeatures);
const std::vector<float> k0shortProbability = mlCustomModelK0Short.evalModel(inputFeatures);
k0shortScore = k0shortProbability[1];
} else {
k0shortScore = v0.k0ShortBDTScore();
Expand All @@ -1892,7 +1892,7 @@ struct derivedlambdakzeroanalysis {
float lambdaScore = -1;
if (mlConfigurations.calculateLambdaScores) {
// evaluate machine-learning scores
float* lambdaProbability = mlCustomModelLambda.evalModel(inputFeatures);
const std::vector<float> lambdaProbability = mlCustomModelLambda.evalModel(inputFeatures);
lambdaScore = lambdaProbability[1];
} else {
lambdaScore = v0.lambdaBDTScore();
Expand All @@ -1907,7 +1907,7 @@ struct derivedlambdakzeroanalysis {
float antiLambdaScore = -1;
if (mlConfigurations.calculateAntiLambdaScores) {
// evaluate machine-learning scores
float* antilambdaProbability = mlCustomModelAntiLambda.evalModel(inputFeatures);
const std::vector<float> antilambdaProbability = mlCustomModelAntiLambda.evalModel(inputFeatures);
antiLambdaScore = antilambdaProbability[1];
} else {
antiLambdaScore = v0.antiLambdaBDTScore();
Expand Down
25 changes: 19 additions & 6 deletions Tools/ML/MlResponse.h
Original file line number Diff line number Diff line change
Expand Up @@ -190,8 +190,16 @@ class MlResponse
LOG(fatal) << "Number of input nodes in the model " << mPaths[nModel] << " is different from the number of input features to be tested (" << numInputNodes << " vs " << numInputFeatures << ")";
}

TypeOutputScore* outputPtr = mModels[nModel].template evalModel<TypeOutputScore>(input);
return std::vector<TypeOutputScore>{outputPtr, outputPtr + mNClasses};
// evalModel returns an owning copy of the (last) output tensor of the model
std::vector<TypeOutputScore> output = mModels[nModel].template evalModel<TypeOutputScore>(input);
if (output.size() < mNClasses) {
LOG(fatal) << "Model " << mPaths[nModel] << " returned " << output.size() << " scores, but " << static_cast<int>(mNClasses) << " classes are expected. Please check your configurables.";
}
if (output.size() > mNClasses) {
// keep only the first mNClasses scores (e.g. single-candidate probabilities of a multi-output model)
output.resize(mNClasses);
}
return output;
}

/// Get vector with model predictions for a batch of candidates
Expand Down Expand Up @@ -221,11 +229,16 @@ class MlResponse
LOG(fatal) << "Number of input nodes in the model " << mPaths[nModel] << " differs from features per row (" << numInputNodes << " vs " << featuresPerRow << ")";
}

TypeOutputScore* outputPtr = mModels[nModel].template evalModel<TypeOutputScore>(input);
if (outputPtr == nullptr) {
LOG(fatal) << "Batched model evaluation failed for model " << mPaths[nModel];
std::vector<TypeOutputScore> output = mModels[nModel].template evalModel<TypeOutputScore>(input);
const std::size_t expectedOutputSize = nRows * mNClasses;
if (output.size() < expectedOutputSize) {
LOG(fatal) << "Model " << mPaths[nModel] << " returned " << output.size() << " scores, but " << expectedOutputSize << " scores are expected for " << nRows << " rows and " << static_cast<int>(mNClasses) << " classes. Please check your configurables.";
}
if (output.size() > expectedOutputSize) {
// keep only the first scores (e.g. batched probabilities of a multi-output model)
output.resize(expectedOutputSize);
}
return std::vector<TypeOutputScore>{outputPtr, outputPtr + nRows * mNClasses};
return output;
}

/// ML selections
Expand Down
99 changes: 99 additions & 0 deletions Tools/ML/model.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,11 @@
#include <onnxruntime_c_api.h>
#include <onnxruntime_cxx_api.h>

#include <algorithm>
#include <cassert>
#include <cstddef>
#include <cstdint>
#include <iterator>
#include <memory>
#include <sstream>
#include <string>
Expand All @@ -42,6 +44,9 @@ namespace ml

std::string OnnxModel::printShape(const std::vector<int64_t>& v)
{
if (v.empty()) {
return "[]";
}
std::stringstream ss("");
for (std::size_t i = 0; i < v.size() - 1; i++)
ss << v[i] << "x";
Expand Down Expand Up @@ -90,7 +95,12 @@ void OnnxModel::initModel(const std::string& localPath, const bool enableOptimiz

mEnv = std::make_shared<Ort::Env>(ORT_LOGGING_LEVEL_WARNING, "onnx-model");
mSession = std::make_shared<Ort::Session>(*mEnv, modelPath.c_str(), sessionOptions);
mMemInfo = Ort::MemoryInfo::CreateCpu(OrtAllocatorType::OrtArenaAllocator, OrtMemType::OrtMemTypeDefault);

mInputNames.clear();
mInputShapes.clear();
mOutputNames.clear();
mOutputShapes.clear();
Ort::AllocatorWithDefaultOptions const tmpAllocator;
for (std::size_t i = 0; i < mSession->GetInputCount(); ++i) {
mInputNames.push_back(mSession->GetInputNameAllocated(i, tmpAllocator).get());
Expand Down Expand Up @@ -122,6 +132,95 @@ void OnnxModel::initModel(const std::string& localPath, const bool enableOptimiz
LOG(info) << "--- Model initialized! ---";
}

std::vector<int64_t> OnnxModel::inferInputShape(const std::size_t iinput, const int64_t size) const
{
const std::vector<int64_t>& modelShape = mInputShapes[iinput];

// Rank-1 input: the whole vector is the tensor
if (modelShape.size() < 2) {
return {size};
}

// Product of all non-batch dimensions; dynamic dimensions (< 0) cannot be inferred
int64_t totalSize = 1;
bool hasDynamicDim = false;
for (std::size_t idim = 1; idim < modelShape.size(); idim++) {
if (modelShape[idim] < 0) {
hasDynamicDim = true;
} else {
totalSize *= modelShape[idim];
}
}

if (hasDynamicDim) {
if (modelShape.size() == 2) {
// [batch, features] with dynamic feature dimension: interpret the vector as a single sample
return {1, size};
}
LOG(fatal) << "Input " << iinput << " (" << mInputNames[iinput] << ") has dynamic non-batch dimensions (" << printShape(modelShape) << "), the tensor shape cannot be inferred from a flat vector. Please provide std::vector<Ort::Value> inputs instead.";
}

if (totalSize <= 0 || size % totalSize != 0) {
LOG(fatal) << "Size of the input vector (" << size << ") is not a multiple of the model input size (" << totalSize << ") for input " << iinput << " (" << mInputNames[iinput] << ", shape " << printShape(modelShape) << ")";
}

std::vector<int64_t> inputShape;
inputShape.reserve(modelShape.size());
inputShape.push_back(size / totalSize);
for (std::size_t idim = 1; idim < modelShape.size(); idim++) {
inputShape.push_back(modelShape[idim]);
}
return inputShape;
}

std::vector<Ort::Value> OnnxModel::evalModelRaw(std::vector<Ort::Value>& input)
{
if (!mSession) {
LOG(fatal) << "OnnxModel::evalModel called before initModel()";
}
if (input.size() != mInputNames.size()) {
LOG(fatal) << "Number of input tensors (" << input.size() << ") does not agree with the number of model inputs (" << mInputNames.size() << ")";
}
for (std::size_t i = 0; i < input.size(); i++) {
LOG(debug) << "Input tensor " << i << " shape: " << printShape(input[i].GetTensorTypeAndShapeInfo().GetShape());
}

std::vector<const char*> inputNamesChar(mInputNames.size(), nullptr);
std::transform(std::begin(mInputNames), std::end(mInputNames), std::begin(inputNamesChar),
[](const std::string& str) { return str.c_str(); });

std::vector<const char*> outputNamesChar(mOutputNames.size(), nullptr);
std::transform(std::begin(mOutputNames), std::end(mOutputNames), std::begin(outputNamesChar),
[](const std::string& str) { return str.c_str(); });

std::vector<Ort::Value> outputTensors;
try {
const Ort::RunOptions runOptions;
outputTensors = mSession->Run(runOptions, inputNamesChar.data(), input.data(), input.size(), outputNamesChar.data(), outputNamesChar.size());
} catch (const Ort::Exception& exception) {
LOG(fatal) << "Error running model inference: " << exception.what();
}

LOG(debug) << "Number of output tensors: " << outputTensors.size();
if (outputTensors.size() != mOutputNames.size()) {
LOG(fatal) << "Number of output tensors: " << outputTensors.size() << " does not agree with the model specified size: " << mOutputNames.size();
}
for (std::size_t i = 0; i < outputTensors.size(); i++) {
const std::vector<int64_t> shape = outputTensors[i].GetTensorTypeAndShapeInfo().GetShape();
LOG(debug) << "Output tensor " << i << " shape: " << printShape(shape);
bool shapeOk = (shape.size() == mOutputShapes[i].size());
for (std::size_t idim = 0; shapeOk && idim < shape.size(); idim++) {
// dynamic dimensions of the model (< 0) can take any value
shapeOk = (mOutputShapes[i][idim] < 0) || (shape[idim] == mOutputShapes[i][idim]);
}
if (!shapeOk) {
LOG(fatal) << "Shape of output tensor " << i << " does not agree with model specification! Output: " << printShape(shape) << " model: " << printShape(mOutputShapes[i]);
}
}

return outputTensors;
}

void OnnxModel::setActiveThreads(const int threads)
{
activeThreads = threads;
Expand Down
Loading
Loading