Skip to content

Commit b79face

Browse files
authored
Add Onnxruntime (CPU-only) backend plugin (#17)
1 parent 7b2844b commit b79face

File tree

20 files changed

+1259
-29
lines changed

20 files changed

+1259
-29
lines changed

DEVELOPING.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,9 @@ This project includes VS Code dev container configurations for easy ROS2 develop
3131
Inside the container:
3232

3333
```bash
34+
# Update apt
35+
sudo apt update
36+
3437
# Update rosdep
3538
rosdep update
3639

deep_core/include/deep_core/deep_node_base.hpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -135,10 +135,10 @@ class DeepNodeBase : public rclcpp_lifecycle::LifecycleNode
135135

136136
/**
137137
* @brief Run inference on input tensor
138-
* @param inputs Input tensor for inference
138+
* @param inputs Input tensor for inference (note: some backends may require mutable access for zero-copy operations)
139139
* @return Output tensor from inference
140140
*/
141-
Tensor run_inference(const Tensor & inputs);
141+
Tensor run_inference(Tensor & inputs);
142142

143143
/**
144144
* @brief Check if a backend plugin is loaded

deep_core/include/deep_core/plugin_interfaces/backend_inference_executor.hpp

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,12 +45,12 @@ class BackendInferenceExecutor
4545

4646
/**
4747
* @brief Run inference on input tensor
48-
* @param input Input tensor
48+
* @param input Input tensor (note: some backends may require mutable access for zero-copy operations)
4949
* @return Output tensor
5050
* @throws std::invalid_argument if input tensor is invalid
5151
* @throws std::runtime_error if no model is loaded
5252
*/
53-
Tensor run_inference(const Tensor & input);
53+
Tensor run_inference(Tensor & input);
5454

5555
/**
5656
* @brief Unload the currently loaded model
@@ -80,8 +80,9 @@ class BackendInferenceExecutor
8080

8181
/**
8282
* @brief Implementation of run_inference (to be overridden by backends)
83+
* @param input Input tensor (note: some backends may require mutable access)
8384
*/
84-
virtual Tensor run_inference_impl(const Tensor & input) = 0;
85+
virtual Tensor run_inference_impl(Tensor & input) = 0;
8586

8687
/**
8788
* @brief Implementation of unload_model (to be overridden by backends)

deep_core/package.xml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313
<depend>pluginlib</depend>
1414
<depend>bondcpp</depend>
1515

16-
1716
<export>
1817
<build_type>ament_cmake</build_type>
1918
</export>

deep_core/src/backend_inference_executor.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ bool BackendInferenceExecutor::load_model(const std::filesystem::path & model_pa
3434
return success;
3535
}
3636

37-
Tensor BackendInferenceExecutor::run_inference(const Tensor & input)
37+
Tensor BackendInferenceExecutor::run_inference(Tensor & input)
3838
{
3939
// Validate input tensor
4040
if (input.data() == nullptr) {

deep_core/src/deep_node_base.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,7 @@ void DeepNodeBase::unload_model()
191191
}
192192
}
193193

194-
Tensor DeepNodeBase::run_inference(const Tensor & inputs)
194+
Tensor DeepNodeBase::run_inference(Tensor & inputs)
195195
{
196196
if (!plugin_) {
197197
throw std::runtime_error("No plugin loaded");
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
# Copyright (c) 2025-present WATonomous. All rights reserved.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
cmake_minimum_required(VERSION 3.22)
16+
project(deep_ort_backend_plugin)
17+
18+
if(NOT CMAKE_CXX_STANDARD)
19+
set(CMAKE_CXX_STANDARD 17)
20+
endif()
21+
22+
if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
23+
add_compile_options(-Wall -Wextra -Wpedantic)
24+
add_link_options(-Wl,-no-undefined)
25+
endif()
26+
27+
find_package(ament_cmake REQUIRED)
28+
find_package(deep_core REQUIRED)
29+
find_package(onnxruntime_vendor REQUIRED)
30+
find_package(pluginlib REQUIRED)
31+
find_package(rclcpp REQUIRED)
32+
find_package(rclcpp_lifecycle REQUIRED)
33+
34+
set(include_dir ${CMAKE_CURRENT_SOURCE_DIR}/include)
35+
36+
# deep_ort_backend_plugin library
37+
set(DEEP_ORT_LIB ${PROJECT_NAME}_lib)
38+
add_library(${DEEP_ORT_LIB} SHARED
39+
src/ort_cpu_memory_allocator.cpp
40+
src/ort_backend_executor.cpp
41+
src/ort_backend_plugin.cpp
42+
)
43+
44+
target_include_directories(${DEEP_ORT_LIB} PUBLIC
45+
$<BUILD_INTERFACE:${include_dir}>
46+
$<INSTALL_INTERFACE:include>
47+
)
48+
49+
target_link_libraries(${DEEP_ORT_LIB}
50+
PUBLIC
51+
pluginlib::pluginlib
52+
rclcpp::rclcpp
53+
PRIVATE
54+
deep_core::deep_core_lib
55+
onnxruntime_vendor::onnxruntime_lib
56+
)
57+
58+
install(TARGETS
59+
${DEEP_ORT_LIB}
60+
EXPORT ${PROJECT_NAME}Targets
61+
ARCHIVE DESTINATION lib
62+
LIBRARY DESTINATION lib
63+
RUNTIME DESTINATION bin
64+
)
65+
66+
install(EXPORT ${PROJECT_NAME}Targets
67+
NAMESPACE ${PROJECT_NAME}::
68+
DESTINATION share/${PROJECT_NAME}/cmake
69+
)
70+
71+
install(DIRECTORY include/
72+
DESTINATION include
73+
)
74+
75+
install(FILES plugins.xml
76+
DESTINATION share/${PROJECT_NAME}
77+
)
78+
79+
# Export plugin description file to ament index
80+
pluginlib_export_plugin_description_file(deep_core plugins.xml)
81+
82+
if(BUILD_TESTING)
83+
find_package(deep_test REQUIRED)
84+
85+
add_deep_test(test_ort_backend test/test_ort_backend.cpp
86+
LIBRARIES
87+
${DEEP_ORT_LIB}
88+
deep_core::deep_core_lib
89+
onnxruntime_vendor::onnxruntime_lib
90+
)
91+
92+
endif()
93+
94+
ament_export_targets(${PROJECT_NAME}Targets HAS_LIBRARY_TARGET)
95+
ament_export_libraries(${DEEP_ORT_LIB})
96+
ament_package()

deep_ort_backend_plugin/README.md

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
# deep_ort_backend_plugin
2+
3+
ONNX Runtime CPU backend plugin for deep_core.
4+
5+
## Overview
6+
7+
Provides:
8+
- CPU inference executor using ONNX Runtime
9+
- 64-byte aligned memory allocator for optimal SIMD performance
10+
- Zero-copy inference with IO binding
11+
12+
## Plugin Name
13+
14+
`onnxruntime_cpu`
15+
16+
## Supported Formats
17+
18+
ONNX models (.onnx files)
19+
20+
## Usage
21+
22+
Add to your `package.xml`:
23+
24+
```xml
25+
<exec_depend>deep_ort_backend_plugin</exec_depend>
26+
```
27+
28+
Configure your inference nodes to use this plugin:
29+
30+
```yaml
31+
inference_node:
32+
ros__parameters:
33+
Backend.plugin: "onnxruntime_cpu"
34+
model_path: "/path/to/model.onnx"
35+
```
36+
37+
## Dependencies
38+
39+
- deep_core
40+
- onnxruntime_vendor
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
// Copyright (c) 2025-present WATonomous. All rights reserved.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#pragma once
16+
17+
#include <onnxruntime_cxx_api.h>
18+
19+
#include <filesystem>
20+
#include <memory>
21+
#include <string>
22+
#include <vector>
23+
24+
#include <deep_core/plugin_interfaces/backend_inference_executor.hpp>
25+
#include <deep_core/types/tensor.hpp>
26+
27+
namespace deep_ort_backend
28+
{
29+
30+
/**
31+
* @brief ONNX Runtime backend inference executor
32+
*
33+
* Provides inference execution using ONNX Runtime with CPU optimization.
34+
* Uses zero-copy IO binding for efficient tensor operations.
35+
*/
36+
class OrtBackendExecutor : public deep_ros::BackendInferenceExecutor
37+
{
38+
public:
39+
/**
40+
* @brief Constructor - initializes ONNX Runtime environment
41+
*/
42+
OrtBackendExecutor();
43+
44+
/**
45+
* @brief Destructor
46+
*/
47+
~OrtBackendExecutor() override = default;
48+
49+
/**
50+
* @brief Get supported model formats
51+
* @return Vector containing "onnx"
52+
*/
53+
std::vector<std::string> supported_model_formats() const override;
54+
55+
protected:
56+
/**
57+
* @brief Load an ONNX model from file
58+
* @param model_path Path to the .onnx model file
59+
* @return true if successful, false otherwise
60+
*/
61+
bool load_model_impl(const std::filesystem::path & model_path) override;
62+
63+
/**
64+
* @brief Run inference using zero-copy IO binding
65+
* @param input Input tensor (must be compatible with model input)
66+
* @return Output tensor with inference results
67+
* @throws std::runtime_error if inference fails or no model loaded
68+
*/
69+
deep_ros::Tensor run_inference_impl(deep_ros::Tensor & input) override;
70+
71+
/**
72+
* @brief Unload the currently loaded model
73+
*/
74+
void unload_model_impl() override;
75+
76+
private:
77+
std::filesystem::path model_path_;
78+
79+
std::unique_ptr<Ort::Env> env_;
80+
std::unique_ptr<Ort::Session> session_;
81+
Ort::MemoryInfo memory_info_;
82+
83+
/**
84+
* @brief Convert deep_ros DataType to ONNX tensor element type
85+
* @param dtype deep_ros data type
86+
* @return ONNX tensor element data type
87+
*/
88+
ONNXTensorElementDataType convert_to_onnx_type(deep_ros::DataType dtype) const;
89+
90+
/**
91+
* @brief Get model output shape based on input shape
92+
* @param input_shape Input tensor shape
93+
* @return Expected output tensor shape
94+
* @throws std::runtime_error if model not loaded or shape inference fails
95+
*/
96+
std::vector<size_t> get_output_shape(const std::vector<size_t> & input_shape) const;
97+
98+
/**
99+
* @brief Get element size in bytes for a data type
100+
* @param dtype Data type
101+
* @return Size in bytes per element
102+
*/
103+
size_t get_element_size(deep_ros::DataType dtype) const;
104+
};
105+
106+
} // namespace deep_ort_backend
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
// Copyright (c) 2025-present WATonomous. All rights reserved.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#pragma once
16+
17+
#include <memory>
18+
#include <string>
19+
20+
#include <deep_core/plugin_interfaces/backend_inference_executor.hpp>
21+
#include <deep_core/plugin_interfaces/backend_memory_allocator.hpp>
22+
#include <deep_core/plugin_interfaces/deep_backend_plugin.hpp>
23+
24+
namespace deep_ort_backend
25+
{
26+
27+
/**
28+
* @brief ONNX Runtime backend plugin
29+
*
30+
* Combines ORT CPU memory allocator and inference executor into a single
31+
* backend plugin for use with pluginlib.
32+
*/
33+
class OrtBackendPlugin : public deep_ros::DeepBackendPlugin
34+
{
35+
public:
36+
/**
37+
* @brief Constructor - initializes ORT allocator and executor
38+
*/
39+
OrtBackendPlugin();
40+
41+
/**
42+
* @brief Destructor
43+
*/
44+
~OrtBackendPlugin() override = default;
45+
46+
/**
47+
* @brief Get backend name
48+
* @return "onnxruntime"
49+
*/
50+
std::string backend_name() const override;
51+
52+
/**
53+
* @brief Get the ORT CPU memory allocator
54+
* @return Shared pointer to ORT memory allocator
55+
*/
56+
std::shared_ptr<deep_ros::BackendMemoryAllocator> get_allocator() const override;
57+
58+
/**
59+
* @brief Get the ORT inference executor
60+
* @return Shared pointer to ORT inference executor
61+
*/
62+
std::shared_ptr<deep_ros::BackendInferenceExecutor> get_inference_executor() const override;
63+
64+
private:
65+
std::shared_ptr<deep_ros::BackendMemoryAllocator> allocator_;
66+
std::shared_ptr<deep_ros::BackendInferenceExecutor> executor_;
67+
};
68+
69+
} // namespace deep_ort_backend

0 commit comments

Comments
 (0)