Skip to content

Commit 8075964

Browse files
committed
Updating ML model class with safe copies, avoiding memory overwrites
1 parent 06d94cc commit 8075964

11 files changed

Lines changed: 247 additions & 87 deletions

File tree

CODEOWNERS

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@
7878
/PWGUD @alibuild @amatyja @rolavick
7979
/PWGJE @alibuild @nzardosh @fjonasALICE @jaimenorman @mhemmer-cern
8080
/Tools/PIDML @alibuild @saganatt
81-
/Tools/ML @alibuild @fcatalan92 @fmazzasc
81+
/Tools/ML @alibuild @fcatalan92 @fmazzasc @ChSonnabend
8282
/Tutorials/PWGCF @alibuild @jgrosseo @victor-gonzalez @zchochul
8383
/Tutorials/PWGDQ @alibuild @iarsene @mcoquet642 @XiaozhiBai @mguilbau
8484
/Tutorials/PWGEM @alibuild @mikesas @rbailhac @dsekihat @ivorobye @feisenhu

Common/Tools/PID/pidTPCModule.h

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
#include <TRandom.h>
4747
#include <TString.h>
4848

49+
#include <algorithm>
4950
#include <chrono>
5051
#include <cstddef>
5152
#include <cstdint>
@@ -510,6 +511,7 @@ class pidTPCModule
510511
float duration_network = 0;
511512

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

@@ -601,14 +603,13 @@ class pidTPCModule
601603
}
602604

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

613614
counter_track_props = 0;
614615
loop_counter += 1;

