Skip to content

Commit b983840

Browse files
committed
Adjusting for better compute performance
1 parent 8541987 commit b983840

2 files changed

Lines changed: 72 additions & 34 deletions

File tree

‎Tools/ML/model.cxx‎

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

29-
#include <algorithm>
3029
#include <cassert>
3130
#include <cstddef>
3231
#include <cstdint>
33-
#include <iterator>
3432
#include <memory>
3533
#include <sstream>
3634
#include <string>
@@ -97,6 +95,8 @@ void OnnxModel::initModel(const std::string& localPath, const bool enableOptimiz
9795
mSession = std::make_shared<Ort::Session>(*mEnv, modelPath.c_str(), sessionOptions);
9896
mMemInfo = Ort::MemoryInfo::CreateCpu(OrtAllocatorType::OrtArenaAllocator, OrtMemType::OrtMemTypeDefault);
9997

98+
mInputNamesChar.clear();
99+
mOutputNamesChar.clear();
100100
mInputNames.clear();
101101
mInputShapes.clear();
102102
mOutputNames.clear();
@@ -114,6 +114,14 @@ void OnnxModel::initModel(const std::string& localPath, const bool enableOptimiz
114114
for (std::size_t i = 0; i < mSession->GetOutputCount(); ++i) {
115115
mOutputShapes.emplace_back(mSession->GetOutputTypeInfo(i).GetTensorTypeAndShapeInfo().GetShape());
116116
}
117+
mInputNamesChar.reserve(mInputNames.size());
118+
for (const auto& name : mInputNames) {
119+
mInputNamesChar.push_back(name.c_str());
120+
}
121+
mOutputNamesChar.reserve(mOutputNames.size());
122+
for (const auto& name : mOutputNames) {
123+
mOutputNamesChar.push_back(name.c_str());
124+
}
117125
LOG(info) << "Input Nodes:";
118126
for (std::size_t i = 0; i < mInputNames.size(); i++) {
119127
LOG(info) << "\t" << mInputNames[i] << " : " << printShape(mInputShapes[i]);
@@ -173,7 +181,7 @@ std::vector<int64_t> OnnxModel::inferInputShape(const std::size_t iinput, const
173181
return inputShape;
174182
}
175183

176-
std::vector<Ort::Value> OnnxModel::evalModelRaw(std::vector<Ort::Value>& input)
184+
void OnnxModel::checkInput(const std::vector<Ort::Value>& input) const
177185
{
178186
if (!mSession) {
179187
LOG(fatal) << "OnnxModel::evalModel called before initModel()";
@@ -184,19 +192,15 @@ std::vector<Ort::Value> OnnxModel::evalModelRaw(std::vector<Ort::Value>& input)
184192
for (std::size_t i = 0; i < input.size(); i++) {
185193
LOG(debug) << "Input tensor " << i << " shape: " << printShape(input[i].GetTensorTypeAndShapeInfo().GetShape());
186194
}
195+
}
187196

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-
197+
std::vector<Ort::Value> OnnxModel::evalModelRaw(std::vector<Ort::Value>& input)
198+
{
199+
checkInput(input);
196200
std::vector<Ort::Value> outputTensors;
197201
try {
198-
const Ort::RunOptions runOptions;
199-
outputTensors = mSession->Run(runOptions, inputNamesChar.data(), input.data(), input.size(), outputNamesChar.data(), outputNamesChar.size());
202+
const Ort::RunOptions runOptions{nullptr};
203+
outputTensors = mSession->Run(runOptions, mInputNamesChar.data(), input.data(), input.size(), mOutputNamesChar.data(), mOutputNamesChar.size());
200204
} catch (const Ort::Exception& exception) {
201205
LOG(fatal) << "Error running model inference: " << exception.what();
202206
}
@@ -206,21 +210,44 @@ std::vector<Ort::Value> OnnxModel::evalModelRaw(std::vector<Ort::Value>& input)
206210
LOG(fatal) << "Number of output tensors: " << outputTensors.size() << " does not agree with the model specified size: " << mOutputNames.size();
207211
}
208212
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-
}
213+
checkOutput(outputTensors[i], i);
219214
}
220215

221216
return outputTensors;
222217
}
223218

219+
Ort::Value OnnxModel::evalModelLast(std::vector<Ort::Value>& input)
220+
{
221+
checkInput(input);
222+
if (mOutputNamesChar.empty()) {
223+
LOG(fatal) << "Model has no outputs";
224+
}
225+
Ort::Value output{nullptr};
226+
try {
227+
// A null RunOptions uses the runtime defaults without allocating options per call.
228+
const Ort::RunOptions runOptions{nullptr};
229+
mSession->Run(runOptions, mInputNamesChar.data(), input.data(), input.size(), &mOutputNamesChar.back(), &output, 1);
230+
} catch (const Ort::Exception& exception) {
231+
LOG(fatal) << "Error running model inference: " << exception.what();
232+
}
233+
checkOutput(output, mOutputShapes.size() - 1);
234+
return output;
235+
}
236+
237+
void OnnxModel::checkOutput(const Ort::Value& tensor, const std::size_t index) const
238+
{
239+
const std::vector<int64_t> shape = tensor.GetTensorTypeAndShapeInfo().GetShape();
240+
LOG(debug) << "Output tensor " << index << " shape: " << printShape(shape);
241+
bool shapeOk = (shape.size() == mOutputShapes[index].size());
242+
for (std::size_t idim = 0; shapeOk && idim < shape.size(); idim++) {
243+
// Dynamic dimensions of the model (< 0) can take any value.
244+
shapeOk = (mOutputShapes[index][idim] < 0) || (shape[idim] == mOutputShapes[index][idim]);
245+
}
246+
if (!shapeOk) {
247+
LOG(fatal) << "Shape of output tensor " << index << " does not agree with model specification! Output: " << printShape(shape) << " model: " << printShape(mOutputShapes[index]);
248+
}
249+
}
250+
224251
void OnnxModel::setActiveThreads(const int threads)
225252
{
226253
activeThreads = threads;

‎Tools/ML/model.h‎

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,8 @@ namespace ml
4545
/// for as long as the returned vector is alive.
4646
/// - evalModel(input): convenience wrapper returning a copy of the *last* model output as std::vector<T>.
4747
/// - evalModel(input, output): same as above but writes into a caller-provided vector. The vector is
48-
/// resized as needed and its capacity is reused across calls, which avoids per-call allocations in
49-
/// hot loops (e.g. batched inference).
48+
/// resized as needed and its capacity is reused across calls, which avoids repeated allocations of the result buffer.
49+
/// Only the last model output is requested from ONNX Runtime.
5050
///
5151
/// Inputs given as std::vector<T> are wrapped in an Ort::Value without copying. The input vector must therefore
5252
/// stay alive until the call returns (which is always the case for the synchronous calls provided here).
@@ -87,8 +87,8 @@ class OnnxModel
8787
template <typename T>
8888
void evalModel(std::vector<Ort::Value>& input, std::vector<T>& output)
8989
{
90-
const std::vector<Ort::Value> outputTensors = evalModelRaw(input);
91-
copyLastOutput<T>(outputTensors, output);
90+
const Ort::Value tensor = evalModelLast(input);
91+
copyOutput<T>(tensor, output);
9292
}
9393

9494
/// Run a single-input model on a flat vector of features (batches are inferred from the model input shape)
@@ -178,6 +178,11 @@ class OnnxModel
178178
std::vector<std::string> mOutputNames;
179179
std::vector<std::vector<int64_t>> mOutputShapes;
180180

181+
// Pointers into the name vectors above, populated only after all names are loaded.
182+
// Moving the model transfers the backing vectors without invalidating these pointers.
183+
std::vector<const char*> mInputNamesChar;
184+
std::vector<const char*> mOutputNamesChar;
185+
181186
// Environment settings
182187
std::string modelPath;
183188
int activeThreads = 0;
@@ -197,20 +202,26 @@ class OnnxModel
197202
tensors.emplace_back(Ort::Value::CreateTensor<T>(mMemInfo, data.data(), data.size(), inputShape.data(), inputShape.size()));
198203
}
199204

200-
/// Copy the content of the last output tensor into output (reusing its allocation)
205+
/// Request only the last output, keeping its tensor alive until the caller has copied it
206+
Ort::Value evalModelLast(std::vector<Ort::Value>& input);
207+
void checkInput(const std::vector<Ort::Value>& input) const;
208+
void checkOutput(const Ort::Value& tensor, std::size_t index) const;
209+
210+
/// Copy the content of an output tensor into output (reusing its allocation)
201211
template <typename T>
202-
void copyLastOutput(const std::vector<Ort::Value>& outputTensors, std::vector<T>& output) const
212+
void copyOutput(const Ort::Value& tensor, std::vector<T>& output) const
203213
{
204-
if (outputTensors.empty()) {
205-
LOG(fatal) << "Model returned no output tensors";
206-
}
207-
const Ort::Value& tensor = outputTensors.back();
208214
const auto info = tensor.GetTensorTypeAndShapeInfo();
209215
if (info.GetElementType() != Ort::TypeToTensorType<T>::type) {
210216
LOG(fatal) << "Requested output type (ONNX type id " << static_cast<int>(Ort::TypeToTensorType<T>::type) << ") does not match the model output tensor type (ONNX type id " << static_cast<int>(info.GetElementType()) << ")";
211217
}
212218
const T* data = tensor.GetTensorData<T>();
213-
output.assign(data, data + info.GetElementCount());
219+
const auto size = info.GetElementCount();
220+
if (size == 0) {
221+
output.clear();
222+
} else {
223+
output.assign(data, data + size);
224+
}
214225
}
215226

216227
// Internal function for printing the shape of tensors

0 commit comments

Comments
 (0)