From c90ec4f49ccf9198965cf777f9c60c4b307da056 Mon Sep 17 00:00:00 2001 From: Christian Sonnabend Date: Mon, 14 Sep 2026 10:07:04 +0200 Subject: [PATCH 1/5] ORT CI tests --- Common/ML/CMakeLists.txt | 35 ++ .../test/onnxruntime-inference/CMakeLists.txt | 23 ++ Common/ML/test/onnxruntime-inference/net.onnx | Bin 0 -> 51444 bytes .../onnxruntime_ep_inference.cxx | 337 ++++++++++++++++++ .../run-local-onnxruntime-inference-test.sh | 134 +++++++ .../run-onnxruntime-all-eps.sh | 41 +++ .../run-onnxruntime-cpu.sh | 17 + .../run-onnxruntime-cuda.sh | 50 +++ .../run-onnxruntime-migraphx.sh | 18 + .../run-onnxruntime-tensorrt.sh | 18 + 10 files changed, 673 insertions(+) create mode 100644 Common/ML/test/onnxruntime-inference/CMakeLists.txt create mode 100644 Common/ML/test/onnxruntime-inference/net.onnx create mode 100644 Common/ML/test/onnxruntime-inference/onnxruntime_ep_inference.cxx create mode 100755 Common/ML/test/onnxruntime-inference/run-local-onnxruntime-inference-test.sh create mode 100755 Common/ML/test/onnxruntime-inference/run-onnxruntime-all-eps.sh create mode 100755 Common/ML/test/onnxruntime-inference/run-onnxruntime-cpu.sh create mode 100755 Common/ML/test/onnxruntime-inference/run-onnxruntime-cuda.sh create mode 100755 Common/ML/test/onnxruntime-inference/run-onnxruntime-migraphx.sh create mode 100755 Common/ML/test/onnxruntime-inference/run-onnxruntime-tensorrt.sh diff --git a/Common/ML/CMakeLists.txt b/Common/ML/CMakeLists.txt index 0ed52e1a23e20..d3ef2515a188c 100644 --- a/Common/ML/CMakeLists.txt +++ b/Common/ML/CMakeLists.txt @@ -20,3 +20,38 @@ target_compile_definitions(${targetName} PRIVATE $<$:ORT_CUDA_BUILD> $<$:ORT_MIGRAPHX_BUILD> $<$:ORT_TENSORRT_BUILD>) + +if(BUILD_TESTING) + set(ONNXRUNTIME_INFERENCE_TEST_DIR ${CMAKE_CURRENT_SOURCE_DIR}/test/onnxruntime-inference) + + o2_add_executable(onnxruntime-ep-inference + SOURCES ${ONNXRUNTIME_INFERENCE_TEST_DIR}/onnxruntime_ep_inference.cxx + COMPONENT_NAME ML + IS_TEST + NO_INSTALL + TARGETVARNAME onnxruntimeInferenceTestTarget + PUBLIC_LINK_LIBRARIES onnxruntime::onnxruntime) + target_compile_features(${onnxruntimeInferenceTestTarget} PRIVATE cxx_std_17) + target_compile_definitions(${onnxruntimeInferenceTestTarget} PRIVATE + $<$:ORT_CUDA_BUILD> + $<$:ORT_MIGRAPHX_BUILD> + $<$:ORT_TENSORRT_BUILD>) + + foreach(provider CUDA MIGRAPHX TENSORRT) + set(ONNXRUNTIME_INFERENCE_TEST_${provider} 0) + if(ORT_${provider}_BUILD) + set(ONNXRUNTIME_INFERENCE_TEST_${provider} 1) + endif() + endforeach() + + o2_add_test_command(NAME Common/ML/onnxruntime-inference + COMMAND ${ONNXRUNTIME_INFERENCE_TEST_DIR}/run-onnxruntime-all-eps.sh + COMMAND_LINE_ARGS ${ONNXRUNTIME_INFERENCE_TEST_DIR}/net.onnx + ENVIRONMENT + "ONNXRUNTIME_INFERENCE_TEST_BINARY=$" + "ORT_CUDA_BUILD=${ONNXRUNTIME_INFERENCE_TEST_CUDA}" + "ORT_MIGRAPHX_BUILD=${ONNXRUNTIME_INFERENCE_TEST_MIGRAPHX}" + "ORT_TENSORRT_BUILD=${ONNXRUNTIME_INFERENCE_TEST_TENSORRT}" + LABELS ml onnxruntime + TIMEOUT 300) +endif() diff --git a/Common/ML/test/onnxruntime-inference/CMakeLists.txt b/Common/ML/test/onnxruntime-inference/CMakeLists.txt new file mode 100644 index 0000000000000..fcf284f06b287 --- /dev/null +++ b/Common/ML/test/onnxruntime-inference/CMakeLists.txt @@ -0,0 +1,23 @@ +cmake_minimum_required(VERSION 3.16) +project(onnxruntime_inference_test LANGUAGES CXX) + +find_package(onnxruntime CONFIG REQUIRED) + +add_executable(onnxruntime-ep-inference onnxruntime_ep_inference.cxx) +target_compile_features(onnxruntime-ep-inference PRIVATE cxx_std_17) +target_link_libraries(onnxruntime-ep-inference PRIVATE onnxruntime::onnxruntime) +target_compile_definitions(onnxruntime-ep-inference PRIVATE + $<$:ORT_CUDA_BUILD> + $<$:ORT_MIGRAPHX_BUILD> + $<$:ORT_TENSORRT_BUILD>) + +install(TARGETS onnxruntime-ep-inference RUNTIME DESTINATION bin) +install(PROGRAMS + run-onnxruntime-all-eps.sh + run-onnxruntime-cpu.sh + run-onnxruntime-cuda.sh + run-onnxruntime-migraphx.sh + run-onnxruntime-tensorrt.sh + run-local-onnxruntime-inference-test.sh + DESTINATION bin) +install(FILES net.onnx DESTINATION test/onnxruntime-inference) diff --git a/Common/ML/test/onnxruntime-inference/net.onnx b/Common/ML/test/onnxruntime-inference/net.onnx new file mode 100644 index 0000000000000000000000000000000000000000..448aa72dacacf2c2f387bfd5e86badaca51a7d25 GIT binary patch literal 51444 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 literal 0 HcmV?d00001 diff --git a/Common/ML/test/onnxruntime-inference/onnxruntime_ep_inference.cxx b/Common/ML/test/onnxruntime-inference/onnxruntime_ep_inference.cxx new file mode 100644 index 0000000000000..a9d0129ba494f --- /dev/null +++ b/Common/ML/test/onnxruntime-inference/onnxruntime_ep_inference.cxx @@ -0,0 +1,337 @@ +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace +{ + +struct Arguments { + std::string modelPath; + std::string provider; + int deviceId = 0; + size_t expectedInputElements = 0; + size_t expectedOutputElements = 0; + bool requireProviderAssignment = true; +}; + +std::string toLower(std::string value) +{ + std::transform(value.begin(), value.end(), value.begin(), [](unsigned char c) { + return static_cast(std::tolower(c)); + }); + return value; +} + +bool hasProvider(const std::vector& providers, const std::string& provider) +{ + return std::find(providers.begin(), providers.end(), provider) != providers.end(); +} + +std::string join(const std::vector& values) +{ + std::ostringstream os; + for (size_t i = 0; i < values.size(); ++i) { + os << (i == 0 ? "" : ", ") << values[i]; + } + return os.str(); +} + +void usage(const char* argv0) +{ + std::cerr << "usage: " << argv0 + << " --model MODEL.onnx --provider cpu|migraphx|cuda|tensorrt " + "[--device-id N] [--expected-input-elements N] " + "[--expected-output-elements N] [--allow-cpu-fallback]\n"; +} + +Arguments parseArguments(int argc, char** argv) +{ + Arguments args; + for (int i = 1; i < argc; ++i) { + const std::string arg = argv[i]; + auto needValue = [&](const char* name) -> std::string { + if (i + 1 >= argc) { + throw std::runtime_error(std::string("missing value for ") + name); + } + return argv[++i]; + }; + + if (arg == "--model") { + args.modelPath = needValue("--model"); + } else if (arg == "--provider") { + args.provider = toLower(needValue("--provider")); + } else if (arg == "--device-id") { + args.deviceId = std::stoi(needValue("--device-id")); + } else if (arg == "--expected-input-elements") { + args.expectedInputElements = std::stoull(needValue("--expected-input-elements")); + } else if (arg == "--expected-output-elements") { + args.expectedOutputElements = std::stoull(needValue("--expected-output-elements")); + } else if (arg == "--allow-cpu-fallback") { + args.requireProviderAssignment = false; + } else if (arg == "--help" || arg == "-h") { + usage(argv[0]); + std::exit(0); + } else { + throw std::runtime_error("unknown argument: " + arg); + } + } + + if (args.modelPath.empty()) { + throw std::runtime_error("--model is required"); + } + if (args.provider != "cpu" && args.provider != "migraphx" && args.provider != "cuda" && args.provider != "tensorrt") { + throw std::runtime_error("--provider must be one of: cpu, migraphx, cuda, tensorrt"); + } + return args; +} + +std::string ortProviderName(const std::string& provider) +{ + if (provider == "cpu") { + return "CPUExecutionProvider"; + } + if (provider == "migraphx") { + return "MIGraphXExecutionProvider"; + } + if (provider == "cuda") { + return "CUDAExecutionProvider"; + } + if (provider == "tensorrt") { + return "TensorrtExecutionProvider"; + } + throw std::runtime_error("unsupported provider: " + provider); +} + +void appendProvider(Ort::SessionOptions& options, const Arguments& args) +{ + if (args.provider == "cpu") { + return; + } + if (args.provider == "cuda") { +#ifdef ORT_CUDA_BUILD + OrtCUDAProviderOptions cudaOptions{}; + cudaOptions.device_id = args.deviceId; + options.AppendExecutionProvider_CUDA(cudaOptions); + return; +#else + throw std::runtime_error("CUDA execution provider support was not enabled at build time"); +#endif + } + if (args.provider == "migraphx") { +#ifdef ORT_MIGRAPHX_BUILD + OrtMIGraphXProviderOptions migraphxOptions{}; + migraphxOptions.device_id = args.deviceId; + migraphxOptions.migraphx_mem_limit = std::numeric_limits::max(); + options.AppendExecutionProvider_MIGraphX(migraphxOptions); + return; +#else + throw std::runtime_error("MIGraphX execution provider support was not enabled at build time"); +#endif + } + if (args.provider == "tensorrt") { +#ifdef ORT_TENSORRT_BUILD + Ort::TensorRTProviderOptions tensorrtOptions; + tensorrtOptions.Update({{"device_id", std::to_string(args.deviceId)}}); + options.AppendExecutionProvider_TensorRT_V2(*tensorrtOptions); + return; +#else + throw std::runtime_error("TensorRT execution provider support was not enabled at build time"); +#endif + } +} + +std::vector concreteShape(std::vector shape) +{ + for (auto& dim : shape) { + if (dim <= 0) { + dim = 1; + } + } + return shape; +} + +size_t elementCount(const std::vector& shape) +{ + if (shape.empty()) { + return 1; + } + return std::accumulate(shape.begin(), shape.end(), size_t{1}, [](size_t product, int64_t dim) { + if (dim <= 0) { + throw std::runtime_error("invalid concrete tensor dimension"); + } + return product * static_cast(dim); + }); +} + +std::string shapeString(const std::vector& shape) +{ + std::ostringstream os; + os << "["; + for (size_t i = 0; i < shape.size(); ++i) { + os << (i == 0 ? "" : ",") << shape[i]; + } + os << "]"; + return os.str(); +} + +bool assignedToProvider(const Ort::Session& session, const std::string& providerName, size_t& assignedNodes) +{ + assignedNodes = 0; + for (const auto& subgraph : session.GetEpGraphAssignmentInfo()) { + if (subgraph.GetEpName() == providerName) { + assignedNodes += subgraph.GetNodes().size(); + } + } + return assignedNodes > 0; +} + +} // namespace + +int main(int argc, char** argv) +{ + try { + const auto args = parseArguments(argc, argv); + const auto providerName = ortProviderName(args.provider); + const auto availableProviders = Ort::GetAvailableProviders(); + if (!hasProvider(availableProviders, providerName)) { + throw std::runtime_error(providerName + " is not available in this ONNX Runtime build. Available providers: " + join(availableProviders)); + } + + Ort::Env env(ORT_LOGGING_LEVEL_WARNING, "onnxruntime-ep-inference"); + Ort::SessionOptions options; + options.SetIntraOpNumThreads(1); + options.SetGraphOptimizationLevel(GraphOptimizationLevel::ORT_ENABLE_ALL); + appendProvider(options, args); + + Ort::Session session(env, args.modelPath.c_str(), options); + size_t assignedNodes = 0; + if (args.provider != "cpu" && args.requireProviderAssignment && !assignedToProvider(session, providerName, assignedNodes)) { + throw std::runtime_error(providerName + " did not receive any graph nodes"); + } + + Ort::AllocatorWithDefaultOptions allocator; + Ort::MemoryInfo memoryInfo = Ort::MemoryInfo::CreateCpu(OrtArenaAllocator, OrtMemTypeDefault); + + std::vector inputNames; + std::vector inputNamePointers; + std::vector> inputBuffers; + std::vector inputValues; + size_t totalInputElements = 0; + + const size_t inputCount = session.GetInputCount(); + if (inputCount == 0) { + throw std::runtime_error("model has no inputs"); + } + inputNames.reserve(inputCount); + inputNamePointers.reserve(inputCount); + inputBuffers.reserve(inputCount); + inputValues.reserve(inputCount); + + for (size_t i = 0; i < inputCount; ++i) { + auto name = session.GetInputNameAllocated(i, allocator); + inputNames.emplace_back(name.get()); + inputNamePointers.push_back(inputNames.back().c_str()); + + auto typeInfo = session.GetInputTypeInfo(i); + if (typeInfo.GetONNXType() != ONNX_TYPE_TENSOR) { + throw std::runtime_error("input " + inputNames.back() + " is not a tensor"); + } + auto tensorInfo = typeInfo.GetTensorTypeAndShapeInfo(); + if (tensorInfo.GetElementType() != ONNX_TENSOR_ELEMENT_DATA_TYPE_FLOAT) { + throw std::runtime_error("input " + inputNames.back() + " is not a float tensor"); + } + + const auto shape = concreteShape(tensorInfo.GetShape()); + const auto elements = elementCount(shape); + totalInputElements += elements; + inputBuffers.emplace_back(elements); + for (size_t j = 0; j < elements; ++j) { + inputBuffers.back()[j] = static_cast((static_cast((i + j) % 23) - 11) * 0.03125f); + } + inputValues.emplace_back(Ort::Value::CreateTensor( + memoryInfo, inputBuffers.back().data(), elements, shape.data(), shape.size())); + std::cout << "input[" << i << "] " << inputNames.back() << " shape=" << shapeString(shape) + << " elements=" << elements << "\n"; + } + + if (args.expectedInputElements != 0 && totalInputElements != args.expectedInputElements) { + throw std::runtime_error("model input element count is " + std::to_string(totalInputElements) + + ", expected " + std::to_string(args.expectedInputElements)); + } + + std::vector outputNames; + std::vector outputNamePointers; + const size_t outputCount = session.GetOutputCount(); + if (outputCount == 0) { + throw std::runtime_error("model has no outputs"); + } + outputNames.reserve(outputCount); + outputNamePointers.reserve(outputCount); + for (size_t i = 0; i < outputCount; ++i) { + auto name = session.GetOutputNameAllocated(i, allocator); + outputNames.emplace_back(name.get()); + outputNamePointers.push_back(outputNames.back().c_str()); + } + + auto outputs = session.Run(Ort::RunOptions{nullptr}, + inputNamePointers.data(), + inputValues.data(), + inputValues.size(), + outputNamePointers.data(), + outputNamePointers.size()); + + if (outputs.size() != outputCount) { + throw std::runtime_error("ONNX Runtime returned an unexpected number of outputs"); + } + + size_t totalOutputElements = 0; + for (size_t i = 0; i < outputs.size(); ++i) { + if (!outputs[i].IsTensor()) { + throw std::runtime_error("output " + outputNames[i] + " is not a tensor"); + } + auto tensorInfo = outputs[i].GetTensorTypeAndShapeInfo(); + if (tensorInfo.GetElementType() != ONNX_TENSOR_ELEMENT_DATA_TYPE_FLOAT) { + throw std::runtime_error("output " + outputNames[i] + " is not a float tensor"); + } + const auto shape = tensorInfo.GetShape(); + const auto elements = tensorInfo.GetElementCount(); + totalOutputElements += elements; + const float* data = outputs[i].GetTensorData(); + for (size_t j = 0; j < elements; ++j) { + if (!std::isfinite(data[j])) { + throw std::runtime_error("output " + outputNames[i] + " contains a non-finite value"); + } + } + std::cout << "output[" << i << "] " << outputNames[i] << " shape=" << shapeString(shape) + << " elements=" << elements << "\n"; + } + + if (args.expectedOutputElements != 0 && totalOutputElements != args.expectedOutputElements) { + throw std::runtime_error("model output element count is " + std::to_string(totalOutputElements) + + ", expected " + std::to_string(args.expectedOutputElements)); + } + + std::cout << "provider=" << providerName << " assigned_nodes=" << assignedNodes + << " total_inputs=" << totalInputElements + << " total_outputs=" << totalOutputElements << "\n"; + return 0; + } catch (const Ort::Exception& ex) { + std::cerr << "ONNX Runtime error: " << ex.what() << "\n"; + } catch (const std::exception& ex) { + std::cerr << "error: " << ex.what() << "\n"; + } + + usage(argv[0]); + return 1; +} diff --git a/Common/ML/test/onnxruntime-inference/run-local-onnxruntime-inference-test.sh b/Common/ML/test/onnxruntime-inference/run-local-onnxruntime-inference-test.sh new file mode 100755 index 0000000000000..c1ac4931133a4 --- /dev/null +++ b/Common/ML/test/onnxruntime-inference/run-local-onnxruntime-inference-test.sh @@ -0,0 +1,134 @@ +#!/usr/bin/env bash +set -euo pipefail + +usage() { + cat <<'EOF' +usage: run-local-onnxruntime-inference-test.sh [options] + +Build and run the ONNX Runtime execution-provider inference smoke test in the +currently loaded aliBuild environment. If needed, the script re-runs itself in +an environment that provides ONNXRuntime, CMake, and Ninja. + +Options: + --model FILE ONNX model to test. Defaults to the bundled net.onnx. + --build-dir DIR Temporary CMake build dir. Defaults to /tmp. + --providers LIST Comma-separated providers to force, e.g. cpu,cuda. + By default, providers are selected from ort-init.sh. + --device-id N GPU device id passed to CUDA/MIGraphX/TensorRT tests. + --help Show this message. +EOF +} + +ORIGINAL_ARGS=("$@") +SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) +if [[ -f ${SCRIPT_DIR}/net.onnx ]]; then + MODEL=${SCRIPT_DIR}/net.onnx +else + MODEL=${SCRIPT_DIR}/../test/onnxruntime-inference/net.onnx +fi +BUILD_DIR=${TMPDIR:-/tmp}/onnxruntime-inference-test-local-${USER:-user} +PROVIDERS= +DEVICE_ID= + +while [[ $# -gt 0 ]]; do + case "$1" in + --model) + MODEL=$2 + shift 2 + ;; + --build-dir) + BUILD_DIR=$2 + shift 2 + ;; + --providers) + PROVIDERS=$2 + shift 2 + ;; + --device-id) + DEVICE_ID=$2 + shift 2 + ;; + --help|-h) + usage + exit 0 + ;; + *) + echo "run-local-onnxruntime-inference-test: unknown option: $1" >&2 + usage >&2 + exit 2 + ;; + esac +done + +if [[ ! -f $MODEL ]]; then + echo "run-local-onnxruntime-inference-test: model not found: $MODEL" >&2 + exit 2 +fi + +detect_onnxruntime_root() { + [[ -n ${ONNXRUNTIME_ROOT:-} && -d $ONNXRUNTIME_ROOT/lib/cmake/onnxruntime ]] && return 0 + + IFS=: read -r -a SEARCH_PATHS <<< "${CMAKE_PREFIX_PATH:-}:${LD_LIBRARY_PATH:-}:${ROOT_INCLUDE_PATH:-}" + for PATH_ENTRY in "${SEARCH_PATHS[@]}"; do + CANDIDATE= + case "$PATH_ENTRY" in + */ONNXRuntime/*/lib) + CANDIDATE=${PATH_ENTRY%/lib} + ;; + */ONNXRuntime/*/include/onnxruntime) + CANDIDATE=${PATH_ENTRY%/include/onnxruntime} + ;; + */ONNXRuntime/*) + CANDIDATE=$PATH_ENTRY + ;; + esac + if [[ -n $CANDIDATE && -d $CANDIDATE/lib/cmake/onnxruntime ]]; then + export ONNXRUNTIME_ROOT=$CANDIDATE + return 0 + fi + done + return 1 +} + +if ! detect_onnxruntime_root || ! command -v cmake > /dev/null || ! command -v ninja > /dev/null; then + if [[ ${ONNXRUNTIME_INFERENCE_TEST_BOOTSTRAPPED:-0} != 1 ]] && command -v alienv > /dev/null; then + export ONNXRUNTIME_INFERENCE_TEST_BOOTSTRAPPED=1 + exec alienv setenv ONNXRuntime/latest,CMake/latest,ninja/latest \ + -c "$SCRIPT_DIR/run-local-onnxruntime-inference-test.sh" "${ORIGINAL_ARGS[@]}" + fi +fi + +if [[ -z ${ONNXRUNTIME_ROOT:-} ]]; then + echo "run-local-onnxruntime-inference-test: ONNXRUNTIME_ROOT is not set" >&2 + echo "Could not infer it from the loaded environment." >&2 + exit 2 +fi + +if [[ -f $ONNXRUNTIME_ROOT/etc/ort-init.sh ]]; then + source "$ONNXRUNTIME_ROOT/etc/ort-init.sh" +fi + +if [[ -n $PROVIDERS ]]; then + export ONNXRUNTIME_INFERENCE_TEST_PROVIDERS=$PROVIDERS +fi +if [[ -n $DEVICE_ID ]]; then + export ONNXRUNTIME_INFERENCE_TEST_DEVICE_ID=$DEVICE_ID +fi + +if [[ -n ${ONNXRUNTIME_INFERENCE_TEST_BINARY:-} ]]; then + : +elif [[ -x ${SCRIPT_DIR}/onnxruntime-ep-inference && ! -f ${SCRIPT_DIR}/CMakeLists.txt ]]; then + export ONNXRUNTIME_INFERENCE_TEST_BINARY="$SCRIPT_DIR/onnxruntime-ep-inference" +else + rm -Rf "$BUILD_DIR" + cmake -S "$SCRIPT_DIR" \ + -B "$BUILD_DIR" \ + -G Ninja \ + -Donnxruntime_DIR="$ONNXRUNTIME_ROOT/lib/cmake/onnxruntime" \ + -DORT_CUDA_BUILD="${ORT_CUDA_BUILD:-0}" \ + -DORT_MIGRAPHX_BUILD="${ORT_MIGRAPHX_BUILD:-0}" \ + -DORT_TENSORRT_BUILD="${ORT_TENSORRT_BUILD:-0}" + cmake --build "$BUILD_DIR" + export ONNXRUNTIME_INFERENCE_TEST_BINARY="$BUILD_DIR/onnxruntime-ep-inference" +fi +"$SCRIPT_DIR/run-onnxruntime-all-eps.sh" "$MODEL" diff --git a/Common/ML/test/onnxruntime-inference/run-onnxruntime-all-eps.sh b/Common/ML/test/onnxruntime-inference/run-onnxruntime-all-eps.sh new file mode 100755 index 0000000000000..dc56c84ee2e4a --- /dev/null +++ b/Common/ML/test/onnxruntime-inference/run-onnxruntime-all-eps.sh @@ -0,0 +1,41 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) +MODEL=${1:-${SCRIPT_DIR}/net.onnx} +if [[ ! -f $MODEL ]]; then + echo "onnxruntime-inference-test: model not found: $MODEL" >&2 + exit 2 +fi + +if [[ -n ${ONNXRUNTIME_ROOT:-} && -f $ONNXRUNTIME_ROOT/etc/ort-init.sh ]]; then + source "$ONNXRUNTIME_ROOT/etc/ort-init.sh" +fi + +if [[ -n ${ONNXRUNTIME_INFERENCE_TEST_PROVIDERS:-} ]]; then + IFS=', ' read -r -a PROVIDERS <<< "$ONNXRUNTIME_INFERENCE_TEST_PROVIDERS" +else + PROVIDERS=(cpu) + [[ ${ORT_MIGRAPHX_BUILD:-0} == 1 ]] && PROVIDERS+=(migraphx) + [[ ${ORT_CUDA_BUILD:-0} == 1 ]] && PROVIDERS+=(cuda) + [[ ${ORT_TENSORRT_BUILD:-0} == 1 ]] && PROVIDERS+=(tensorrt) +fi + +echo "onnxruntime-inference-test: selected providers: ${PROVIDERS[*]}" + +FAILURES=() +for PROVIDER in "${PROVIDERS[@]}"; do + [[ -n $PROVIDER ]] || continue + PROVIDER=${PROVIDER,,} + echo "onnxruntime-inference-test: running ${PROVIDER}" + if ! "${SCRIPT_DIR}/run-onnxruntime-${PROVIDER}.sh" "$MODEL"; then + FAILURES+=("$PROVIDER") + fi +done + +if [[ ${#FAILURES[@]} != 0 ]]; then + echo "onnxruntime-inference-test: failed providers: ${FAILURES[*]}" >&2 + exit 1 +fi + +echo "onnxruntime-inference-test: all providers passed" diff --git a/Common/ML/test/onnxruntime-inference/run-onnxruntime-cpu.sh b/Common/ML/test/onnxruntime-inference/run-onnxruntime-cpu.sh new file mode 100755 index 0000000000000..839995e88f6d4 --- /dev/null +++ b/Common/ML/test/onnxruntime-inference/run-onnxruntime-cpu.sh @@ -0,0 +1,17 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) +MODEL=${1:-${SCRIPT_DIR}/net.onnx} +if [[ ! -f $MODEL ]]; then + echo "onnxruntime-inference-test: model not found: $MODEL" >&2 + exit 2 +fi + +TESTER=${ONNXRUNTIME_INFERENCE_TEST_BINARY:-${SCRIPT_DIR}/onnxruntime-ep-inference} + +exec "$TESTER" \ + --model "$MODEL" \ + --provider cpu \ + --expected-input-elements "${ONNXRUNTIME_INFERENCE_TEST_EXPECTED_INPUTS:-246}" \ + --expected-output-elements "${ONNXRUNTIME_INFERENCE_TEST_EXPECTED_OUTPUTS:-7}" diff --git a/Common/ML/test/onnxruntime-inference/run-onnxruntime-cuda.sh b/Common/ML/test/onnxruntime-inference/run-onnxruntime-cuda.sh new file mode 100755 index 0000000000000..a8331000e4976 --- /dev/null +++ b/Common/ML/test/onnxruntime-inference/run-onnxruntime-cuda.sh @@ -0,0 +1,50 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) +MODEL=${1:-${SCRIPT_DIR}/net.onnx} +if [[ ! -f $MODEL ]]; then + echo "onnxruntime-inference-test: model not found: $MODEL" >&2 + exit 2 +fi + +TESTER=${ONNXRUNTIME_INFERENCE_TEST_BINARY:-${SCRIPT_DIR}/onnxruntime-ep-inference} + +if [[ -n ${GPU_SYSTEM_ROOT:-} && -f $GPU_SYSTEM_ROOT/etc/gpu-features-available.sh ]]; then + source "$GPU_SYSTEM_ROOT/etc/gpu-features-available.sh" +fi + +add_cuda_driver_path() { + local roots=() + local root candidate + + shopt -s nullglob + roots+=(/usr/local/cuda*) + shopt -u nullglob + [[ -n ${O2_GPU_CUDA_HOME:-} ]] && roots+=("$O2_GPU_CUDA_HOME") + roots+=(/usr/local/nvidia/lib64 /usr/local/nvidia/lib) + roots+=(/usr/lib64 /usr/lib/x86_64-linux-gnu /usr/lib/wsl/lib) + + for root in "${roots[@]}"; do + [[ -d $root ]] || continue + candidate=$(find "$root" -type d -name stubs -prune -false -o \ + \( -type f -o -type l \) \( -name libcuda.so -o -name libcuda.so.1 \) \ + -printf '%h\n' -quit 2>/dev/null || true) + if [[ -n $candidate ]]; then + export LD_LIBRARY_PATH="$candidate${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}" + echo "onnxruntime-inference-test: added CUDA driver library path: $candidate" + return 0 + fi + done + + echo "onnxruntime-inference-test: no CUDA driver library found; LD_LIBRARY_PATH=${LD_LIBRARY_PATH:-}" >&2 +} + +add_cuda_driver_path + +exec "$TESTER" \ + --model "$MODEL" \ + --provider cuda \ + --device-id "${ONNXRUNTIME_INFERENCE_TEST_DEVICE_ID:-0}" \ + --expected-input-elements "${ONNXRUNTIME_INFERENCE_TEST_EXPECTED_INPUTS:-246}" \ + --expected-output-elements "${ONNXRUNTIME_INFERENCE_TEST_EXPECTED_OUTPUTS:-7}" diff --git a/Common/ML/test/onnxruntime-inference/run-onnxruntime-migraphx.sh b/Common/ML/test/onnxruntime-inference/run-onnxruntime-migraphx.sh new file mode 100755 index 0000000000000..58cf870432b16 --- /dev/null +++ b/Common/ML/test/onnxruntime-inference/run-onnxruntime-migraphx.sh @@ -0,0 +1,18 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) +MODEL=${1:-${SCRIPT_DIR}/net.onnx} +if [[ ! -f $MODEL ]]; then + echo "onnxruntime-inference-test: model not found: $MODEL" >&2 + exit 2 +fi + +TESTER=${ONNXRUNTIME_INFERENCE_TEST_BINARY:-${SCRIPT_DIR}/onnxruntime-ep-inference} + +exec "$TESTER" \ + --model "$MODEL" \ + --provider migraphx \ + --device-id "${ONNXRUNTIME_INFERENCE_TEST_DEVICE_ID:-0}" \ + --expected-input-elements "${ONNXRUNTIME_INFERENCE_TEST_EXPECTED_INPUTS:-246}" \ + --expected-output-elements "${ONNXRUNTIME_INFERENCE_TEST_EXPECTED_OUTPUTS:-7}" diff --git a/Common/ML/test/onnxruntime-inference/run-onnxruntime-tensorrt.sh b/Common/ML/test/onnxruntime-inference/run-onnxruntime-tensorrt.sh new file mode 100755 index 0000000000000..d31df523e90a4 --- /dev/null +++ b/Common/ML/test/onnxruntime-inference/run-onnxruntime-tensorrt.sh @@ -0,0 +1,18 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) +MODEL=${1:-${SCRIPT_DIR}/net.onnx} +if [[ ! -f $MODEL ]]; then + echo "onnxruntime-inference-test: model not found: $MODEL" >&2 + exit 2 +fi + +TESTER=${ONNXRUNTIME_INFERENCE_TEST_BINARY:-${SCRIPT_DIR}/onnxruntime-ep-inference} + +exec "$TESTER" \ + --model "$MODEL" \ + --provider tensorrt \ + --device-id "${ONNXRUNTIME_INFERENCE_TEST_DEVICE_ID:-0}" \ + --expected-input-elements "${ONNXRUNTIME_INFERENCE_TEST_EXPECTED_INPUTS:-246}" \ + --expected-output-elements "${ONNXRUNTIME_INFERENCE_TEST_EXPECTED_OUTPUTS:-7}" From 7a2af0b235f5517e72edb9250cbe731417d2102c Mon Sep 17 00:00:00 2001 From: Christian Sonnabend Date: Mon, 14 Sep 2026 10:14:51 +0200 Subject: [PATCH 2/5] Adding copyright header --- .../onnxruntime_ep_inference.cxx | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/Common/ML/test/onnxruntime-inference/onnxruntime_ep_inference.cxx b/Common/ML/test/onnxruntime-inference/onnxruntime_ep_inference.cxx index a9d0129ba494f..a066c193707a8 100644 --- a/Common/ML/test/onnxruntime-inference/onnxruntime_ep_inference.cxx +++ b/Common/ML/test/onnxruntime-inference/onnxruntime_ep_inference.cxx @@ -1,3 +1,18 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// \file onnxruntime_ep_inference.h +/// \author Christian Sonnabend +/// \brief A test script for inferencing an ONNX model with a specific execution provider + #include #include From b64a9e2c59655c5a7ef74c22218f0f2fcd9fc1e6 Mon Sep 17 00:00:00 2001 From: Christian Sonnabend Date: Mon, 14 Sep 2026 10:18:45 +0200 Subject: [PATCH 3/5] Adding another copyright header --- Common/ML/test/onnxruntime-inference/CMakeLists.txt | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/Common/ML/test/onnxruntime-inference/CMakeLists.txt b/Common/ML/test/onnxruntime-inference/CMakeLists.txt index fcf284f06b287..e865446441aa0 100644 --- a/Common/ML/test/onnxruntime-inference/CMakeLists.txt +++ b/Common/ML/test/onnxruntime-inference/CMakeLists.txt @@ -1,3 +1,14 @@ +# Copyright 2019-2020 CERN and copyright holders of ALICE O2. +# See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +# All rights not expressly granted are reserved. +# +# This software is distributed under the terms of the GNU General Public +# License v3 (GPL Version 3), copied verbatim in the file "COPYING". +# +# In applying this license CERN does not waive the privileges and immunities +# granted to it by virtue of its status as an Intergovernmental Organization +# or submit itself to any jurisdiction. + cmake_minimum_required(VERSION 3.16) project(onnxruntime_inference_test LANGUAGES CXX) From de4482735625e16acaa6960f5ef07f8c275bf152 Mon Sep 17 00:00:00 2001 From: Christian Sonnabend Date: Mon, 14 Sep 2026 10:30:15 +0200 Subject: [PATCH 4/5] Fixing cuda build test --- .../run-onnxruntime-all-eps.sh | 38 +++++++++++++++++-- 1 file changed, 35 insertions(+), 3 deletions(-) diff --git a/Common/ML/test/onnxruntime-inference/run-onnxruntime-all-eps.sh b/Common/ML/test/onnxruntime-inference/run-onnxruntime-all-eps.sh index dc56c84ee2e4a..bc30046b9b338 100755 --- a/Common/ML/test/onnxruntime-inference/run-onnxruntime-all-eps.sh +++ b/Common/ML/test/onnxruntime-inference/run-onnxruntime-all-eps.sh @@ -12,13 +12,45 @@ if [[ -n ${ONNXRUNTIME_ROOT:-} && -f $ONNXRUNTIME_ROOT/etc/ort-init.sh ]]; then source "$ONNXRUNTIME_ROOT/etc/ort-init.sh" fi +has_cuda_device() { + if command -v nvidia-smi >/dev/null 2>&1 && nvidia-smi -L >/dev/null 2>&1; then + return 0 + fi + compgen -G "/proc/driver/nvidia/gpus/*" >/dev/null +} + +has_rocm_device() { + if command -v rocm-smi >/dev/null 2>&1 && rocm-smi -i >/dev/null 2>&1; then + return 0 + fi + [[ -e /dev/kfd ]] && compgen -G "/dev/dri/renderD*" >/dev/null +} + if [[ -n ${ONNXRUNTIME_INFERENCE_TEST_PROVIDERS:-} ]]; then IFS=', ' read -r -a PROVIDERS <<< "$ONNXRUNTIME_INFERENCE_TEST_PROVIDERS" else PROVIDERS=(cpu) - [[ ${ORT_MIGRAPHX_BUILD:-0} == 1 ]] && PROVIDERS+=(migraphx) - [[ ${ORT_CUDA_BUILD:-0} == 1 ]] && PROVIDERS+=(cuda) - [[ ${ORT_TENSORRT_BUILD:-0} == 1 ]] && PROVIDERS+=(tensorrt) + if [[ ${ORT_MIGRAPHX_BUILD:-0} == 1 ]]; then + if has_rocm_device; then + PROVIDERS+=(migraphx) + else + echo "onnxruntime-inference-test: skipping migraphx, no ROCm device detected" + fi + fi + if [[ ${ORT_CUDA_BUILD:-0} == 1 ]]; then + if has_cuda_device; then + PROVIDERS+=(cuda) + else + echo "onnxruntime-inference-test: skipping cuda, no CUDA device detected" + fi + fi + if [[ ${ORT_TENSORRT_BUILD:-0} == 1 ]]; then + if has_cuda_device; then + PROVIDERS+=(tensorrt) + else + echo "onnxruntime-inference-test: skipping tensorrt, no CUDA device detected" + fi + fi fi echo "onnxruntime-inference-test: selected providers: ${PROVIDERS[*]}" From 48758afd938b19a491f80bc6bb383c812fabf01d Mon Sep 17 00:00:00 2001 From: Christian Sonnabend Date: Mon, 14 Sep 2026 10:43:11 +0200 Subject: [PATCH 5/5] Follow gpu deterministic test setup --- Common/ML/CMakeLists.txt | 2 +- .../run-onnxruntime-all-eps.sh | 27 +++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/Common/ML/CMakeLists.txt b/Common/ML/CMakeLists.txt index d3ef2515a188c..f552d33c31e8b 100644 --- a/Common/ML/CMakeLists.txt +++ b/Common/ML/CMakeLists.txt @@ -52,6 +52,6 @@ if(BUILD_TESTING) "ORT_CUDA_BUILD=${ONNXRUNTIME_INFERENCE_TEST_CUDA}" "ORT_MIGRAPHX_BUILD=${ONNXRUNTIME_INFERENCE_TEST_MIGRAPHX}" "ORT_TENSORRT_BUILD=${ONNXRUNTIME_INFERENCE_TEST_TENSORRT}" - LABELS ml onnxruntime + LABELS ml onnxruntime gpu TIMEOUT 300) endif() diff --git a/Common/ML/test/onnxruntime-inference/run-onnxruntime-all-eps.sh b/Common/ML/test/onnxruntime-inference/run-onnxruntime-all-eps.sh index bc30046b9b338..cc230aa77ba14 100755 --- a/Common/ML/test/onnxruntime-inference/run-onnxruntime-all-eps.sh +++ b/Common/ML/test/onnxruntime-inference/run-onnxruntime-all-eps.sh @@ -11,6 +11,9 @@ fi if [[ -n ${ONNXRUNTIME_ROOT:-} && -f $ONNXRUNTIME_ROOT/etc/ort-init.sh ]]; then source "$ONNXRUNTIME_ROOT/etc/ort-init.sh" fi +if [[ -n ${GPU_SYSTEM_ROOT:-} && -f $GPU_SYSTEM_ROOT/etc/gpu-features-available.sh ]]; then + source "$GPU_SYSTEM_ROOT/etc/gpu-features-available.sh" +fi has_cuda_device() { if command -v nvidia-smi >/dev/null 2>&1 && nvidia-smi -L >/dev/null 2>&1; then @@ -28,11 +31,29 @@ has_rocm_device() { if [[ -n ${ONNXRUNTIME_INFERENCE_TEST_PROVIDERS:-} ]]; then IFS=', ' read -r -a PROVIDERS <<< "$ONNXRUNTIME_INFERENCE_TEST_PROVIDERS" + for PROVIDER in "${PROVIDERS[@]}"; do + PROVIDER=${PROVIDER,,} + if [[ $PROVIDER == "cuda" && ${O2_GPU_CUDA_AVAILABLE:-0} == 1 ]] && ! has_cuda_device; then + echo "onnxruntime-inference-test: CUDA is available but no CUDA device was detected" >&2 + exit 1 + fi + if [[ $PROVIDER == "tensorrt" && ${O2_GPU_CUDA_AVAILABLE:-0} == 1 ]] && ! has_cuda_device; then + echo "onnxruntime-inference-test: TensorRT is available but no CUDA device was detected" >&2 + exit 1 + fi + if [[ $PROVIDER == "migraphx" && ${O2_GPU_ROCM_AVAILABLE:-0} == 1 ]] && ! has_rocm_device; then + echo "onnxruntime-inference-test: ROCm is available but no ROCm device was detected" >&2 + exit 1 + fi + done else PROVIDERS=(cpu) if [[ ${ORT_MIGRAPHX_BUILD:-0} == 1 ]]; then if has_rocm_device; then PROVIDERS+=(migraphx) + elif [[ ${O2_GPU_ROCM_AVAILABLE:-0} == 1 ]]; then + echo "onnxruntime-inference-test: ROCm is available but no ROCm device was detected" >&2 + exit 1 else echo "onnxruntime-inference-test: skipping migraphx, no ROCm device detected" fi @@ -40,6 +61,9 @@ else if [[ ${ORT_CUDA_BUILD:-0} == 1 ]]; then if has_cuda_device; then PROVIDERS+=(cuda) + elif [[ ${O2_GPU_CUDA_AVAILABLE:-0} == 1 ]]; then + echo "onnxruntime-inference-test: CUDA is available but no CUDA device was detected" >&2 + exit 1 else echo "onnxruntime-inference-test: skipping cuda, no CUDA device detected" fi @@ -47,6 +71,9 @@ else if [[ ${ORT_TENSORRT_BUILD:-0} == 1 ]]; then if has_cuda_device; then PROVIDERS+=(tensorrt) + elif [[ ${O2_GPU_CUDA_AVAILABLE:-0} == 1 ]]; then + echo "onnxruntime-inference-test: TensorRT is available but no CUDA device was detected" >&2 + exit 1 else echo "onnxruntime-inference-test: skipping tensorrt, no CUDA device detected" fi