PWGDQ/Tasks/quarkoniaToHyperons.cxx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1773,7 +1773,7 @@ struct QuarkoniaToHyperons {
17731773
float k0shortScore = -1;
17741774
if (mlConfigurations.calculateK0ShortScores) {
17751775
// evaluate machine-learning scores
1776-
float* k0shortProbability = mlCustomModelK0Short.evalModel(inputFeatures);
1776+
const std::vector<float> k0shortProbability = mlCustomModelK0Short.evalModel(inputFeatures);
17771777
k0shortScore = k0shortProbability[1];
17781778
} else {
17791779
k0shortScore = v0.k0ShortBDTScore();
@@ -1788,7 +1788,7 @@ struct QuarkoniaToHyperons {
17881788
float lambdaScore = -1;
17891789
if (mlConfigurations.calculateLambdaScores) {
17901790
// evaluate machine-learning scores
1791-
float* lambdaProbability = mlCustomModelLambda.evalModel(inputFeatures);
1791+
const std::vector<float> lambdaProbability = mlCustomModelLambda.evalModel(inputFeatures);
17921792
lambdaScore = lambdaProbability[1];
17931793
} else {
17941794
lambdaScore = v0.lambdaBDTScore();
@@ -1803,7 +1803,7 @@ struct QuarkoniaToHyperons {
18031803
float antiLambdaScore = -1;
18041804
if (mlConfigurations.calculateAntiLambdaScores) {
18051805
// evaluate machine-learning scores
1806-
float* antilambdaProbability = mlCustomModelAntiLambda.evalModel(inputFeatures);
1806+
const std::vector<float> antilambdaProbability = mlCustomModelAntiLambda.evalModel(inputFeatures);
18071807
antiLambdaScore = antilambdaProbability[1];
18081808
} else {
18091809
antiLambdaScore = v0.antiLambdaBDTScore();

PWGHF/TableProducer/candidateSelectorLcPidMl.cxx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -307,12 +307,12 @@ struct HfCandidateSelectorLcPidMl {
307307
std::vector<double> inputFeaturesD{trackParPos1.getPt(), trackPos1.dcaXY(), trackPos1.dcaZ(), trackParNeg.getPt(), trackNeg.dcaXY(), trackNeg.dcaZ(), trackParPos2.getPt(), trackPos2.dcaXY(), trackPos2.dcaZ()};
308308
float scores[3] = {-1.f, -1.f, -1.f};
309309
if (dataTypeML == 1) {
310-
auto* scoresRaw = model.evalModel(inputFeaturesF);
310+
const auto scoresRaw = model.evalModel(inputFeaturesF);
311311
for (int iScore = 0; iScore < 3; ++iScore) {
312312
scores[iScore] = scoresRaw[iScore];
313313
}
314314
} else if (dataTypeML == 11) {
315-
auto* scoresRaw = model.evalModel(inputFeaturesD);
315+
const auto scoresRaw = model.evalModel(inputFeaturesD);
316316
for (int iScore = 0; iScore < 3; ++iScore) {
317317
scores[iScore] = scoresRaw[iScore];
318318
}

PWGLF/TableProducer/Strangeness/lambdakzeromlselection.cxx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -210,19 +210,19 @@ struct lambdakzeromlselection {
210210

211211
// calculate classifier output
212212
if (PredictLambda) {
213-
float* LambdaProbability = lambda_bdt.evalModel(inputFeatures);
213+
const std::vector<float> LambdaProbability = lambda_bdt.evalModel(inputFeatures);
214214
lambdaMLSelections(LambdaProbability[1]);
215215
}
216216
if (PredictGamma) {
217-
float* GammaProbability = gamma_bdt.evalModel(inputFeatures);
217+
const std::vector<float> GammaProbability = gamma_bdt.evalModel(inputFeatures);
218218
gammaMLSelections(GammaProbability[1]);
219219
}
220220
if (PredictAntiLambda) {
221-
float* AntiLambdaProbability = antilambda_bdt.evalModel(inputFeatures);
221+
const std::vector<float> AntiLambdaProbability = antilambda_bdt.evalModel(inputFeatures);
222222
antiLambdaMLSelections(AntiLambdaProbability[1]);
223223
}
224224
if (PredictKZeroShort) {
225-
float* KZeroShortProbability = kzeroshort_bdt.evalModel(inputFeatures);
225+
const std::vector<float> KZeroShortProbability = kzeroshort_bdt.evalModel(inputFeatures);
226226
kzeroShortMLSelections(KZeroShortProbability[1]);
227227
}
228228
}

PWGLF/TableProducer/Strangeness/strangenessbuilder.cxx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1006,7 +1006,7 @@ struct StrangenessBuilder {
10061006
AvgPA, // 6. Avg Pointing Angle
10071007
static_cast<float>(v0zRanks[ic])}; // 7. V0 Vtx z Rank
10081008

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

10111011
if (BDTProbability[1] > bestMLScore) {
10121012
bestMLScore = BDTProbability[1];

PWGLF/Tasks/Strangeness/derivedlambdakzeroanalysis.cxx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1877,7 +1877,7 @@ struct derivedlambdakzeroanalysis {
18771877
float k0shortScore = -1;
18781878
if (mlConfigurations.calculateK0ShortScores) {
18791879
// evaluate machine-learning scores
1880-
float* k0shortProbability = mlCustomModelK0Short.evalModel(inputFeatures);
1880+
const std::vector<float> k0shortProbability = mlCustomModelK0Short.evalModel(inputFeatures);
18811881
k0shortScore = k0shortProbability[1];
18821882
} else {
18831883
k0shortScore = v0.k0ShortBDTScore();
@@ -1892,7 +1892,7 @@ struct derivedlambdakzeroanalysis {
18921892
float lambdaScore = -1;
18931893
if (mlConfigurations.calculateLambdaScores) {
18941894
// evaluate machine-learning scores
1895-
float* lambdaProbability = mlCustomModelLambda.evalModel(inputFeatures);
1895+
const std::vector<float> lambdaProbability = mlCustomModelLambda.evalModel(inputFeatures);
18961896
lambdaScore = lambdaProbability[1];
18971897
} else {
18981898
lambdaScore = v0.lambdaBDTScore();
@@ -1907,7 +1907,7 @@ struct derivedlambdakzeroanalysis {
19071907
float antiLambdaScore = -1;
19081908
if (mlConfigurations.calculateAntiLambdaScores) {
19091909
// evaluate machine-learning scores
1910-
float* antilambdaProbability = mlCustomModelAntiLambda.evalModel(inputFeatures);
1910+
const std::vector<float> antilambdaProbability = mlCustomModelAntiLambda.evalModel(inputFeatures);
19111911
antiLambdaScore = antilambdaProbability[1];
19121912
} else {
19131913
antiLambdaScore = v0.antiLambdaBDTScore();

Tools/ML/MlResponse.h

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -190,8 +190,16 @@ class MlResponse
190190
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 << ")";
191191
}
192192

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

197205
/// ML selections

Tools/ML/model.cxx

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,11 @@
2626
#include <onnxruntime_c_api.h>
2727
#include <onnxruntime_cxx_api.h>
2828

29+
#include <algorithm>
2930
#include <cassert>
3031
#include <cstddef>
3132
#include <cstdint>
33+
#include <iterator>
3234
#include <memory>
3335
#include <sstream>
3436
#include <string>
@@ -42,6 +44,9 @@ namespace ml
4244

4345
std::string OnnxModel::printShape(const std::vector<int64_t>& v)
4446
{
47+
if (v.empty()) {
48+
return "[]";
49+
}
4550
std::stringstream ss("");
4651
for (std::size_t i = 0; i < v.size() - 1; i++)
4752
ss << v[i] << "x";
@@ -90,7 +95,12 @@ void OnnxModel::initModel(const std::string& localPath, const bool enableOptimiz
9095

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

100+
mInputNames.clear();
101+
mInputShapes.clear();
102+
mOutputNames.clear();
103+
mOutputShapes.clear();
94104
Ort::AllocatorWithDefaultOptions const tmpAllocator;
95105
for (std::size_t i = 0; i < mSession->GetInputCount(); ++i) {
96106
mInputNames.push_back(mSession->GetInputNameAllocated(i, tmpAllocator).get());
@@ -122,6 +132,95 @@ void OnnxModel::initModel(const std::string& localPath, const bool enableOptimiz
122132
LOG(info) << "--- Model initialized! ---";
123133
}
124134

135+
std::vector<int64_t> OnnxModel::inferInputShape(const std::size_t iinput, const int64_t size) const
136+
{
137+
const std::vector<int64_t>& modelShape = mInputShapes[iinput];
138+
139+
// Rank-1 input: the whole vector is the tensor
140+
if (modelShape.size() < 2) {
141+
return {size};
142+
}
143+
144+
// Product of all non-batch dimensions; dynamic dimensions (< 0) cannot be inferred
145+
int64_t totalSize = 1;
146+
bool hasDynamicDim = false;
147+
for (std::size_t idim = 1; idim < modelShape.size(); idim++) {
148+
if (modelShape[idim] < 0) {
149+
hasDynamicDim = true;
150+
} else {
151+
totalSize *= modelShape[idim];
152+
}
153+
}
154+
155+
if (hasDynamicDim) {
156+
if (modelShape.size() == 2) {
157+
// [batch, features] with dynamic feature dimension: interpret the vector as a single sample
158+
return {1, size};
159+
}
160+
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.";
161+
}
162+
163+
if (totalSize <= 0 || size % totalSize != 0) {
164+
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) << ")";
165+
}
166+
167+
std::vector<int64_t> inputShape;
168+
inputShape.reserve(modelShape.size());
169+
inputShape.push_back(size / totalSize);
170+
for (std::size_t idim = 1; idim < modelShape.size(); idim++) {
171+
inputShape.push_back(modelShape[idim]);
172+
}
173+
return inputShape;
174+
}
175+
176+
std::vector<Ort::Value> OnnxModel::evalModelRaw(std::vector<Ort::Value>& input)
177+
{
178+
if (!mSession) {
179+
LOG(fatal) << "OnnxModel::evalModel called before initModel()";
180+
}
181+
if (input.size() != mInputNames.size()) {
182+
LOG(fatal) << "Number of input tensors (" << input.size() << ") does not agree with the number of model inputs (" << mInputNames.size() << ")";
183+
}
184+
for (std::size_t i = 0; i < input.size(); i++) {
185+
LOG(debug) << "Input tensor " << i << " shape: " << printShape(input[i].GetTensorTypeAndShapeInfo().GetShape());
186+
}
187+
188+
std::vector<const char*> inputNamesChar(mInputNames.size(), nullptr);
189+
std::transform(std::begin(mInputNames), std::end(mInputNames), std::begin(inputNamesChar),
190+
[](const std::string& str) { return str.c_str(); });
191+
192+
std::vector<const char*> outputNamesChar(mOutputNames.size(), nullptr);
193+
std::transform(std::begin(mOutputNames), std::end(mOutputNames), std::begin(outputNamesChar),
194+
[](const std::string& str) { return str.c_str(); });
195+
196+
std::vector<Ort::Value> outputTensors;
197+
try {
198+
const Ort::RunOptions runOptions;
199+
outputTensors = mSession->Run(runOptions, inputNamesChar.data(), input.data(), input.size(), outputNamesChar.data(), outputNamesChar.size());
200+
} catch (const Ort::Exception& exception) {
201+
LOG(fatal) << "Error running model inference: " << exception.what();
202+
}
203+
204+
LOG(debug) << "Number of output tensors: " << outputTensors.size();
205+
if (outputTensors.size() != mOutputNames.size()) {
206+
LOG(fatal) << "Number of output tensors: " << outputTensors.size() << " does not agree with the model specified size: " << mOutputNames.size();
207+
}
208+
for (std::size_t i = 0; i < outputTensors.size(); i++) {
209+
const std::vector<int64_t> shape = outputTensors[i].GetTensorTypeAndShapeInfo().GetShape();
210+
LOG(debug) << "Output tensor " << i << " shape: " << printShape(shape);
211+
bool shapeOk = (shape.size() == mOutputShapes[i].size());
212+
for (std::size_t idim = 0; shapeOk && idim < shape.size(); idim++) {
213+
// dynamic dimensions of the model (< 0) can take any value
214+
shapeOk = (mOutputShapes[i][idim] < 0) || (shape[idim] == mOutputShapes[i][idim]);
215+
}
216+
if (!shapeOk) {
217+
LOG(fatal) << "Shape of output tensor " << i << " does not agree with model specification! Output: " << printShape(shape) << " model: " << printShape(mOutputShapes[i]);
218+
}
219+
}
220+
221+
return outputTensors;
222+
}
223+
125224
void OnnxModel::setActiveThreads(const int threads)
126225
{
127226
activeThreads = threads;

0 commit comments

Comments
 (0)