From b52343ce74be7fa9c13881affac889b7da74a85c Mon Sep 17 00:00:00 2001 From: David Trevelyan Date: Wed, 22 Nov 2023 20:42:09 +0000 Subject: [PATCH 1/7] Add RADSan test with workflow --- .github/workflows/radsan.yml | 33 +++++++++++++++++++++++++++++++++ CMakeLists.txt | 5 +++++ RTNeural/Model.h | 7 ++++--- RTNeural/ModelT.h | 23 ++++++++++++----------- RTNeural/platform/platform.h | 7 +++++++ cmake/RADSan.cmake | 7 +++++++ 6 files changed, 68 insertions(+), 14 deletions(-) create mode 100644 .github/workflows/radsan.yml create mode 100644 RTNeural/platform/platform.h create mode 100644 cmake/RADSan.cmake diff --git a/.github/workflows/radsan.yml b/.github/workflows/radsan.yml new file mode 100644 index 00000000..7df82f63 --- /dev/null +++ b/.github/workflows/radsan.yml @@ -0,0 +1,33 @@ +name: RADSan Real-Time Safety + +on: + push: + branches: + - main + - develop + pull_request: + branches: + - main + - develop + +jobs: + build_and_test: + name: Check real-time safety with RADSan + runs-on: ubuntu-latest + container: realtimesanitizer/radsan-clang:latest + steps: + - name: Install CMake and Git + run: apt-get update && apt-get install -y cmake git + + - name: Checkout code + uses: actions/checkout@v2 + with: + submodules: true + + - name: Configure + run: cmake -Bbuild -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTS=ON -DRTNEURAL_ENABLE_RADSAN=ON + + - name: Test + run: ctest -C Release --test-dir build --parallel --output-on-failure + + diff --git a/CMakeLists.txt b/CMakeLists.txt index 5be249ba..8b403022 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,7 @@ cmake_minimum_required(VERSION 3.1) project(RTNeural VERSION 1.0.0) include(cmake/CXXStandard.cmake) +include(cmake/RADSan.cmake) add_subdirectory(RTNeural) @@ -27,3 +28,7 @@ if(BUILD_EXAMPLES) message(STATUS "RTNeural -- Configuring examples...") add_subdirectory(examples) endif() + +if(RTNEURAL_ENABLE_RADSAN) + rtneural_radsan_configure(RTNeural) +endif() diff --git a/RTNeural/Model.h b/RTNeural/Model.h index 9144b513..d94f5f33 100644 --- a/RTNeural/Model.h +++ b/RTNeural/Model.h @@ -18,6 +18,7 @@ #include "gru/gru.tpp" #include "lstm/lstm.h" #include "lstm/lstm.tpp" +#include "platform/platform.h" namespace RTNeural { @@ -71,14 +72,14 @@ class Model } /** Resets the state of the network layers. */ - void reset() + RTNEURAL_RT_ATTR void reset() { for(auto* l : layers) l->reset(); } /** Performs forward propagation for this model. */ - inline T forward(const T* input) + RTNEURAL_RT_ATTR inline T forward(const T* input) { layers[0]->forward(input, outs[0].data()); @@ -91,7 +92,7 @@ class Model } /** Returns a pointer to the output of the final layer in the network. */ - inline const T* getOutputs() const noexcept + RTNEURAL_RT_ATTR inline const T* getOutputs() const noexcept { return outs.back().data(); } diff --git a/RTNeural/ModelT.h b/RTNeural/ModelT.h index 7376e6bf..250e0779 100644 --- a/RTNeural/ModelT.h +++ b/RTNeural/ModelT.h @@ -1,6 +1,7 @@ #pragma once #include "model_loader.h" +#include "platform/platform.h" namespace RTNeural { @@ -350,20 +351,20 @@ class ModelT /** Get a reference to the layer at index `Index`. */ template - auto& get() noexcept + RTNEURAL_RT_ATTR auto& get() noexcept { return std::get(layers); } /** Get a reference to the layer at index `Index`. */ template - const auto& get() const noexcept + RTNEURAL_RT_ATTR const auto& get() const noexcept { return std::get(layers); } /** Resets the state of the network layers. */ - void reset() + RTNEURAL_RT_ATTR void reset() { modelt_detail::forEachInTuple([&](auto& layer, size_t) { layer.reset(); }, @@ -372,7 +373,7 @@ class ModelT /** Performs forward propagation for this model. */ template - inline typename std::enable_if<(N > 1), T>::type + RTNEURAL_RT_ATTR inline typename std::enable_if<(N > 1), T>::type forward(const T* input) { #if RTNEURAL_USE_XSIMD @@ -399,7 +400,7 @@ class ModelT /** Performs forward propagation for this model. */ template - inline typename std::enable_if::type + RTNEURAL_RT_ATTR inline typename std::enable_if::type forward(const T* input) { #if RTNEURAL_USE_XSIMD @@ -425,7 +426,7 @@ class ModelT } /** Returns a pointer to the output of the final layer in the network. */ - inline const T* getOutputs() const noexcept + RTNEURAL_RT_ATTR inline const T* getOutputs() const noexcept { return outs; } @@ -502,20 +503,20 @@ class ModelT2D /** Get a reference to the layer at index `Index`. */ template - auto& get() noexcept + RTNEURAL_RT_ATTR auto& get() noexcept { return std::get(layers); } /** Get a reference to the layer at index `Index`. */ template - const auto& get() const noexcept + RTNEURAL_RT_ATTR const auto& get() const noexcept { return std::get(layers); } /** Resets the state of the network layers. */ - void reset() + RTNEURAL_RT_ATTR void reset() { modelt_detail::forEachInTuple([&](auto& layer, size_t) { layer.reset(); }, @@ -523,7 +524,7 @@ class ModelT2D } /** Performs forward propagation for this model. */ - inline T forward(const T* input) + RTNEURAL_RT_ATTR inline T forward(const T* input) { for(int feature_index = 0; feature_index < num_features_in; ++feature_index) { @@ -547,7 +548,7 @@ class ModelT2D } /** Returns a pointer to the output of the final layer in the network. */ - inline const T* getOutputs() const noexcept + RTNEURAL_RT_ATTR inline const T* getOutputs() const noexcept { return outs; } diff --git a/RTNeural/platform/platform.h b/RTNeural/platform/platform.h new file mode 100644 index 00000000..c0a7e9f7 --- /dev/null +++ b/RTNeural/platform/platform.h @@ -0,0 +1,7 @@ +#pragma once + +#ifdef RTNEURAL_RADSAN_ENABLED + #define RTNEURAL_RT_ATTR [[clang::realtime]] +#else + #define RTNEURAL_RT_ATTR +#endif diff --git a/cmake/RADSan.cmake b/cmake/RADSan.cmake new file mode 100644 index 00000000..a2d86944 --- /dev/null +++ b/cmake/RADSan.cmake @@ -0,0 +1,7 @@ +option(RTNEURAL_ENABLE_RADSAN "Enable RADSan checks (requires RADSan clang)" OFF) + +function(rtneural_radsan_configure target) + target_compile_definitions(${target} PUBLIC RTNEURAL_RADSAN_ENABLED) + target_compile_options(${target} PUBLIC -fsanitize=realtime) + target_link_options(${target} PUBLIC -fsanitize=realtime) +endfunction() From 2e6b86e19d3d67ce0096ce1a9117d527d08983a7 Mon Sep 17 00:00:00 2001 From: David Trevelyan Date: Wed, 22 Nov 2023 20:46:07 +0000 Subject: [PATCH 2/7] Allow radsan workflow on all pushes temporarily --- .github/workflows/radsan.yml | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/.github/workflows/radsan.yml b/.github/workflows/radsan.yml index 7df82f63..19108fce 100644 --- a/.github/workflows/radsan.yml +++ b/.github/workflows/radsan.yml @@ -1,14 +1,15 @@ name: RADSan Real-Time Safety -on: - push: - branches: - - main - - develop - pull_request: - branches: - - main - - develop +on: [push] +#on: +# push: +# branches: +# - main +# - develop +# pull_request: +# branches: +# - main +# - develop jobs: build_and_test: From 7687910cbdd7e8c2227c4fc630386cb087089d85 Mon Sep 17 00:00:00 2001 From: David Trevelyan Date: Wed, 22 Nov 2023 20:49:36 +0000 Subject: [PATCH 3/7] Trigger workflow --- .github/workflows/radsan.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/radsan.yml b/.github/workflows/radsan.yml index 19108fce..36c30548 100644 --- a/.github/workflows/radsan.yml +++ b/.github/workflows/radsan.yml @@ -1,6 +1,7 @@ name: RADSan Real-Time Safety on: [push] + #on: # push: # branches: From 7f488cd624ab3ff2e4620ff202d71af4cad01889 Mon Sep 17 00:00:00 2001 From: David Trevelyan Date: Wed, 22 Nov 2023 20:51:40 +0000 Subject: [PATCH 4/7] Add missing build step to RADSan workflow --- .github/workflows/radsan.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/radsan.yml b/.github/workflows/radsan.yml index 36c30548..76dd07ad 100644 --- a/.github/workflows/radsan.yml +++ b/.github/workflows/radsan.yml @@ -29,6 +29,9 @@ jobs: - name: Configure run: cmake -Bbuild -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTS=ON -DRTNEURAL_ENABLE_RADSAN=ON + - name: Build + run: cmake --build build --config Release --parallel + - name: Test run: ctest -C Release --test-dir build --parallel --output-on-failure From 85a4fcdb408e04afb1bb965037913b7970dff404 Mon Sep 17 00:00:00 2001 From: David Trevelyan Date: Wed, 22 Nov 2023 21:05:07 +0000 Subject: [PATCH 5/7] Remove specified configuration in ctest invocation --- cmake/Testing.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/Testing.cmake b/cmake/Testing.cmake index 40ff9eb1..cd445d56 100644 --- a/cmake/Testing.cmake +++ b/cmake/Testing.cmake @@ -3,7 +3,7 @@ option(RTNEURAL_TEST_REPORTS "Output test reports to XML files" OFF) macro(rtneural_setup_testing) include(CTest) enable_testing() - add_custom_target(rtneural_test COMMAND ctest -C ${Configuration} --output-on-failure) + add_custom_target(rtneural_test COMMAND ctest --output-on-failure) CPMAddPackage("gh:google/googletest@1.14.0") endmacro() From 187f346af09bbf06bec5e47e757d3765dc0d459f Mon Sep 17 00:00:00 2001 From: David Trevelyan Date: Wed, 22 Nov 2023 21:10:19 +0000 Subject: [PATCH 6/7] Remove ctest arguments in workflow --- .github/workflows/radsan.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/radsan.yml b/.github/workflows/radsan.yml index 76dd07ad..02164f81 100644 --- a/.github/workflows/radsan.yml +++ b/.github/workflows/radsan.yml @@ -33,6 +33,4 @@ jobs: run: cmake --build build --config Release --parallel - name: Test - run: ctest -C Release --test-dir build --parallel --output-on-failure - - + run: cd build && ctest --output-on-failure From c796e0985710adb4aa6727b852f3748183a63a43 Mon Sep 17 00:00:00 2001 From: David Trevelyan Date: Wed, 22 Nov 2023 22:11:32 +0000 Subject: [PATCH 7/7] Demonstrate RADSan testing with dedicated target and testing macro --- .github/workflows/radsan.yml | 4 +- RTNeural/Model.h | 7 +-- RTNeural/ModelT.h | 23 ++++--- RTNeural/platform/platform.h | 7 --- tests/CMakeLists.txt | 4 ++ tests/realtime/CMakeLists.txt | 4 ++ tests/realtime/assertions.hpp | 15 +++++ tests/realtime/modelt_test.cpp | 111 +++++++++++++++++++++++++++++++++ 8 files changed, 150 insertions(+), 25 deletions(-) delete mode 100644 RTNeural/platform/platform.h create mode 100644 tests/realtime/CMakeLists.txt create mode 100644 tests/realtime/assertions.hpp create mode 100644 tests/realtime/modelt_test.cpp diff --git a/.github/workflows/radsan.yml b/.github/workflows/radsan.yml index 02164f81..e8a065ea 100644 --- a/.github/workflows/radsan.yml +++ b/.github/workflows/radsan.yml @@ -30,7 +30,7 @@ jobs: run: cmake -Bbuild -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTS=ON -DRTNEURAL_ENABLE_RADSAN=ON - name: Build - run: cmake --build build --config Release --parallel + run: cmake --build build --config Release --parallel --target rtneural_test_realtime - name: Test - run: cd build && ctest --output-on-failure + run: cd build && ./tests/realtime/rtneural_test_realtime diff --git a/RTNeural/Model.h b/RTNeural/Model.h index d94f5f33..9144b513 100644 --- a/RTNeural/Model.h +++ b/RTNeural/Model.h @@ -18,7 +18,6 @@ #include "gru/gru.tpp" #include "lstm/lstm.h" #include "lstm/lstm.tpp" -#include "platform/platform.h" namespace RTNeural { @@ -72,14 +71,14 @@ class Model } /** Resets the state of the network layers. */ - RTNEURAL_RT_ATTR void reset() + void reset() { for(auto* l : layers) l->reset(); } /** Performs forward propagation for this model. */ - RTNEURAL_RT_ATTR inline T forward(const T* input) + inline T forward(const T* input) { layers[0]->forward(input, outs[0].data()); @@ -92,7 +91,7 @@ class Model } /** Returns a pointer to the output of the final layer in the network. */ - RTNEURAL_RT_ATTR inline const T* getOutputs() const noexcept + inline const T* getOutputs() const noexcept { return outs.back().data(); } diff --git a/RTNeural/ModelT.h b/RTNeural/ModelT.h index 250e0779..7376e6bf 100644 --- a/RTNeural/ModelT.h +++ b/RTNeural/ModelT.h @@ -1,7 +1,6 @@ #pragma once #include "model_loader.h" -#include "platform/platform.h" namespace RTNeural { @@ -351,20 +350,20 @@ class ModelT /** Get a reference to the layer at index `Index`. */ template - RTNEURAL_RT_ATTR auto& get() noexcept + auto& get() noexcept { return std::get(layers); } /** Get a reference to the layer at index `Index`. */ template - RTNEURAL_RT_ATTR const auto& get() const noexcept + const auto& get() const noexcept { return std::get(layers); } /** Resets the state of the network layers. */ - RTNEURAL_RT_ATTR void reset() + void reset() { modelt_detail::forEachInTuple([&](auto& layer, size_t) { layer.reset(); }, @@ -373,7 +372,7 @@ class ModelT /** Performs forward propagation for this model. */ template - RTNEURAL_RT_ATTR inline typename std::enable_if<(N > 1), T>::type + inline typename std::enable_if<(N > 1), T>::type forward(const T* input) { #if RTNEURAL_USE_XSIMD @@ -400,7 +399,7 @@ class ModelT /** Performs forward propagation for this model. */ template - RTNEURAL_RT_ATTR inline typename std::enable_if::type + inline typename std::enable_if::type forward(const T* input) { #if RTNEURAL_USE_XSIMD @@ -426,7 +425,7 @@ class ModelT } /** Returns a pointer to the output of the final layer in the network. */ - RTNEURAL_RT_ATTR inline const T* getOutputs() const noexcept + inline const T* getOutputs() const noexcept { return outs; } @@ -503,20 +502,20 @@ class ModelT2D /** Get a reference to the layer at index `Index`. */ template - RTNEURAL_RT_ATTR auto& get() noexcept + auto& get() noexcept { return std::get(layers); } /** Get a reference to the layer at index `Index`. */ template - RTNEURAL_RT_ATTR const auto& get() const noexcept + const auto& get() const noexcept { return std::get(layers); } /** Resets the state of the network layers. */ - RTNEURAL_RT_ATTR void reset() + void reset() { modelt_detail::forEachInTuple([&](auto& layer, size_t) { layer.reset(); }, @@ -524,7 +523,7 @@ class ModelT2D } /** Performs forward propagation for this model. */ - RTNEURAL_RT_ATTR inline T forward(const T* input) + inline T forward(const T* input) { for(int feature_index = 0; feature_index < num_features_in; ++feature_index) { @@ -548,7 +547,7 @@ class ModelT2D } /** Returns a pointer to the output of the final layer in the network. */ - RTNEURAL_RT_ATTR inline const T* getOutputs() const noexcept + inline const T* getOutputs() const noexcept { return outs; } diff --git a/RTNeural/platform/platform.h b/RTNeural/platform/platform.h deleted file mode 100644 index c0a7e9f7..00000000 --- a/RTNeural/platform/platform.h +++ /dev/null @@ -1,7 +0,0 @@ -#pragma once - -#ifdef RTNEURAL_RADSAN_ENABLED - #define RTNEURAL_RT_ATTR [[clang::realtime]] -#else - #define RTNEURAL_RT_ATTR -#endif diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 4516e917..8e912dd0 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,6 +1,10 @@ add_subdirectory(unit) add_subdirectory(functional) +if(RTNEURAL_ENABLE_RADSAN) + add_subdirectory(realtime) +endif() + option(RTNEURAL_CODE_COVERAGE "Build RTNeural tests with code coverage flags" OFF) if(RTNEURAL_CODE_COVERAGE) include(${CMAKE_CURRENT_SOURCE_DIR}/../cmake/EnableCoverageFlags.cmake) diff --git a/tests/realtime/CMakeLists.txt b/tests/realtime/CMakeLists.txt new file mode 100644 index 00000000..1a3a5dfa --- /dev/null +++ b/tests/realtime/CMakeLists.txt @@ -0,0 +1,4 @@ +rtneural_add_test( + TARGET rtneural_test_realtime + SOURCES modelt_test.cpp + DEPENDENCIES PRIVATE RTNeural) diff --git a/tests/realtime/assertions.hpp b/tests/realtime/assertions.hpp new file mode 100644 index 00000000..e18c4589 --- /dev/null +++ b/tests/realtime/assertions.hpp @@ -0,0 +1,15 @@ +#pragma once + +#ifdef RTNEURAL_RADSAN_ENABLED + +extern "C" void radsan_realtime_enter(); +extern "C" void radsan_realtime_exit(); +#define EXPECT_REAL_TIME_SAFE(statement) \ + radsan_realtime_enter(); \ + statement; \ + radsan_realtime_exit(); +#else +#define EXPECT_REAL_TIME_SAFE(statement) \ + #error EXPECT_REAL_TIME_SAFE \ + requires RTNEURAL_RADSAN_ENABLED +#endif diff --git a/tests/realtime/modelt_test.cpp b/tests/realtime/modelt_test.cpp new file mode 100644 index 00000000..812c7095 --- /dev/null +++ b/tests/realtime/modelt_test.cpp @@ -0,0 +1,111 @@ +#include + +#include "../functional/load_csv.hpp" +#include "../functional/test_configs.hpp" +#include "assertions.hpp" + +#include + +namespace +{ +using TestType = double; +using namespace RTNeural; + +template +void checkRealTimeSafety(const TestConfig& test) +{ + std::ifstream jsonStream(std::string { RTNEURAL_ROOT_DIR } + test.model_file, std::ifstream::binary); + StaticModelType static_model; + static_model.parseJson(jsonStream, true); + static_model.reset(); + + jsonStream.seekg(0); + auto dynamic_model = RTNeural::json_parser::parseJson(jsonStream, true); + dynamic_model->reset(); + + T input[] = { 0.5f }; + EXPECT_REAL_TIME_SAFE(static_model.forward(input)); + EXPECT_REAL_TIME_SAFE(dynamic_model->forward(input)); +} +} + +TEST(TestTemplatedModels, doesNotCauseRADSanErrorWithDense) +{ + using ModelType = ModelT, + TanhActivationT, + DenseT, + ReLuActivationT, + DenseT, + ELuActivationT, + DenseT, + SoftmaxActivationT, + DenseT>; + + checkRealTimeSafety(tests.at("dense")); +} + +TEST(TestTemplatedModels, doesNotCauseRADSanErrorWithConv1D) +{ + using ModelType = ModelT, + TanhActivationT, + Conv1DT, + TanhActivationT, + BatchNorm1DT, + PReLUActivationT, + Conv1DT, + TanhActivationT, + Conv1DT, + TanhActivationT, + BatchNorm1DT, + PReLUActivationT, + DenseT, + SigmoidActivationT>; + + checkRealTimeSafety(tests.at("conv1d")); +} + +TEST(TestTemplatedModels, doesNotCauseRADSanErrorWithGRU) +{ + using ModelType = ModelT, + TanhActivationT, + GRULayerT, + DenseT, + SigmoidActivationT, + DenseT>; + + checkRealTimeSafety(tests.at("gru")); +} + +TEST(TestTemplatedModels, doesNotCauseRADSanErrorWithGRU1D) +{ + using ModelType = ModelT, + DenseT, + SigmoidActivationT, + DenseT>; + + checkRealTimeSafety(tests.at("gru_1d")); +} + +TEST(TestTemplatedModels, doesNotCauseRADSanErrorWithLSTM) +{ + using ModelType = ModelT, + TanhActivationT, + LSTMLayerT, + DenseT>; + + checkRealTimeSafety(tests.at("lstm")); +} + +TEST(TestTemplatedModels, doesNotCauseRADSanErrorWithLSTM1D) +{ + using ModelType = ModelT, + DenseT>; + + checkRealTimeSafety(tests.at("lstm_1d")); +}