From 6719eeca7cf82dc72dbeb117d60c43f3fe110e50 Mon Sep 17 00:00:00 2001 From: Abhishek Shivakumar Date: Wed, 26 Aug 2026 08:51:21 +0100 Subject: [PATCH 1/2] Professionalize core packaging and release contract --- .github/workflows/ci.yml | 78 ++- CHANGELOG.md | 36 ++ CMakeLists.txt | 795 +++++++++++--------------- LICENSING.md | 86 +-- README.md | 104 ++-- TESTING.md | 120 ++-- cmake/TinyMLConfig.cmake.in | 26 + cmake/TinyMLVersion.hpp.in | 6 + docs/STABILITY.md | 39 ++ include/Model.h | 246 ++------ include/NN.h | 67 +-- include/Network.h | 71 ++- include/tinyml/core.hpp | 6 + include/tinyml/tinyml.hpp | 17 + src/NN.cpp | 104 ++-- src/Network.cpp | 180 ++++-- tests/core_smoke.cpp | 53 ++ tests/install_consumer/CMakeLists.txt | 9 + tests/install_consumer/main.cpp | 11 + 19 files changed, 1089 insertions(+), 965 deletions(-) create mode 100644 CHANGELOG.md create mode 100644 cmake/TinyMLConfig.cmake.in create mode 100644 cmake/TinyMLVersion.hpp.in create mode 100644 docs/STABILITY.md create mode 100644 include/tinyml/core.hpp create mode 100644 include/tinyml/tinyml.hpp create mode 100644 tests/core_smoke.cpp create mode 100644 tests/install_consumer/CMakeLists.txt create mode 100644 tests/install_consumer/main.cpp diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6e4c9be..471a139 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -17,20 +17,27 @@ concurrency: jobs: build-and-test: - name: ${{ matrix.compiler }} / ${{ matrix.build_type }} + name: ${{ matrix.name }} runs-on: ubuntu-latest strategy: fail-fast: false matrix: include: - - compiler: GCC + - name: Core / GCC / Release cc: gcc cxx: g++ build_type: Release - - compiler: Clang + extended: 'OFF' + - name: Extended / GCC / Release + cc: gcc + cxx: g++ + build_type: Release + extended: 'ON' + - name: Extended / Clang / Debug cc: clang cxx: clang++ build_type: Debug + extended: 'ON' env: CC: ${{ matrix.cc }} CXX: ${{ matrix.cxx }} @@ -45,7 +52,11 @@ jobs: sudo apt-get install -y ninja-build - name: Configure - run: cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} + run: | + cmake -S . -B build -G Ninja \ + -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} \ + -DTINYML_BUILD_EXTENDED=${{ matrix.extended }} \ + -DTINYML_BUILD_TESTS=ON - name: Build run: cmake --build build --parallel 2 @@ -53,6 +64,17 @@ jobs: - name: Test run: ctest --test-dir build --output-on-failure --timeout 120 + - name: Install package + run: cmake --install build --prefix "$PWD/install" + + - name: Downstream package smoke test + run: | + cmake -S tests/install_consumer -B consumer-build -G Ninja \ + -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} \ + -DCMAKE_PREFIX_PATH="$PWD/install" + cmake --build consumer-build --parallel 2 + ./consumer-build/tinyml_install_consumer + release: if: startsWith(github.ref, 'refs/tags/v') needs: build-and-test @@ -69,24 +91,42 @@ jobs: sudo apt-get update sudo apt-get install -y ninja-build - - name: Configure - run: cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release - - - name: Build - run: cmake --build build --parallel 2 - - - name: Package + - name: Build and stage core package + run: | + core_dir="tinyml-${{ github.ref_name }}-core-linux-x86_64" + cmake -S . -B build-core -G Ninja \ + -DCMAKE_BUILD_TYPE=Release \ + -DTINYML_BUILD_EXTENDED=OFF \ + -DTINYML_BUILD_TESTS=OFF + cmake --build build-core --parallel 2 + cmake --install build-core --prefix "$PWD/$core_dir" + tar -czf "$core_dir.tar.gz" "$core_dir" + + - name: Build and stage extended package + run: | + extended_dir="tinyml-${{ github.ref_name }}-extended-linux-x86_64" + cmake -S . -B build-extended -G Ninja \ + -DCMAKE_BUILD_TYPE=Release \ + -DTINYML_BUILD_EXTENDED=ON \ + -DTINYML_BUILD_TESTS=OFF + cmake --build build-extended --parallel 2 + cmake --install build-extended --prefix "$PWD/$extended_dir" + tar -czf "$extended_dir.tar.gz" "$extended_dir" + + - name: Create checksums + run: sha256sum tinyml-${{ github.ref_name }}-*.tar.gz > SHA256SUMS + + - name: Inspect packages run: | - package_dir="tinyml-${{ github.ref_name }}-linux-x86_64" - mkdir -p "$package_dir/lib" "$package_dir/include" - cp build/libTinyML.a "$package_dir/lib/" - cp -R include/. "$package_dir/include/" - cp README.md LICENSE LICENSE-COMMERCIAL.md LICENSING.md "$package_dir/" - tar -czf "$package_dir.tar.gz" "$package_dir" - tar -tzf "$package_dir.tar.gz" | head -80 + cat SHA256SUMS + tar -tzf tinyml-${{ github.ref_name }}-core-linux-x86_64.tar.gz | head -80 + tar -tzf tinyml-${{ github.ref_name }}-extended-linux-x86_64.tar.gz | head -80 - name: Publish GitHub release uses: softprops/action-gh-release@v2 with: - files: tinyml-${{ github.ref_name }}-linux-x86_64.tar.gz + files: | + tinyml-${{ github.ref_name }}-core-linux-x86_64.tar.gz + tinyml-${{ github.ref_name }}-extended-linux-x86_64.tar.gz + SHA256SUMS generate_release_notes: true diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..130d47f --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,36 @@ +# Changelog + +All notable release-facing changes to tinyML are recorded here. + +The stable core follows semantic versioning. Preview and source-only modules are identified in `docs/STABILITY.md`. + +## Unreleased + +### Added + +- Installable `TinyML::Core` and `TinyML::Extended` CMake package targets. +- Component-aware `find_package(TinyML)` support. +- Dependency-free core build mode. +- Generated `` version macros. +- Stable `` and extended `` umbrella headers. +- Downstream install/consume smoke test. +- Explicit API stability policy. + +### Changed + +- Replaced source globbing with explicit, reviewable source lists. +- Made xsimd discovery/fetch conditional on the extended target. +- Made GoogleTest conditional on tests or benchmark builds. +- Made benchmarks, examples and the playground opt-in. +- Replaced global optimization/compiler flags with target-local configuration. +- Release packaging now uses the CMake install graph. +- Core network inputs, targets and weight vectors now reject invalid dimensions with exceptions instead of relying on assertions or unchecked indexing. +- Network bias neurons use a conventional constant value of `1.0`. +- Weight initialization no longer mutates process-global `rand()` state. + +### Fixed + +- Initialized connection momentum state before the first training update. +- Removed the hard-coded `101.0` divisor and unchecked connection indexing from weight normalization. +- Corrected `Network::updateWeights()` so updates are applied through the destination layer rather than indexing each previous-layer neuron against itself. +- Added exact weight-count validation before replacing model weights. diff --git a/CMakeLists.txt b/CMakeLists.txt index 35c1786..d3ae32c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,483 +1,386 @@ -# CMake version and project name -cmake_minimum_required(VERSION 3.10) -project(TinyML VERSION 1.0 LANGUAGES CXX) - -# Enable CTest so add_test() registrations are picked up. -include(CTest) -enable_testing() - -# Set the C++ standard -set(CMAKE_CXX_STANDARD 17) -set(CMAKE_CXX_STANDARD_REQUIRED True) - -# Find xsimd library -find_package(xsimd QUIET) - -if(NOT xsimd_FOUND) - # Fetch xsimd if not found - include(FetchContent) - FetchContent_Declare( - xsimd - URL https://github.com/xtensor-stack/xsimd/archive/refs/tags/12.1.1.tar.gz - ) - FetchContent_MakeAvailable(xsimd) -endif() +cmake_minimum_required(VERSION 3.20) -# Real-time optimizations -set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O3 -DNDEBUG -flto") -set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -g -O0") - -# Set output directories for binaries -set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) - -# Collect all source files from the src directory -file(GLOB_RECURSE SOURCES "src/*.cpp") -list(REMOVE_ITEM SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/src/SIMDOperations.cpp) -list(REMOVE_ITEM SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/src/NEONOperations.cpp) -list(REMOVE_ITEM SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/src/AdvancedOptimizations.cpp) -list(REMOVE_ITEM SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/src/GenerativeModels.cpp) -list(REMOVE_ITEM SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/src/BayesianNeuralNetwork.cpp) - -# Only include existing source files -set(EXISTING_SOURCES) -foreach(SOURCE ${SOURCES}) - if(EXISTS ${SOURCE}) - list(APPEND EXISTING_SOURCES ${SOURCE}) - endif() -endforeach() +project( + TinyML + VERSION 1.0.0 + DESCRIPTION "Lightweight C++ machine learning for embedded and real-time systems" + LANGUAGES CXX +) -# Add GenerativeModels source if it exists -if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/src/GenerativeModels.cpp) - list(APPEND EXISTING_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/src/GenerativeModels.cpp) -endif() -set(SOURCES ${EXISTING_SOURCES}) +include(GNUInstallDirs) +include(CMakePackageConfigHelpers) -# Print out the source files collected -message(STATUS "Source files: ${SOURCES}") +if(CMAKE_SOURCE_DIR STREQUAL PROJECT_SOURCE_DIR) + set(TINYML_TOP_LEVEL ON) +else() + set(TINYML_TOP_LEVEL OFF) +endif() -# Create the main library target -add_library(TinyML ${SOURCES}) +option(TINYML_BUILD_EXTENDED "Build the xsimd-backed extended TinyML library" ON) +option(TINYML_BUILD_TESTS "Build TinyML's test suite" ${TINYML_TOP_LEVEL}) +option(TINYML_BUILD_BENCHMARKS "Build local benchmark executables" OFF) +option(TINYML_BUILD_EXAMPLES "Build example executables" OFF) +option(TINYML_BUILD_PLAYGROUND "Build the interactive playground server" OFF) +option(TINYML_FETCH_DEPENDENCIES "Fetch missing third-party build dependencies" ${TINYML_TOP_LEVEL}) +option(TINYML_ENABLE_WARNINGS "Enable compiler warnings for TinyML targets" ${TINYML_TOP_LEVEL}) +option(TINYML_ENABLE_LTO "Enable interprocedural optimization when supported" OFF) + +if(TINYML_BUILD_BENCHMARKS AND NOT TINYML_BUILD_EXTENDED) + message(FATAL_ERROR "TINYML_BUILD_BENCHMARKS requires TINYML_BUILD_EXTENDED=ON") +endif() -# Ensure that the include directories for the library are available to targets that link with the library -target_include_directories(TinyML PUBLIC ${PROJECT_SOURCE_DIR}/include) +if(TINYML_BUILD_EXAMPLES AND NOT TINYML_BUILD_EXTENDED) + message(FATAL_ERROR "TINYML_BUILD_EXAMPLES requires TINYML_BUILD_EXTENDED=ON") +endif() -# Link with math library if needed -if(UNIX AND NOT APPLE) - target_link_libraries(TinyML m) +if(TINYML_BUILD_PLAYGROUND AND NOT TINYML_BUILD_EXTENDED) + message(FATAL_ERROR "TINYML_BUILD_PLAYGROUND requires TINYML_BUILD_EXTENDED=ON") endif() -# Link with xsimd if available -if(xsimd_FOUND) - target_link_libraries(TinyML xsimd::xsimd) - target_include_directories(TinyML PUBLIC ${xsimd_INCLUDE_DIRS}) -else() - # Try to find xsimd in the build directory - if(EXISTS "${CMAKE_BINARY_DIR}/_deps/xsimd-src/include") - target_include_directories(TinyML PUBLIC "${CMAKE_BINARY_DIR}/_deps/xsimd-src/include") +function(tinyml_configure_target target) + target_compile_features(${target} PUBLIC cxx_std_17) + target_include_directories( + ${target} + PUBLIC + $ + $ + $ + ) + set_target_properties( + ${target} + PROPERTIES + POSITION_INDEPENDENT_CODE ON + WINDOWS_EXPORT_ALL_SYMBOLS ON + ) + + if(TINYML_ENABLE_WARNINGS) + if(MSVC) + target_compile_options(${target} PRIVATE /W4 /permissive-) + elseif(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang|AppleClang") + target_compile_options(${target} PRIVATE -Wall -Wextra -Wpedantic) + endif() endif() -endif() -# Print the include directories being set -message(STATUS "Include directory for TinyML: ${PROJECT_SOURCE_DIR}/include") + if(TINYML_ENABLE_LTO) + include(CheckIPOSupported) + check_ipo_supported(RESULT ipo_supported OUTPUT ipo_error) + if(ipo_supported) + set_property(TARGET ${target} PROPERTY INTERPROCEDURAL_OPTIMIZATION TRUE) + else() + message(WARNING "IPO/LTO requested for ${target}, but unsupported: ${ipo_error}") + endif() + endif() +endfunction() -# GoogleTest integration (optional) -include(FetchContent) -FetchContent_Declare( - googletest - URL https://github.com/google/googletest/archive/refs/tags/release-1.12.1.zip +file(MAKE_DIRECTORY "${PROJECT_BINARY_DIR}/generated/tinyml") +configure_file( + "${PROJECT_SOURCE_DIR}/cmake/TinyMLVersion.hpp.in" + "${PROJECT_BINARY_DIR}/generated/tinyml/version.hpp" + @ONLY ) -set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) -FetchContent_MakeAvailable(googletest) - -# Tests - only include working GoogleTest test files -set(TEST_SOURCES - ${CMAKE_CURRENT_SOURCE_DIR}/tests/test_phase1_simd.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/tests/test_phase3_dynamic_new.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/tests/test_phase4_fixed.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/tests/test_phase4_simple.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/tests/test_phase5_quantized.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/tests/test_phase6_production.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/tests/test_phase7_advanced_attention.cpp + +set(TINYML_CORE_SOURCES + src/NN.cpp + src/Network.cpp ) -# Add GenerativeModels test if it exists -if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/tests/test_phase11_generative.cpp) - list(APPEND TEST_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/tests/test_phase11_generative.cpp) -endif() +set(TINYML_CORE_HEADERS + include/NN.h + include/Network.h + include/Model.h +) -# Print out the test source files collected -message(STATUS "Test source files: ${TEST_SOURCES}") +add_library(TinyMLCore ${TINYML_CORE_SOURCES}) +add_library(TinyML::Core ALIAS TinyMLCore) +set_target_properties( + TinyMLCore + PROPERTIES + EXPORT_NAME Core + OUTPUT_NAME tinyml-core + VERSION ${PROJECT_VERSION} + SOVERSION ${PROJECT_VERSION_MAJOR} +) +tinyml_configure_target(TinyMLCore) + +set(TINYML_EXTENDED_HEADERS + include/AdvancedAttention.h + include/DynamicNeuralNetwork.h + include/GenerativeModels.h + include/GraphNeuralNetwork.h + include/LightweightAttention.h + include/PhysicsInformedNN.h + include/ProductionAPI.h + include/QuantizedOperations.h + include/RealTimeTransformer.h + include/ReinforcementLearning.h + include/TimeSeriesForecasting.h + include/TinyMLAPI.h + include/XSIMDOperations.h +) -# Create test executable -add_executable(TinyMLTests ${TEST_SOURCES}) +if(TINYML_BUILD_EXTENDED) + find_package(xsimd 12.1 QUIET CONFIG) -# Link the test executable with Google Test and your library -target_link_libraries(TinyMLTests TinyML gtest gtest_main) + if(TARGET xsimd) + set(TINYML_XSIMD_TARGET xsimd) + elseif(TARGET xsimd::xsimd) + set(TINYML_XSIMD_TARGET xsimd::xsimd) + else() + if(NOT TINYML_FETCH_DEPENDENCIES) + message(FATAL_ERROR + "TinyML extended requires xsimd >= 12.1. Install xsimd or configure with " + "-DTINYML_FETCH_DEPENDENCIES=ON. Core-only builds never require xsimd." + ) + endif() -# Ensure the test target also gets the correct include directories -target_include_directories(TinyMLTests PUBLIC ${PROJECT_SOURCE_DIR}/include) -if(xsimd_FOUND) - target_link_libraries(TinyMLTests xsimd::xsimd) - target_include_directories(TinyMLTests PRIVATE ${xsimd_INCLUDE_DIRS}) -else() - if(EXISTS "${CMAKE_BINARY_DIR}/_deps/xsimd-src/include") - target_include_directories(TinyMLTests PRIVATE "${CMAKE_BINARY_DIR}/_deps/xsimd-src/include") + include(FetchContent) + set(XSIMD_SKIP_INSTALL ON CACHE BOOL "" FORCE) + FetchContent_Declare( + xsimd + URL https://github.com/xtensor-stack/xsimd/archive/refs/tags/12.1.1.tar.gz + ) + FetchContent_MakeAvailable(xsimd) + + if(TARGET xsimd) + set(TINYML_XSIMD_TARGET xsimd) + elseif(TARGET xsimd::xsimd) + set(TINYML_XSIMD_TARGET xsimd::xsimd) + else() + message(FATAL_ERROR "xsimd was fetched but did not provide a supported CMake target") + endif() endif() -endif() -# Print the include directories being set for the test target -message(STATUS "Include directory for TinyMLTests: ${PROJECT_SOURCE_DIR}/include") -add_test(NAME TinyMLTests COMMAND TinyMLTests) - -# Basic Test executable -add_executable(BasicTests tests/basic_tests.cpp) -target_link_libraries(BasicTests TinyML gtest gtest_main) -target_include_directories(BasicTests PUBLIC ${PROJECT_SOURCE_DIR}/include) -if(xsimd_FOUND) - target_link_libraries(BasicTests xsimd::xsimd) - target_include_directories(BasicTests PRIVATE ${xsimd_INCLUDE_DIRS}) -else() - if(EXISTS "${CMAKE_BINARY_DIR}/_deps/xsimd-src/include") - target_include_directories(BasicTests PRIVATE "${CMAKE_BINARY_DIR}/_deps/xsimd-src/include") - endif() -endif() -add_test(NAME BasicTests COMMAND BasicTests) - -# Phase 2 Attention Test executable -add_executable(Phase2AttentionTest tests/test_phase2_attention_xsimd.cpp) -target_link_libraries(Phase2AttentionTest TinyML gtest gtest_main) -target_include_directories(Phase2AttentionTest PUBLIC ${PROJECT_SOURCE_DIR}/include) -if(xsimd_FOUND) - target_link_libraries(Phase2AttentionTest xsimd::xsimd) - target_include_directories(Phase2AttentionTest PRIVATE ${xsimd_INCLUDE_DIRS}) -else() - if(EXISTS "${CMAKE_BINARY_DIR}/_deps/xsimd-src/include") - target_include_directories(Phase2AttentionTest PRIVATE "${CMAKE_BINARY_DIR}/_deps/xsimd-src/include") - endif() -endif() -# Phase 2 Simple Test executable -add_executable(Phase2SimpleTest tests/test_phase2_simple.cpp) -target_link_libraries(Phase2SimpleTest TinyML gtest gtest_main) -target_include_directories(Phase2SimpleTest PUBLIC ${PROJECT_SOURCE_DIR}/include) -if(xsimd_FOUND) - target_link_libraries(Phase2SimpleTest xsimd::xsimd) - target_include_directories(Phase2SimpleTest PRIVATE ${xsimd_INCLUDE_DIRS}) -else() - if(EXISTS "${CMAKE_BINARY_DIR}/_deps/xsimd-src/include") - target_include_directories(Phase2SimpleTest PRIVATE "${CMAKE_BINARY_DIR}/_deps/xsimd-src/include") - endif() -endif() -# Phase 3 Dynamic Test executable -add_executable(Phase3DynamicTest tests/test_phase3_dynamic_new.cpp) -target_link_libraries(Phase3DynamicTest TinyML gtest gtest_main) -target_include_directories(Phase3DynamicTest PUBLIC ${PROJECT_SOURCE_DIR}/include) -if(xsimd_FOUND) - target_link_libraries(Phase3DynamicTest xsimd::xsimd) - target_include_directories(Phase3DynamicTest PRIVATE ${xsimd_INCLUDE_DIRS}) -else() - if(EXISTS "${CMAKE_BINARY_DIR}/_deps/xsimd-src/include") - target_include_directories(Phase3DynamicTest PRIVATE "${CMAKE_BINARY_DIR}/_deps/xsimd-src/include") - endif() -endif() -# Phase 4 Transformer Test executable -add_executable(Phase4TransformerTest tests/test_phase4_transformer_new.cpp) -target_link_libraries(Phase4TransformerTest TinyML gtest gtest_main) -target_include_directories(Phase4TransformerTest PUBLIC ${PROJECT_SOURCE_DIR}/include) -if(xsimd_FOUND) - target_link_libraries(Phase4TransformerTest xsimd::xsimd) - target_include_directories(Phase4TransformerTest PRIVATE ${xsimd_INCLUDE_DIRS}) -else() - if(EXISTS "${CMAKE_BINARY_DIR}/_deps/xsimd-src/include") - target_include_directories(Phase4TransformerTest PRIVATE "${CMAKE_BINARY_DIR}/_deps/xsimd-src/include") - endif() -endif() -# Phase 4 Simple Transformer Test executable -add_executable(Phase4SimpleTest tests/test_phase4_simple.cpp) -target_link_libraries(Phase4SimpleTest TinyML gtest gtest_main) -target_include_directories(Phase4SimpleTest PUBLIC ${PROJECT_SOURCE_DIR}/include) -if(xsimd_FOUND) - target_link_libraries(Phase4SimpleTest xsimd::xsimd) - target_include_directories(Phase4SimpleTest PRIVATE ${xsimd_INCLUDE_DIRS}) -else() - if(EXISTS "${CMAKE_BINARY_DIR}/_deps/xsimd-src/include") - target_include_directories(Phase4SimpleTest PRIVATE "${CMAKE_BINARY_DIR}/_deps/xsimd-src/include") - endif() -endif() -# Production API Test executable -add_executable(ProductionAPITest tests/test_production_api.cpp) -target_link_libraries(ProductionAPITest TinyML gtest gtest_main) -target_include_directories(ProductionAPITest PUBLIC ${PROJECT_SOURCE_DIR}/include) -if(xsimd_FOUND) - target_link_libraries(ProductionAPITest xsimd::xsimd) - target_include_directories(ProductionAPITest PRIVATE ${xsimd_INCLUDE_DIRS}) -else() - if(EXISTS "${CMAKE_BINARY_DIR}/_deps/xsimd-src/include") - target_include_directories(ProductionAPITest PRIVATE "${CMAKE_BINARY_DIR}/_deps/xsimd-src/include") - endif() -endif() -# Phase 4 Fixed Transformer Test executable -add_executable(Phase4FixedTest tests/test_phase4_fixed.cpp) -target_link_libraries(Phase4FixedTest TinyML gtest gtest_main) -target_include_directories(Phase4FixedTest PUBLIC ${PROJECT_SOURCE_DIR}/include) -if(xsimd_FOUND) - target_link_libraries(Phase4FixedTest xsimd::xsimd) - target_include_directories(Phase4FixedTest PRIVATE ${xsimd_INCLUDE_DIRS}) -else() - if(EXISTS "${CMAKE_BINARY_DIR}/_deps/xsimd-src/include") - target_include_directories(Phase4FixedTest PRIVATE "${CMAKE_BINARY_DIR}/_deps/xsimd-src/include") - endif() -endif() -add_test(NAME Phase4FixedTest COMMAND Phase4FixedTest) - -# Phase 5 Quantized Operations Test executable -add_executable(Phase5QuantizedTest tests/test_phase5_quantized.cpp) -target_link_libraries(Phase5QuantizedTest TinyML gtest gtest_main) -target_include_directories(Phase5QuantizedTest PUBLIC ${PROJECT_SOURCE_DIR}/include) -if(xsimd_FOUND) - target_link_libraries(Phase5QuantizedTest xsimd::xsimd) - target_include_directories(Phase5QuantizedTest PRIVATE ${xsimd_INCLUDE_DIRS}) -else() - if(EXISTS "${CMAKE_BINARY_DIR}/_deps/xsimd-src/include") - target_include_directories(Phase5QuantizedTest PRIVATE "${CMAKE_BINARY_DIR}/_deps/xsimd-src/include") - endif() -endif() -add_test(NAME Phase5QuantizedTest COMMAND Phase5QuantizedTest) - -# Phase 6 Production API Test executable -add_executable(Phase6ProductionTest tests/test_phase6_production_new.cpp) -target_link_libraries(Phase6ProductionTest TinyML gtest gtest_main) -target_include_directories(Phase6ProductionTest PUBLIC ${PROJECT_SOURCE_DIR}/include) -if(xsimd_FOUND) - target_link_libraries(Phase6ProductionTest xsimd::xsimd) - target_include_directories(Phase6ProductionTest PRIVATE ${xsimd_INCLUDE_DIRS}) -else() - if(EXISTS "${CMAKE_BINARY_DIR}/_deps/xsimd-src/include") - target_include_directories(Phase6ProductionTest PRIVATE "${CMAKE_BINARY_DIR}/_deps/xsimd-src/include") - endif() -endif() -# Phase 7 Advanced Attention Test executable -add_executable(Phase7AdvancedAttentionTest tests/test_phase7_advanced_attention.cpp) -target_link_libraries(Phase7AdvancedAttentionTest TinyML gtest gtest_main) -target_include_directories(Phase7AdvancedAttentionTest PUBLIC ${PROJECT_SOURCE_DIR}/include) -if(xsimd_FOUND) - target_link_libraries(Phase7AdvancedAttentionTest xsimd::xsimd) - target_include_directories(Phase7AdvancedAttentionTest PRIVATE ${xsimd_INCLUDE_DIRS}) -else() - if(EXISTS "${CMAKE_BINARY_DIR}/_deps/xsimd-src/include") - target_include_directories(Phase7AdvancedAttentionTest PRIVATE "${CMAKE_BINARY_DIR}/_deps/xsimd-src/include") - endif() -endif() -add_test(NAME Phase7AdvancedAttentionTest COMMAND Phase7AdvancedAttentionTest) - -# Phase 8 Physics-Informed Neural Networks Test executable -add_executable(Phase8PhysicsInformedTest tests/test_phase8_physics_informed.cpp) -target_link_libraries(Phase8PhysicsInformedTest TinyML gtest gtest_main) -target_include_directories(Phase8PhysicsInformedTest PUBLIC ${PROJECT_SOURCE_DIR}/include) -if(xsimd_FOUND) - target_link_libraries(Phase8PhysicsInformedTest xsimd::xsimd) - target_include_directories(Phase8PhysicsInformedTest PRIVATE ${xsimd_INCLUDE_DIRS}) -else() - if(EXISTS "${CMAKE_BINARY_DIR}/_deps/xsimd-src/include") - target_include_directories(Phase8PhysicsInformedTest PRIVATE "${CMAKE_BINARY_DIR}/_deps/xsimd-src/include") - endif() -endif() -add_test(NAME Phase8PhysicsInformedTest COMMAND Phase8PhysicsInformedTest) -# Takes 16+ minutes on CI - too slow for regular CI runs -set_tests_properties(Phase8PhysicsInformedTest PROPERTIES DISABLED true) - -# Phase 8 Comprehensive Physics-Informed Neural Networks Test executable -add_executable(Phase8PhysicsComprehensiveTest tests/test_phase8_physics_comprehensive.cpp) -target_link_libraries(Phase8PhysicsComprehensiveTest TinyML gtest gtest_main) -target_include_directories(Phase8PhysicsComprehensiveTest PUBLIC ${PROJECT_SOURCE_DIR}/include) -if(xsimd_FOUND) - target_link_libraries(Phase8PhysicsComprehensiveTest xsimd::xsimd) - target_include_directories(Phase8PhysicsComprehensiveTest PRIVATE ${xsimd_INCLUDE_DIRS}) -else() - if(EXISTS "${CMAKE_BINARY_DIR}/_deps/xsimd-src/include") - target_include_directories(Phase8PhysicsComprehensiveTest PRIVATE "${CMAKE_BINARY_DIR}/_deps/xsimd-src/include") - endif() -endif() -add_test(NAME Phase8PhysicsComprehensiveTest COMMAND Phase8PhysicsComprehensiveTest) -# Flaky convergence test - non-deterministic results vary by environment -set_tests_properties(Phase8PhysicsComprehensiveTest PROPERTIES DISABLED true) - -# Phase 10 Graph Neural Networks Test executable -add_executable(Phase10GraphNeuralTest tests/test_phase10_graph_neural.cpp) -target_link_libraries(Phase10GraphNeuralTest TinyML gtest gtest_main) -target_include_directories(Phase10GraphNeuralTest PUBLIC ${PROJECT_SOURCE_DIR}/include) -if(xsimd_FOUND) - target_link_libraries(Phase10GraphNeuralTest xsimd::xsimd) - target_include_directories(Phase10GraphNeuralTest PRIVATE ${xsimd_INCLUDE_DIRS}) -else() - if(EXISTS "${CMAKE_BINARY_DIR}/_deps/xsimd-src/include") - target_include_directories(Phase10GraphNeuralTest PRIVATE "${CMAKE_BINARY_DIR}/_deps/xsimd-src/include") - endif() -endif() -add_test(NAME Phase10GraphNeuralTest COMMAND Phase10GraphNeuralTest) - -# Production API Demo executable -add_executable(ProductionAPIDemo EXCLUDE_FROM_ALL examples/production_api_demo.cpp) -target_link_libraries(ProductionAPIDemo TinyML) -target_include_directories(ProductionAPIDemo PUBLIC ${PROJECT_SOURCE_DIR}/include) -if(xsimd_FOUND) - target_link_libraries(ProductionAPIDemo xsimd::xsimd) - target_include_directories(ProductionAPIDemo PRIVATE ${xsimd_INCLUDE_DIRS}) -else() - if(EXISTS "${CMAKE_BINARY_DIR}/_deps/xsimd-src/include") - target_include_directories(ProductionAPIDemo PRIVATE "${CMAKE_BINARY_DIR}/_deps/xsimd-src/include") - endif() -endif() + set(TINYML_EXTENDED_SOURCES + src/AdvancedAttention.cpp + src/DynamicNeuralNetwork.cpp + src/GenerativeModels.cpp + src/GraphNeuralNetwork.cpp + src/LightweightAttention.cpp + src/PDESolvers.cpp + src/PhysicsInformedNN.cpp + src/ProductionAPI.cpp + src/QuantizedOperations.cpp + src/RealTimeTransformer.cpp + src/ReinforcementLearning.cpp + src/TimeSeriesForecasting.cpp + src/TinyMLAPI.cpp + src/TinyMLUtils.cpp + src/XSIMDOperations.cpp + ) -# ============================================================================= -# BENCHMARKS -# These are built but NOT registered as CTest tests so they don't run in CI. -# Free GitHub Actions runners are too slow/unreliable for benchmarks — results -# are meaningless on shared hardware and long runs waste CI minutes. -# Run benchmarks locally: cd build && ./bin/SIMDBenchmark -# See TESTING.md for full details. -# ============================================================================= - -# SIMD Benchmark executable (using XSIMD) -add_executable(SIMDBenchmark benchmarks/benchmark_xsimd.cpp) -target_link_libraries(SIMDBenchmark TinyML gtest gtest_main) -target_include_directories(SIMDBenchmark PUBLIC ${PROJECT_SOURCE_DIR}/include) -if(xsimd_FOUND) - target_link_libraries(SIMDBenchmark xsimd::xsimd) - target_include_directories(SIMDBenchmark PRIVATE ${xsimd_INCLUDE_DIRS}) -else() - if(EXISTS "${CMAKE_BINARY_DIR}/_deps/xsimd-src/include") - target_include_directories(SIMDBenchmark PRIVATE "${CMAKE_BINARY_DIR}/_deps/xsimd-src/include") - endif() -endif() -# Not registered as ctest — run locally only - -# Physics-Informed Neural Networks Benchmark executable -add_executable(PhysicsBenchmark EXCLUDE_FROM_ALL benchmarks/benchmark_physics.cpp) -target_link_libraries(PhysicsBenchmark TinyML) -target_include_directories(PhysicsBenchmark PUBLIC ${PROJECT_SOURCE_DIR}/include) -if(xsimd_FOUND) - target_link_libraries(PhysicsBenchmark xsimd::xsimd) - target_include_directories(PhysicsBenchmark PRIVATE ${xsimd_INCLUDE_DIRS}) -else() - if(EXISTS "${CMAKE_BINARY_DIR}/_deps/xsimd-src/include") - target_include_directories(PhysicsBenchmark PRIVATE "${CMAKE_BINARY_DIR}/_deps/xsimd-src/include") + add_library(TinyML ${TINYML_EXTENDED_SOURCES}) + add_library(TinyML::Extended ALIAS TinyML) + add_library(TinyML::TinyML ALIAS TinyML) + set_target_properties( + TinyML + PROPERTIES + EXPORT_NAME Extended + OUTPUT_NAME tinyml + VERSION ${PROJECT_VERSION} + SOVERSION ${PROJECT_VERSION_MAJOR} + ) + tinyml_configure_target(TinyML) + target_link_libraries(TinyML PUBLIC TinyMLCore ${TINYML_XSIMD_TARGET}) + + if(UNIX AND NOT APPLE) + target_link_libraries(TinyML PRIVATE m) endif() endif() -# Lightweight Attention Test executable -add_executable(LightweightAttentionTest tests/test_lightweight_attention.cpp) -target_link_libraries(LightweightAttentionTest TinyML gtest gtest_main) -target_include_directories(LightweightAttentionTest PUBLIC ${PROJECT_SOURCE_DIR}/include) -add_test(NAME LightweightAttentionTest COMMAND LightweightAttentionTest) - -# Attention Benchmark executable -add_executable(AttentionBenchmark benchmarks/benchmark_attention.cpp) -target_link_libraries(AttentionBenchmark TinyML gtest gtest_main) -target_include_directories(AttentionBenchmark PUBLIC ${PROJECT_SOURCE_DIR}/include) -# Not registered as ctest — run locally only - -# Simple Attention Benchmark executable (standalone) -add_executable(SimpleAttentionBenchmark benchmarks/benchmark_simple_attention.cpp) -target_include_directories(SimpleAttentionBenchmark PUBLIC ${PROJECT_SOURCE_DIR}/include) -# Not registered as ctest — run locally only - -# Advanced Optimizations Benchmark executable - disabled until -# AdvancedOptimizations.cpp is fixed (missing member fields, headers) -message(STATUS "Skipping AdvancedOptimizationsBenchmark (AdvancedOptimizations.cpp needs fixes)") - -# Phase 13 Time Series Forecasting Test executable -add_executable(Phase13TimeSeriesTest tests/test_phase13_time_series.cpp) -target_link_libraries(Phase13TimeSeriesTest TinyML gtest gtest_main) -target_include_directories(Phase13TimeSeriesTest PUBLIC ${PROJECT_SOURCE_DIR}/include) -if(xsimd_FOUND) - target_link_libraries(Phase13TimeSeriesTest xsimd::xsimd) - target_include_directories(Phase13TimeSeriesTest PRIVATE ${xsimd_INCLUDE_DIRS}) -else() - if(EXISTS "${CMAKE_BINARY_DIR}/_deps/xsimd-src/include") - target_include_directories(Phase13TimeSeriesTest PRIVATE "${CMAKE_BINARY_DIR}/_deps/xsimd-src/include") +if((TINYML_BUILD_TESTS AND TINYML_BUILD_EXTENDED) OR TINYML_BUILD_BENCHMARKS) + find_package(GTest QUIET) + + if(NOT TARGET GTest::gtest_main) + if(NOT TINYML_FETCH_DEPENDENCIES) + message(FATAL_ERROR + "TinyML tests/benchmarks require GoogleTest. Install it or configure with " + "-DTINYML_FETCH_DEPENDENCIES=ON." + ) + endif() + + include(FetchContent) + set(INSTALL_GTEST OFF CACHE BOOL "" FORCE) + set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) + FetchContent_Declare( + googletest + URL https://github.com/google/googletest/archive/refs/tags/release-1.12.1.zip + ) + FetchContent_MakeAvailable(googletest) endif() endif() -add_test(NAME Phase13TimeSeriesTest COMMAND Phase13TimeSeriesTest) - -# Phase 12 Reinforcement Learning Test executable -add_executable(Phase12ReinforcementTest tests/test_phase12_reinforcement.cpp) -target_link_libraries(Phase12ReinforcementTest TinyML gtest gtest_main) -target_include_directories(Phase12ReinforcementTest PUBLIC ${PROJECT_SOURCE_DIR}/include) -if(xsimd_FOUND) - target_link_libraries(Phase12ReinforcementTest xsimd::xsimd) - target_include_directories(Phase12ReinforcementTest PRIVATE ${xsimd_INCLUDE_DIRS}) -else() - if(EXISTS "${CMAKE_BINARY_DIR}/_deps/xsimd-src/include") - target_include_directories(Phase12ReinforcementTest PRIVATE "${CMAKE_BINARY_DIR}/_deps/xsimd-src/include") + +if(TINYML_BUILD_TESTS) + enable_testing() + + add_executable(TinyMLCoreSmoke tests/core_smoke.cpp) + target_link_libraries(TinyMLCoreSmoke PRIVATE TinyMLCore) + target_compile_features(TinyMLCoreSmoke PRIVATE cxx_std_17) + add_test(NAME TinyMLCoreSmoke COMMAND TinyMLCoreSmoke) + + if(TINYML_BUILD_EXTENDED) + function(tinyml_add_gtest target) + add_executable(${target} ${ARGN}) + target_link_libraries(${target} PRIVATE TinyML GTest::gtest_main) + target_compile_features(${target} PRIVATE cxx_std_17) + add_test(NAME ${target} COMMAND ${target}) + endfunction() + + tinyml_add_gtest( + TinyMLTests + tests/test_phase1_simd.cpp + tests/test_phase3_dynamic_new.cpp + tests/test_phase4_fixed.cpp + tests/test_phase4_simple.cpp + tests/test_phase5_quantized.cpp + tests/test_phase6_production.cpp + tests/test_phase7_advanced_attention.cpp + tests/test_phase11_generative.cpp + ) + tinyml_add_gtest(BasicTests tests/basic_tests.cpp) + tinyml_add_gtest(Phase4FixedTest tests/test_phase4_fixed.cpp) + tinyml_add_gtest(Phase5QuantizedTest tests/test_phase5_quantized.cpp) + tinyml_add_gtest(Phase7AdvancedAttentionTest tests/test_phase7_advanced_attention.cpp) + tinyml_add_gtest(Phase8PhysicsInformedTest tests/test_phase8_physics_informed.cpp) + tinyml_add_gtest(Phase8PhysicsComprehensiveTest tests/test_phase8_physics_comprehensive.cpp) + tinyml_add_gtest(Phase10GraphNeuralTest tests/test_phase10_graph_neural.cpp) + tinyml_add_gtest(LightweightAttentionTest tests/test_lightweight_attention.cpp) + tinyml_add_gtest(Phase13TimeSeriesTest tests/test_phase13_time_series.cpp) + tinyml_add_gtest(Phase12ReinforcementTest tests/test_phase12_reinforcement.cpp) + tinyml_add_gtest(Phase11GenerativeTest tests/test_phase11_generative.cpp) + + set_tests_properties( + Phase8PhysicsInformedTest + PROPERTIES + DISABLED TRUE + LABELS "long-running" + ) + set_tests_properties( + Phase8PhysicsComprehensiveTest + PROPERTIES + DISABLED TRUE + LABELS "flaky" + ) + set_tests_properties( + Phase12ReinforcementTest + PROPERTIES + DISABLED TRUE + LABELS "known-failure" + ) endif() endif() -add_test(NAME Phase12ReinforcementTest COMMAND Phase12ReinforcementTest) -# Known segfault in RL test - disable until fixed -set_tests_properties(Phase12ReinforcementTest PROPERTIES DISABLED true) - -# Reinforcement Learning Benchmark executable -add_executable(ReinforcementLearningBenchmark benchmarks/benchmark_reinforcement.cpp) -target_link_libraries(ReinforcementLearningBenchmark TinyML gtest gtest_main) -target_include_directories(ReinforcementLearningBenchmark PUBLIC ${PROJECT_SOURCE_DIR}/include) -if(xsimd_FOUND) - target_link_libraries(ReinforcementLearningBenchmark xsimd::xsimd) - target_include_directories(ReinforcementLearningBenchmark PRIVATE ${xsimd_INCLUDE_DIRS}) -else() - if(EXISTS "${CMAKE_BINARY_DIR}/_deps/xsimd-src/include") - target_include_directories(ReinforcementLearningBenchmark PRIVATE "${CMAKE_BINARY_DIR}/_deps/xsimd-src/include") - endif() + +if(TINYML_BUILD_BENCHMARKS) + add_executable(SIMDBenchmark benchmarks/benchmark_xsimd.cpp) + target_link_libraries(SIMDBenchmark PRIVATE TinyML GTest::gtest_main) + + add_executable(AttentionBenchmark benchmarks/benchmark_attention.cpp) + target_link_libraries(AttentionBenchmark PRIVATE TinyML GTest::gtest_main) + + add_executable(SimpleAttentionBenchmark benchmarks/benchmark_simple_attention.cpp) + target_include_directories(SimpleAttentionBenchmark PRIVATE ${PROJECT_SOURCE_DIR}/include) + target_compile_features(SimpleAttentionBenchmark PRIVATE cxx_std_17) + + add_executable(PhysicsBenchmark benchmarks/benchmark_physics.cpp) + target_link_libraries(PhysicsBenchmark PRIVATE TinyML) + + add_executable(GenerativeModelsBenchmark benchmarks/benchmark_generative.cpp) + target_link_libraries(GenerativeModelsBenchmark PRIVATE TinyML GTest::gtest_main) endif() -# Not registered as ctest — segfaults on CI, run locally only - -# Phase 11 Generative Models Test executable -if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/tests/test_phase11_generative.cpp) - add_executable(Phase11GenerativeTest tests/test_phase11_generative.cpp) - target_link_libraries(Phase11GenerativeTest TinyML gtest gtest_main) - target_include_directories(Phase11GenerativeTest PUBLIC ${PROJECT_SOURCE_DIR}/include) - if(xsimd_FOUND) - target_link_libraries(Phase11GenerativeTest xsimd::xsimd) - target_include_directories(Phase11GenerativeTest PRIVATE ${xsimd_INCLUDE_DIRS}) - else() - if(EXISTS "${CMAKE_BINARY_DIR}/_deps/xsimd-src/include") - target_include_directories(Phase11GenerativeTest PRIVATE "${CMAKE_BINARY_DIR}/_deps/xsimd-src/include") - endif() - endif() - add_test(NAME Phase11GenerativeTest COMMAND Phase11GenerativeTest) + +if(TINYML_BUILD_EXAMPLES) + add_executable(ProductionAPIDemo examples/production_api_demo.cpp) + target_link_libraries(ProductionAPIDemo PRIVATE TinyML) endif() -# Generative Models Benchmark executable -if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/benchmarks/benchmark_generative.cpp) - add_executable(GenerativeModelsBenchmark benchmarks/benchmark_generative.cpp) - target_link_libraries(GenerativeModelsBenchmark TinyML gtest gtest_main) - target_include_directories(GenerativeModelsBenchmark PUBLIC ${PROJECT_SOURCE_DIR}/include) - if(xsimd_FOUND) - target_link_libraries(GenerativeModelsBenchmark xsimd::xsimd) - target_include_directories(GenerativeModelsBenchmark PRIVATE ${xsimd_INCLUDE_DIRS}) - else() - if(EXISTS "${CMAKE_BINARY_DIR}/_deps/xsimd-src/include") - target_include_directories(GenerativeModelsBenchmark PRIVATE "${CMAKE_BINARY_DIR}/_deps/xsimd-src/include") - endif() - endif() - # Not registered as ctest — run locally only +if(TINYML_BUILD_PLAYGROUND) + add_executable( + PlaygroundServer + playground/server.cpp + src/BayesianNeuralNetwork.cpp + ) + target_link_libraries(PlaygroundServer PRIVATE TinyML) + target_include_directories(PlaygroundServer PRIVATE ${PROJECT_SOURCE_DIR}/playground/include) endif() -# Debugging: Print the include directories that will be passed to the compiler -get_target_property(INCLUDE_DIRS TinyML INCLUDE_DIRECTORIES) -message(STATUS "TinyML include directories: ${INCLUDE_DIRS}") +install( + TARGETS TinyMLCore + EXPORT TinyMLCoreTargets + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} +) + +install( + FILES ${TINYML_CORE_HEADERS} + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} +) +install( + FILES include/tinyml/core.hpp + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/tinyml +) +install( + FILES "${PROJECT_BINARY_DIR}/generated/tinyml/version.hpp" + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/tinyml +) + +install( + EXPORT TinyMLCoreTargets + FILE TinyMLCoreTargets.cmake + NAMESPACE TinyML:: + DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/TinyML +) -get_target_property(INCLUDE_DIRS_TEST TinyMLTests INCLUDE_DIRECTORIES) -message(STATUS "TinyMLTests include directories: ${INCLUDE_DIRS_TEST}") +if(TINYML_BUILD_EXTENDED) + install( + TARGETS TinyML + EXPORT TinyMLExtendedTargets + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} + ) + install( + FILES ${TINYML_EXTENDED_HEADERS} + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} + ) + install( + FILES include/tinyml/tinyml.hpp + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/tinyml + ) + install( + EXPORT TinyMLExtendedTargets + FILE TinyMLExtendedTargets.cmake + NAMESPACE TinyML:: + DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/TinyML + ) +endif() -# Playground server executable -add_executable(PlaygroundServer playground/server.cpp src/BayesianNeuralNetwork.cpp) -target_link_libraries(PlaygroundServer TinyML) -target_include_directories(PlaygroundServer PUBLIC ${PROJECT_SOURCE_DIR}/include ${PROJECT_SOURCE_DIR}/playground/include) +configure_package_config_file( + cmake/TinyMLConfig.cmake.in + "${PROJECT_BINARY_DIR}/TinyMLConfig.cmake" + INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/TinyML +) +write_basic_package_version_file( + "${PROJECT_BINARY_DIR}/TinyMLConfigVersion.cmake" + VERSION ${PROJECT_VERSION} + COMPATIBILITY SameMajorVersion +) -if(xsimd_FOUND) - target_link_libraries(PlaygroundServer xsimd::xsimd) - target_include_directories(PlaygroundServer PRIVATE ${xsimd_INCLUDE_DIRS}) -else() - if(EXISTS "${CMAKE_BINARY_DIR}/_deps/xsimd-src/include") - target_include_directories(PlaygroundServer PRIVATE "${CMAKE_BINARY_DIR}/_deps/xsimd-src/include") - endif() +install( + FILES + "${PROJECT_BINARY_DIR}/TinyMLConfig.cmake" + "${PROJECT_BINARY_DIR}/TinyMLConfigVersion.cmake" + DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/TinyML +) + +install( + FILES README.md LICENSE LICENSING.md + DESTINATION ${CMAKE_INSTALL_DATADIR}/TinyML +) +if(TINYML_BUILD_EXTENDED) + install( + FILES LICENSE-COMMERCIAL.md + DESTINATION ${CMAKE_INSTALL_DATADIR}/TinyML + ) endif() diff --git a/LICENSING.md b/LICENSING.md index 4d0a708..a37bf63 100644 --- a/LICENSING.md +++ b/LICENSING.md @@ -1,57 +1,33 @@ # Licensing -tinyML is split in two. The core is MIT. The extended model library is -commercial. The split follows the dependency graph, so it is enforceable by -inspection: no MIT header includes a commercial one. - -## MIT core - -Zero third-party dependencies. Compiler intrinsics (``, -``) only. Free for any use, including commercial, under -[LICENSE](LICENSE). - -| Header | | -| --- | --- | -| `include/common.h` | Shared types and helpers | -| `include/logger.h` | Logging | -| `include/NN.h` | Neural network primitives | -| `include/Network.h` | Network composition | -| `include/Model.h` | Model container | -| `include/Perceptron.h` | Perceptron | -| `include/VectorOperations.h` | Vector maths | -| `include/VectorStatistics.h` | Statistical analysis | -| `include/SIMDOperations.h` | SSE/AVX paths | -| `include/NEONOperations.h` | ARM NEON paths | -| `include/QuantizedOperations.h` | Quantised arithmetic | -| `include/AdvancedOptimizations.h` | Optimiser implementations | -| `include/BayesianNeuralNetwork.h` | Bayesian networks | -| `include/TinyMLAPI.h` | Public API surface | - -## Commercial - -Requires [xsimd](https://github.com/xtensor-stack/xsimd) (BSD-3-Clause). -Covered by [LICENSE-COMMERCIAL.md](LICENSE-COMMERCIAL.md). Free for research, -education and personal projects. Commercial use requires a paid licence. - -| Header | | -| --- | --- | -| `include/XSIMDOperations.h` | xsimd backend, the root of this tier | -| `include/AdvancedAttention.h` | Attention variants | -| `include/LightweightAttention.h` | Reduced-cost attention | -| `include/RealTimeTransformer.h` | Streaming transformer | -| `include/DynamicNeuralNetwork.h` | Dynamic topology networks | -| `include/GenerativeModels.h` | Generative models | -| `include/GraphNeuralNetwork.h` | Graph networks | -| `include/PhysicsInformedNN.h` | Physics-informed networks | -| `include/ReinforcementLearning.h` | RL agents | -| `include/TimeSeriesForecasting.h` | Forecasting | -| `include/ProductionAPI.h` | Production deployment API | - -## Why the split falls here - -Commercial headers may include MIT headers. MIT headers include nothing from -the commercial tier. That means the MIT core compiles and ships on its own, -with no third-party dependency and no licence entanglement. - -To build core-only, exclude the commercial headers from your include path. -They are not referenced by anything in the core. +tinyML uses a mixed-license repository. Licensing and API stability are separate concepts: [docs/STABILITY.md](docs/STABILITY.md) defines what is supported and installed, while this document defines the license that applies to source files. + +## MIT-licensed code + +The following files are licensed under [LICENSE](LICENSE), including commercial use under the terms of the MIT License: + +`include/common.h`, `include/logger.h`, `include/NN.h`, `include/Network.h`, `include/Model.h`, `include/Perceptron.h`, `include/VectorOperations.h`, `include/VectorStatistics.h`, `include/SIMDOperations.h`, `include/NEONOperations.h`, `include/QuantizedOperations.h`, `include/AdvancedOptimizations.h`, `include/BayesianNeuralNetwork.h`, `include/TinyMLAPI.h` and their corresponding MIT implementation files where present. + +The supported `TinyML::Core` distribution deliberately contains only a smaller stable subset: `NN.h`, `Network.h`, `Model.h`, `src/NN.cpp` and `src/Network.cpp`, plus the generated/version umbrella headers. A core-only build does not discover, fetch, link or expose xsimd. + +Some historical files contain older copyright banners. Copyright ownership is compatible with an open-source license grant; for files explicitly identified as MIT in this document, [LICENSE](LICENSE) is the repository's license grant for that code. + +## Commercially licensed code + +The following modules are covered by [LICENSE-COMMERCIAL.md](LICENSE-COMMERCIAL.md): + +`include/XSIMDOperations.h`, `include/AdvancedAttention.h`, `include/LightweightAttention.h`, `include/RealTimeTransformer.h`, `include/DynamicNeuralNetwork.h`, `include/GenerativeModels.h`, `include/GraphNeuralNetwork.h`, `include/PhysicsInformedNN.h`, `include/ReinforcementLearning.h`, `include/TimeSeriesForecasting.h`, `include/ProductionAPI.h` and their corresponding implementation files. + +The `TinyML::Extended` target combines these modules with MIT-licensed support code. Linking MIT code into the extended target does not relicense that MIT code, but use of the commercially licensed modules remains subject to the commercial license. + +`TinyMLAPI.h` itself remains MIT licensed, but its current implementation is intentionally part of `TinyML::Extended` because it instantiates extended model types. It is therefore not part of the zero-dependency core artifact. + +## Third-party dependency + +The extended target uses [xsimd](https://github.com/xtensor-stack/xsimd), which is distributed under the BSD 3-Clause License. Core-only builds do not require xsimd. + +## Distribution boundary + +The build graph enforces the distribution boundary. `TINYML_BUILD_EXTENDED=OFF` creates and installs only the MIT core target and stable core headers. Enabling the extended build creates a separate `TinyML::Extended` target and extended install export. + +The release archives mirror that boundary: the core archive is dependency-free; the extended archive requires an xsimd package when consumed through CMake. diff --git a/README.md b/README.md index e2cf7bd..7ab7052 100644 --- a/README.md +++ b/README.md @@ -1,69 +1,101 @@ -# TinyML +# tinyML [![CI](https://github.com/godofecht/tinyML/actions/workflows/ci.yml/badge.svg)](https://github.com/godofecht/tinyML/actions/workflows/ci.yml) -[![Pages](https://github.com/godofecht/tinyML/actions/workflows/pages.yml/badge.svg)](https://godofecht.github.io/tinyML/) -![C++](https://img.shields.io/badge/C%2B%2B-17%2F20-blue.svg) -![License](https://img.shields.io/badge/license-MIT%20core%20%2B%20commercial-blue.svg) +[![GitHub Pages](https://github.com/godofecht/tinyML/actions/workflows/pages.yml/badge.svg)](https://github.com/godofecht/tinyML/actions/workflows/pages.yml) +![C++17](https://img.shields.io/badge/C%2B%2B-17%2B-blue.svg) +![Version](https://img.shields.io/badge/version-1.0.0-blue.svg) -**TinyML is a lightweight, high-performance C++ machine-learning and statistical-computing library aimed at real-time and embedded use.** It implements the stack directly in modern C++, with SIMD-aware kernels, quantization, neural-network architectures, scientific ML, reinforcement learning, graph models, generative models and an interactive playground. +A lightweight C++ machine-learning library for embedded, edge and real-time workloads. -**Project site:** https://godofecht.github.io/tinyML/ +The repository contains two intentionally different surfaces. `TinyML::Core` is the supported, dependency-free library surface with an install/export contract and semantic-versioning guarantees. `TinyML::Extended` contains the xsimd-backed model stack and research modules. The extended target is built by default for source compatibility, but its individual modules have their own stability status documented in [docs/STABILITY.md](docs/STABILITY.md). -## What is here +## Requirements -The library includes feed-forward networks, CNNs, RNNs, streaming Transformers, Bayesian neural networks, VAEs, GANs, PINNs, policy-gradient and Q-learning examples, graph neural networks, time-series forecasting, quantized inference, SIMD operations and production-oriented serving helpers. +The core requires a C++17 compiler and CMake 3.20 or newer. It has no third-party runtime or build dependency. -The `playground/` directory contains a C++ backend plus a browser frontend for interactive scenarios such as CartPole, Pong, attention visualisation, CNN operations, PINNs and graph diffusion. The GitHub Pages site is a separate static project front page; the full playground still runs against the local C++ server. +The extended target additionally requires xsimd 12.1 or newer. Top-level source builds can fetch xsimd automatically; package consumers are expected to provide it through their normal dependency manager. ## Build +Core only: + ```bash -git clone https://github.com/godofecht/tinyML.git -cd tinyML -cmake -S . -B build -DCMAKE_BUILD_TYPE=Release +cmake -S . -B build -DTINYML_BUILD_EXTENDED=OFF cmake --build build --parallel ctest --test-dir build --output-on-failure ``` -CMake fetches xsimd and GoogleTest when they are not already available. See [TESTING.md](TESTING.md) for the distinction between correctness tests, disabled long-running tests and opt-in timing assertions. +Full source build: -## Playground +```bash +cmake -S . -B build +cmake --build build --parallel +ctest --test-dir build --output-on-failure +``` + +Benchmarks, examples and the playground are deliberately excluded from ordinary library builds. Enable them explicitly with `TINYML_BUILD_BENCHMARKS`, `TINYML_BUILD_EXAMPLES` or `TINYML_BUILD_PLAYGROUND`. + +## Install and consume ```bash -cmake --build build --target PlaygroundServer --parallel -./build/bin/PlaygroundServer +cmake -S . -B build -DTINYML_BUILD_EXTENDED=OFF -DTINYML_BUILD_TESTS=OFF +cmake --build build --parallel +cmake --install build --prefix ./install ``` -Then open `http://localhost:8081`. +A downstream CMake project can then use the installed core without xsimd: -## Repository map +```cmake +find_package(TinyML 1.0 CONFIG REQUIRED COMPONENTS Core) +target_link_libraries(my_target PRIVATE TinyML::Core) +``` -```text -include/ public library headers -src/ implementation -examples/ usage examples -benchmarks/ local benchmark programs -playground/ C++ server + browser UI -tests/ GoogleTest suite -docs/ API, demo and wiki documentation -blog/ implementation and architecture notes -site/ static GitHub Pages site +```cpp +#include + +ML::Model model ({ 2, 4, 1 }); +model.feedForward ({ 0.25, -0.5 }); +auto output = model.getResult(); +``` + +For the extended package: + +```cmake +find_package(TinyML 1.0 CONFIG REQUIRED COMPONENTS Extended) +target_link_libraries(my_target PRIVATE TinyML::Extended) ``` -## CI and releases +The installed package includes a generated `` with `TINYML_VERSION_MAJOR`, `TINYML_VERSION_MINOR`, `TINYML_VERSION_PATCH` and `TINYML_VERSION_STRING`. -Every pull request and push to `main` builds the project with both GCC and Clang and runs the registered CTest suite. Version tags matching `v*` additionally produce a Linux x86-64 release archive. +## Build options -Wall-clock performance assertions are intentionally opt-in because shared CI hardware is not a meaningful benchmark environment. Use `TINYML_PERF_ASSERTS=1` locally when you explicitly want those thresholds enforced. +| Option | Default | Purpose | +| --- | --- | --- | +| `TINYML_BUILD_EXTENDED` | `ON` | Build the xsimd-backed extended library | +| `TINYML_BUILD_TESTS` | top-level only | Build the test suite | +| `TINYML_BUILD_BENCHMARKS` | `OFF` | Build local benchmark executables | +| `TINYML_BUILD_EXAMPLES` | `OFF` | Build examples | +| `TINYML_BUILD_PLAYGROUND` | `OFF` | Build the playground server | +| `TINYML_FETCH_DEPENDENCIES` | top-level only | Fetch missing xsimd/GoogleTest dependencies | +| `TINYML_ENABLE_WARNINGS` | top-level only | Enable compiler warning flags on TinyML targets | +| `TINYML_ENABLE_LTO` | `OFF` | Enable IPO/LTO when supported | -## Documentation +## Testing and release guarantees -Start with [PRODUCTION_API.md](docs/PRODUCTION_API.md), [TESTING.md](TESTING.md), [ROADMAP.md](ROADMAP.md), [DEMO_ROADMAP.md](docs/DEMO_ROADMAP.md) and the material in `docs/wiki/` and `blog/`. +CI builds the zero-dependency core separately from the full library with GCC and Clang. Every configuration is installed into a staging prefix and then consumed by a fresh downstream CMake project through `find_package`, so packaging regressions fail before release. -## Contributing and security +Timing assertions are opt-in because shared CI hardware is unsuitable for performance gates. Benchmark executables are local-only. See [TESTING.md](TESTING.md). -See [CONTRIBUTING.md](CONTRIBUTING.md) before opening a change. Security-sensitive reports should follow [SECURITY.md](SECURITY.md). +Versioned releases are assembled from `cmake --install`, not by manually copying build artifacts. Core and extended archives are published separately, with SHA-256 checksums. + +## API stability + +The compatibility contract is explicit rather than implied by the presence of a header in the repository. See [docs/STABILITY.md](docs/STABILITY.md) for the stable, preview and source-only surfaces. + +Changes to the stable core follow semantic versioning. Preview and source-only modules may change before they graduate into the stable surface. ## Licensing -TinyML is dual licensed. The zero-dependency core is MIT-licensed, including commercial use. The extended model library is covered by the commercial terms described in [LICENSING.md](LICENSING.md). That file is the authoritative map of which headers belong to each side of the license boundary. +The installable core is MIT licensed. The extended model library contains commercially licensed modules and xsimd-backed functionality. Individual file licensing and the distinction between licensing and API stability are documented in [LICENSING.md](LICENSING.md). Commercial terms are in [LICENSE-COMMERCIAL.md](LICENSE-COMMERCIAL.md). + +Security reports should follow [SECURITY.md](SECURITY.md). Contributions should follow [CONTRIBUTING.md](CONTRIBUTING.md). Release history is tracked in [CHANGELOG.md](CHANGELOG.md). diff --git a/TESTING.md b/TESTING.md index 7b8cda1..bd28aaa 100644 --- a/TESTING.md +++ b/TESTING.md @@ -1,95 +1,85 @@ # Testing & CI Policy -## What runs in CI +## Release gates -CI runs on **pull requests to `main`**, **pushes to `main`** and **release tags** (`v*`). -Only fast unit tests execute in CI — the full suite finishes in under 2 minutes. +CI runs on pull requests to `main`, pushes to `main` and version tags. The build matrix contains a dependency-free core configuration plus full GCC and Clang configurations. -``` -ctest --output-on-failure --timeout 120 -``` - -## What does NOT run in CI (and why) +Every matrix entry performs four distinct checks: configure/build, CTest correctness tests, `cmake --install`, and a fresh downstream consumer build using `find_package(TinyML CONFIG REQUIRED COMPONENTS Core)`. This catches packaging/export errors that an in-tree build cannot detect. -### Benchmarks (removed from ctest) +Core-only verification: -| Benchmark | Why skipped | -|---|---| -| `SIMDBenchmark` | Benchmark results on shared CI runners are meaningless — hardware varies per run | -| `AttentionBenchmark` | Same reason — timing-sensitive, needs dedicated hardware | -| `SimpleAttentionBenchmark` | Same reason | -| `ReinforcementLearningBenchmark` | Segfaults on Linux CI runners (works locally on macOS) | -| `GenerativeModelsBenchmark` | Benchmark — not a correctness test | +```bash +cmake -S . -B build-core -G Ninja \ + -DCMAKE_BUILD_TYPE=Release \ + -DTINYML_BUILD_EXTENDED=OFF +cmake --build build-core --parallel +ctest --test-dir build-core --output-on-failure +``` -**Benchmarks are still built** so compilation is verified. They just aren't registered -with `add_test()` so `ctest` won't run them. Run them locally: +Full verification: ```bash -cd build -./bin/SIMDBenchmark -./bin/AttentionBenchmark -./bin/SimpleAttentionBenchmark -./bin/ReinforcementLearningBenchmark -./bin/GenerativeModelsBenchmark +cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release +cmake --build build --parallel +ctest --test-dir build --output-on-failure --timeout 120 ``` -### Timing assertions (skipped unless asked for) +Installed-consumer verification: -Five tests assert wall-clock thresholds: +```bash +cmake --install build --prefix "$PWD/install" +cmake -S tests/install_consumer -B consumer-build -G Ninja \ + -DCMAKE_PREFIX_PATH="$PWD/install" +cmake --build consumer-build +ctest --test-dir build --output-on-failure +./consumer-build/tinyml_install_consumer +``` -| Test | Asserts | -|---|---| -| `Phase1SIMDTest.PerformanceTargetsValidation` | per-op time against a target in µs | -| `Phase6ProductionTest.ProductionPerformanceBenchmarks` | audio, time series, vision and text latency | -| `Phase6ProductionTest.ProductionDeploymentScenarios` | speech, IoT, edge and device-text latency | -| `Phase4SimpleTest.StreamingSimulation` | jitter, as max/min per-token time | -| `Phase7AdvancedAttentionTest.PerformanceBenchmarks` | attention latency and throughput | +## Performance assertions -On a shared runner these measure the runner. The correctness assertions in the -same tests always run; the timing ones are opt-in: +Wall-clock thresholds are not release correctness gates on shared CI hardware. The tests still print measured timings, while assertions are enabled locally with: ```bash -TINYML_PERF_ASSERTS=1 ctest --output-on-failure +TINYML_PERF_ASSERTS=1 ctest --test-dir build --output-on-failure ``` -The measured numbers print either way. The current targets do not hold on a -GitHub runner, and `Phase7AdvancedAttentionTest` throughput sits near its 25 -tok/s line even on a loaded laptop, so treat them as goals rather than facts. - -### Disabled tests (registered but skipped) - -| Test | Why disabled | -|---|---| -| `Phase8PhysicsInformedTest` | Takes **16+ minutes** on CI runners — too slow for free GitHub Actions | -| `Phase8PhysicsComprehensiveTest` | Non-deterministic convergence — `loss_ratio` swings from 0.04 to 21+ across runs | -| `Phase12ReinforcementTest` | Segfaults on Linux CI runners | +## Benchmarks -Run these locally if you need them: +Benchmarks are excluded from normal builds and from CTest. Enable the supported benchmark targets explicitly: ```bash -cd build -ctest -R Phase8PhysicsInformedTest --force-new-ctest-process -ctest -R Phase12ReinforcementTest --force-new-ctest-process +cmake -S . -B build-bench \ + -DCMAKE_BUILD_TYPE=Release \ + -DTINYML_BUILD_TESTS=OFF \ + -DTINYML_BUILD_BENCHMARKS=ON +cmake --build build-bench --parallel ``` -## Releases +Benchmark numbers should be recorded with compiler, flags, CPU, operating system, power mode and dataset/input shape. Results from an unspecified shared runner are not suitable for performance claims. -Pushing a version tag (e.g. `git tag v1.0.0 && git push --tags`) triggers: +## Registered disabled tests -1. Full build + unit tests -2. Release binary packaging (`libTinyML.a` + executables) -3. Upload to GitHub Releases +The following tests remain compiled but disabled in the default CTest run: -## Running the full suite locally +| Test | Status | +| --- | --- | +| `Phase8PhysicsInformedTest` | Long-running experiment; unsuitable for the fast release gate | +| `Phase8PhysicsComprehensiveTest` | Non-deterministic convergence; requires a deterministic acceptance criterion | +| `Phase12ReinforcementTest` | Known Linux failure; module remains preview until fixed | -```bash -mkdir -p build && cd build -cmake .. -DCMAKE_BUILD_TYPE=Release -cmake --build . -j$(nproc) +A module with a known-failure test cannot graduate to the stable API surface. -# Fast unit tests only (what CI runs) -ctest --output-on-failure --timeout 120 +Run an individual disabled test locally with CTest's disabled-test override, for example: -# Everything including disabled tests -ctest --output-on-failure --timeout 1200 --force-new-ctest-process +```bash +ctest --test-dir build -R Phase12ReinforcementTest \ + --output-on-failure --force-new-ctest-process ``` + +## Sanitizers and dedicated performance hardware + +Sanitizer runs and dedicated-hardware performance baselines are appropriate release-hardening checks, but they are intentionally separate from the current fast CI matrix. They should be added only when the corresponding test corpus is deterministic enough that a failure represents a code defect rather than runner variance. + +## Releases + +A `v*` tag is packaged from the CMake install graph. The release job produces separate core and extended archives and SHA-256 checksums. The core archive contains no xsimd dependency. The extended package records xsimd as a CMake dependency rather than vendoring it into the TinyML SDK. diff --git a/cmake/TinyMLConfig.cmake.in b/cmake/TinyMLConfig.cmake.in new file mode 100644 index 0000000..b4f317b --- /dev/null +++ b/cmake/TinyMLConfig.cmake.in @@ -0,0 +1,26 @@ +@PACKAGE_INIT@ + +include(CMakeFindDependencyMacro) + +set(TinyML_EXTENDED_AVAILABLE "@TINYML_BUILD_EXTENDED@") + +include("${CMAKE_CURRENT_LIST_DIR}/TinyMLCoreTargets.cmake") +set(TinyML_Core_FOUND TRUE) +set(TinyML_Extended_FOUND FALSE) + +if(NOT TinyML_FIND_COMPONENTS) + set(TinyML_FIND_COMPONENTS Core) + if(TinyML_EXTENDED_AVAILABLE) + list(APPEND TinyML_FIND_COMPONENTS Extended) + endif() +endif() + +if("Extended" IN_LIST TinyML_FIND_COMPONENTS) + if(TinyML_EXTENDED_AVAILABLE) + find_dependency(xsimd 12.1 CONFIG) + include("${CMAKE_CURRENT_LIST_DIR}/TinyMLExtendedTargets.cmake") + set(TinyML_Extended_FOUND TRUE) + endif() +endif() + +check_required_components(TinyML) diff --git a/cmake/TinyMLVersion.hpp.in b/cmake/TinyMLVersion.hpp.in new file mode 100644 index 0000000..0ca42d1 --- /dev/null +++ b/cmake/TinyMLVersion.hpp.in @@ -0,0 +1,6 @@ +#pragma once + +#define TINYML_VERSION_MAJOR @PROJECT_VERSION_MAJOR@ +#define TINYML_VERSION_MINOR @PROJECT_VERSION_MINOR@ +#define TINYML_VERSION_PATCH @PROJECT_VERSION_PATCH@ +#define TINYML_VERSION_STRING "@PROJECT_VERSION@" diff --git a/docs/STABILITY.md b/docs/STABILITY.md new file mode 100644 index 0000000..487383e --- /dev/null +++ b/docs/STABILITY.md @@ -0,0 +1,39 @@ +# API stability + +The repository intentionally separates licensing, buildability and API stability. A file can be MIT licensed without being part of the supported core API, and a source file can remain in the repository without being installed into a release SDK. + +## Stable + +The stable surface is the `TinyML::Core` CMake target and the headers installed by a core-only package: + +| Header | Contract | +| --- | --- | +| `` | Stable umbrella header | +| `` | Stable version macros | +| `` | Neuron and layer primitives | +| `` | Feed-forward network composition and training | +| `` | Model wrapper, persistence and inference | + +Stable APIs follow semantic versioning. Source-compatible additions may land in minor releases. Breaking source or behavior changes require a new major version unless they fix undefined behavior, memory safety, data corruption or another correctness defect that cannot reasonably be preserved. + +## Preview + +The `TinyML::Extended` target is installable and packaged, but its module-level APIs are currently preview surfaces: + +`AdvancedAttention.h`, `DynamicNeuralNetwork.h`, `GenerativeModels.h`, `GraphNeuralNetwork.h`, `LightweightAttention.h`, `PhysicsInformedNN.h`, `ProductionAPI.h`, `QuantizedOperations.h`, `RealTimeTransformer.h`, `ReinforcementLearning.h`, `TimeSeriesForecasting.h`, `TinyMLAPI.h` and `XSIMDOperations.h`. + +Preview modules are compiled in the normal extended build and their supported tests run in CI. Their public APIs may still change in minor releases while they are hardened. Graduation to stable requires deterministic correctness tests, no disabled known-failure test, documented serialization behavior where applicable, and inclusion in the installed-consumer test matrix. + +## Source-only / experimental + +The following legacy or incomplete modules remain available to repository developers but are not installed as part of the supported SDK: + +`AdvancedOptimizations.h`, `BayesianNeuralNetwork.h`, `NEONOperations.h`, `Perceptron.h`, `SIMDOperations.h`, `VectorOperations.h`, `VectorStatistics.h`, `common.h` and `logger.h`. + +These files are not covered by API compatibility guarantees. Keeping them source-only prevents a partially implemented symbol or historical helper API from accidentally becoming permanent public surface area. + +## Test status is part of stability + +A module with a disabled test is not considered stable. Long-running experiments may remain disabled in CI for cost reasons, but a module cannot graduate until it also has a deterministic fast correctness suite appropriate for release gating. + +Benchmarks are evidence, not tests. Wall-clock thresholds are never used as correctness gates on shared CI hardware. diff --git a/include/Model.h b/include/Model.h index 921ad27..f10818b 100644 --- a/include/Model.h +++ b/include/Model.h @@ -1,186 +1,93 @@ -//**************************************************************************** -/* Copyright (C) Abhishek Shivakumar - All Rights Reserved - * Unauthorized copying of this file, via any medium is strictly prohibited - * Proprietary and confidential - * Written by Abhishek Shivakumar , 22/04/2022 -*****************************************************************************/ +// SPDX-License-Identifier: MIT +// Copyright (c) Abhishek Shivakumar #ifndef MODEL_H #define MODEL_H #include "Network.h" -#include + #include +#include +#include +#include +#include +#include +#include #include -#include namespace ML { - /** - * @brief The Model class encapsulates the Network class, providing methods to train, - * update, and query the network. It also includes utilities for saving and loading weights. - * - * Usage: - * - * 1. Initialize the Model with a topology (vector) defining the number of neurons in each layer. - * 2. Use the `feedForward` method to pass inputs through the network. - * 3. Use the `getResult` method to obtain the network's output. - * 4. Use the `backPropagate` method to train the network with target values. - * 5. Save and load weights using `saveWeightsToFile` and `loadWeightsFromFile`. - * - * Example: - * - * ``` - * std::vector topology = {3, 2, 1}; // 3 neurons in the input layer, 2 in the hidden, 1 in the output - * ML::Model model(topology); - * - * std::vector inputVals = {1.0, 0.5, -1.5}; - * model.FeedForward(inputVals); - * - * std::vector results = model.GetResult(); - * model.BackPropagate({0.8}); // Assuming the target value is 0.8 for the output - * - * model.SaveWeightsToFile("weights.txt"); - * model.LoadWeightsFromFile("weights.txt"); - * ``` - */ class Model { - private: - Network thisNetwork; // The neural network associated with this model - std::vector topology; // The topology of the network (number of neurons per layer) - std::vector weights; // Cache for the weights of the network - public: - /** - * @brief Constructor that initializes the model with the given topology. - * - * @param tp The topology defining the number of neurons in each layer. - */ - Model(const std::vector& tp) - : thisNetwork(tp), topology(tp) + explicit Model (const std::vector& topology) + : thisNetwork (topology), topology (topology) { } - /** - * @brief Get the topology of the network. - * - * @return std::vector The topology of the network. - */ - std::vector getTopology() const + const std::vector& getTopology() const noexcept { return topology; } - /** - * @brief Get the weights of the network. - * - * @return std::vector The weights of the network. - */ std::vector getWeights() const { - return weights; + return thisNetwork.getWeights(); } - /** - * @brief Get the current activations (output values) of all neurons in the network. - * - * @return std::vector> A vector of vectors containing activations for each layer. - */ std::vector> getActivations() const { std::vector> activations; - for (const auto& layer : thisNetwork.layers) + activations.reserve (thisNetwork.GetLayers().size()); + + for (const auto& layer : thisNetwork.GetLayers()) { std::vector layerActivations; + layerActivations.reserve (layer.size()); + for (const auto& neuron : layer) { - layerActivations.push_back(neuron->getOutputVal()); + layerActivations.push_back (neuron->getOutputVal()); } - activations.push_back(layerActivations); + + activations.push_back (std::move (layerActivations)); } + return activations; } - /** - * @brief Get the recent average error (loss) of the network. - * - * @return double The recent average error. - */ - double getRecentAverageError() const + double getRecentAverageError() const noexcept { return thisNetwork.getRecentAverageError(); } - /** - * @brief Set a new topology for the model. - * - * This function allows you to change the network structure after initialization. - * However, if you change the topology, you'll need to reinitialize the network with the new topology. - * - * @param tp A vector representing the new topology. - */ - void setTopology(const std::vector& tp) + void setTopology (const std::vector& newTopology) { - topology = tp; - thisNetwork = Network(tp); // Reinitialize the network with the new topology + Network replacement (newTopology); + thisNetwork = std::move (replacement); + topology = newTopology; } - /** - * @brief Perform backpropagation on the network to update the weights based on target values. - * - * @param targetVals The expected output values used for training. - */ - void backPropagate(const std::vector& targetVals) + void backPropagate (const std::vector& targetVals) { - thisNetwork.backPropagate(targetVals); + thisNetwork.backPropagate (targetVals); } - /** - * @brief Get a pointer to the internal network. - * - * This allows you to directly access the Network class for advanced operations if needed. - * - * @return Network* A pointer to the internal Network object. - */ - Network* getNetwork() + Network* getNetwork() noexcept { return &thisNetwork; } - /** - * @brief Get the current weights of the network. - * - * This function retrieves the current weights of the network, which are cached for later use. - * - * @return std::vector A vector containing the current weights of the network. - */ - std::vector getWeights() + const Network* getNetwork() const noexcept { - weights = thisNetwork.getWeights(); // Update the cached weights - return weights; + return &thisNetwork; } - /** - * @brief Perform forward propagation through the network with the given inputs. - * - * This method processes the input values through the network to calculate the output. - * - * @param inputs A vector of input values corresponding to the input layer of the network. - */ - void feedForward (std::vector inputs) + void feedForward (const std::vector& inputs) { - assert (inputs.size() == topology.front()); // Ensure the input size matches the network input layer thisNetwork.feedForward (inputs); } - /** - * @brief Get the results (output values) from the network. - * - * After calling `FeedForward`, use this method to retrieve the calculated outputs. - * - * @return std::vector A vector containing the output values from the network. - */ std::vector getResult() const { std::vector resultVals; @@ -188,112 +95,81 @@ namespace ML return resultVals; } - /** - * @brief Set new weights for the network. - * - * This function allows you to manually set the weights of the network. - * - * @param newWeights A vector containing the new weights to be applied to the network. - */ - void setWeights(const std::vector& newWeights) + void setWeights (const std::vector& newWeights) { - thisNetwork.putWeights(newWeights); + thisNetwork.putWeights (newWeights); } - /** - * @brief Display the topology of the network. - * - * This function prints the number of neurons in each layer of the network to the console. - */ void displayTopology() const { std::cout << "Network Topology:\n"; - for (unsigned layerSize : topology) + for (const unsigned layerSize : topology) { std::cout << layerSize << " neurons\n"; } } - /** - * @brief Update the weights of the network. - * - * This function applies updates to the weights based on the training (backpropagation) process. - */ void updateWeights() { thisNetwork.updateWeights(); } - /** - * @brief Display the weights of the network. - * - * This function prints the current weights of the network to the console. - */ void displayWeights() const { std::cout << "Network Weights:\n"; - const std::vector& currentWeights = const_cast(this)->getWeights(); - for (double weight : currentWeights) + for (const double weight : getWeights()) { - std::cout << weight << " "; + std::cout << weight << ' '; } - std::cout << "\n"; + std::cout << '\n'; } - /** - * @brief Save the network weights to a file. - * - * This function writes the current weights of the network to a file for later retrieval. - * - * @param filename The name of the file where weights will be saved. - */ - void saveWeightsToFile(const std::string& filename) const + void saveWeightsToFile (const std::string& filename) const { - std::ofstream outFile(filename); + std::ofstream outFile (filename, std::ios::trunc); if (!outFile) { - std::cerr << "Error: Unable to open file for saving weights\n"; - return; + throw std::runtime_error ("Unable to open TinyML weight file for writing: " + filename); + } + + outFile << std::setprecision (std::numeric_limits::max_digits10); + for (const double weight : getWeights()) + { + outFile << weight << '\n'; } - const std::vector& currentWeights = const_cast(this)->getWeights(); - for (double weight : currentWeights) + if (!outFile) { - outFile << weight << "\n"; + throw std::runtime_error ("Failed while writing TinyML weight file: " + filename); } - outFile.close(); } - /** - * @brief Load the network weights from a file. - * - * This function reads weights from a file and applies them to the network. - * - * @param filename The name of the file from which to load weights. - */ void loadWeightsFromFile (const std::string& filename) { - std::ifstream inFile(filename); + std::ifstream inFile (filename); if (!inFile) { - std::cerr << "Error: Unable to open file for loading weights\n"; - return; + throw std::runtime_error ("Unable to open TinyML weight file for reading: " + filename); } std::vector newWeights; - double weight; + double weight = 0.0; while (inFile >> weight) { - newWeights.push_back(weight); + newWeights.push_back (weight); } - if (!newWeights.empty()) + if (!inFile.eof()) { - setWeights(newWeights); + throw std::runtime_error ("TinyML weight file contains invalid data: " + filename); } - inFile.close(); + setWeights (newWeights); } + + private: + Network thisNetwork; + std::vector topology; }; } diff --git a/include/NN.h b/include/NN.h index 58c3c09..d1572d7 100644 --- a/include/NN.h +++ b/include/NN.h @@ -1,16 +1,11 @@ -//**************************************************************************** -/* Copyright (C) Abhishek Shivakumar - All Rights Reserved - * Unauthorized copying of this file, via any medium is strictly prohibited - * Proprietary and confidential - * Written by Abhishek Shivakumar , 22/04/2022 -*****************************************************************************/ +// SPDX-License-Identifier: MIT +// Copyright (c) Abhishek Shivakumar #ifndef NN_H #define NN_H -#include #include -#include +#include namespace ML { @@ -20,45 +15,51 @@ namespace ML class Neuron { public: - Neuron(unsigned numOutputs, unsigned neuronIndex); + Neuron (unsigned numOutputs, unsigned neuronIndex); - void calcHiddenGradients(const Layer& nextLayer); - void calcOutputGradients(double targetVal); - void feedForward(Layer& prevLayer); - void updateInputWeights(Layer& prevLayer); + void calcHiddenGradients (const Layer& nextLayer); + void calcOutputGradients (double targetVal); + void feedForward (Layer& prevLayer); + void updateInputWeights (Layer& prevLayer); - static double transferFunction(double x); - static double transferFunctionDerivative(double x); + static double transferFunction (double x); + static double transferFunctionDerivative (double x); double getOutputVal() const; - void setOutputVal(double value); + void setOutputVal (double value); int getIndex() const; private: - double randomWeight(); - double sumDOW(const Layer& nextLayer) const; - - double outputVal; - double gradient; - double error; - double recentAverageError; - unsigned index; - struct connection { - double weight; - double deltaweight; + double weight = 0.0; + double deltaweight = 0.0; }; + double randomWeight(); + double sumDOW (const Layer& nextLayer) const; + + double outputVal = 0.0; + double gradient = 0.0; + double error = 0.0; + double recentAverageError = 0.0; + unsigned index = 0; std::vector> outputWeights; - static constexpr double eta = 0.15; // learning rate - static constexpr double alpha = 0.5; // momentum + static constexpr double eta = 0.15; + static constexpr double alpha = 0.5; - public: - const std::vector>& getOutputWeights() const { return outputWeights; } - std::vector>& getOutputWeights() { return outputWeights; } - }; + public: + const std::vector>& getOutputWeights() const noexcept + { + return outputWeights; + } + + std::vector>& getOutputWeights() noexcept + { + return outputWeights; + } + }; } #endif // NN_H diff --git a/include/Network.h b/include/Network.h index a7e0847..415a104 100644 --- a/include/Network.h +++ b/include/Network.h @@ -1,40 +1,53 @@ -//**************************************************************************** -/* Copyright (C) Abhishek Shivakumar - All Rights Reserved - * Unauthorized copying of this file, via any medium is strictly prohibited - * Proprietary and confidential - * Written by Abhishek Shivakumar , 22/04/2022 -*****************************************************************************/ +// SPDX-License-Identifier: MIT +// Copyright (c) Abhishek Shivakumar #ifndef NETWORK_H #define NETWORK_H #include "NN.h" + #include namespace ML { - class Network - { - public: - Network (const std::vector & topology); - void backPropagate (const std::vector & targetVals); - void feedForward (std::vector inputVals); //TODO: make const - void getResults (std::vector & resultVals) const; - void putWeights (const std::vector& weights); - void updateWeights(); - void normalizeWeights (int connection_index); - std::vector& GetLayers() { return layers; } - double getRecentAverageError (void) const { return recentAverageError; } - - std::vector getWeights() const; - - std::vector layers; - private: - double gradient = 0.0; - double error = 0.0; - double recentAverageError = 0.0; - double recentAverageSmoothingFactor = 0.0; - }; + class Network + { + public: + explicit Network (const std::vector& topology); + + void backPropagate (const std::vector& targetVals); + void feedForward (const std::vector& inputVals); + void getResults (std::vector& resultVals) const; + void putWeights (const std::vector& weights); + void updateWeights(); + void normalizeWeights (int connectionIndex); + + std::vector& GetLayers() noexcept + { + return layers; + } + + const std::vector& GetLayers() const noexcept + { + return layers; + } + + double getRecentAverageError() const noexcept + { + return recentAverageError; + } + + std::vector getWeights() const; + + // Kept public for source compatibility with the original API. + std::vector layers; + + private: + double gradient = 0.0; + double error = 0.0; + double recentAverageError = 0.0; + double recentAverageSmoothingFactor = 0.0; + }; } -#endif +#endif // NETWORK_H diff --git a/include/tinyml/core.hpp b/include/tinyml/core.hpp new file mode 100644 index 0000000..6243c31 --- /dev/null +++ b/include/tinyml/core.hpp @@ -0,0 +1,6 @@ +#pragma once + +#include +#include +#include +#include diff --git a/include/tinyml/tinyml.hpp b/include/tinyml/tinyml.hpp new file mode 100644 index 0000000..8811604 --- /dev/null +++ b/include/tinyml/tinyml.hpp @@ -0,0 +1,17 @@ +#pragma once + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include diff --git a/src/NN.cpp b/src/NN.cpp index ce19dd8..5ac4589 100644 --- a/src/NN.cpp +++ b/src/NN.cpp @@ -1,44 +1,56 @@ -//**************************************************************************** -/* Copyright (C) Abhishek Shivakumar - All Rights Reserved - * Unauthorized copying of this file, via any medium is strictly prohibited - * Proprietary and confidential - * Written by Abhishek Shivakumar , 22/04/2022 -*****************************************************************************/ +// SPDX-License-Identifier: MIT +// Copyright (c) Abhishek Shivakumar #include "NN.h" +#include +#include +#include +#include +#include + namespace ML { - Neuron::Neuron(unsigned numOutputs, unsigned neuronIndex) - : error(0.0f), gradient(0.0f), outputVal(0.0f), recentAverageError(0.0f), index(neuronIndex) + Neuron::Neuron (unsigned numOutputs, unsigned neuronIndex) + : index (neuronIndex) { - outputWeights.reserve(numOutputs); - for (unsigned i = 0; i < numOutputs; ++i) { + outputWeights.reserve (numOutputs); + + for (unsigned i = 0; i < numOutputs; ++i) + { auto connectionPtr = std::make_unique(); - connectionPtr->weight = static_cast(rand()) / RAND_MAX; - outputWeights.push_back(std::move(connectionPtr)); + connectionPtr->weight = randomWeight(); + outputWeights.push_back (std::move (connectionPtr)); } } - void Neuron::calcHiddenGradients(const Layer& nextLayer) + void Neuron::calcHiddenGradients (const Layer& nextLayer) { - double dow = sumDOW(nextLayer); - gradient = dow * Neuron::transferFunctionDerivative(outputVal); + gradient = sumDOW (nextLayer) * transferFunctionDerivative (outputVal); } - void Neuron::calcOutputGradients(double targetVal) + void Neuron::calcOutputGradients (double targetVal) { - double delta = targetVal - outputVal; - gradient = delta * Neuron::transferFunctionDerivative(outputVal); + const double delta = targetVal - outputVal; + gradient = delta * transferFunctionDerivative (outputVal); } - void Neuron::feedForward(Layer& prevLayer) + void Neuron::feedForward (Layer& prevLayer) { double sum = 0.0; - for (const auto& neuron : prevLayer) { - sum += neuron->getOutputVal() * neuron->outputWeights[index]->weight; + + for (const auto& neuron : prevLayer) + { + const auto& weights = neuron->getOutputWeights(); + if (index >= weights.size()) + { + throw std::out_of_range ("TinyML connection index is outside the previous layer"); + } + + sum += neuron->getOutputVal() * weights[index]->weight; } - outputVal = Neuron::transferFunction(sum); + + outputVal = transferFunction (sum); } double Neuron::getOutputVal() const @@ -46,47 +58,67 @@ namespace ML return outputVal; } - void Neuron::updateInputWeights(Layer& prevLayer) + void Neuron::updateInputWeights (Layer& prevLayer) { - for (auto& neuron : prevLayer) { - double oldDeltaWeight = neuron->outputWeights[index]->deltaweight; - double newDeltaWeight = eta * neuron->getOutputVal() * gradient + alpha * oldDeltaWeight; - neuron->outputWeights[index]->deltaweight = newDeltaWeight; - neuron->outputWeights[index]->weight += newDeltaWeight; + for (auto& neuron : prevLayer) + { + auto& weights = neuron->getOutputWeights(); + if (index >= weights.size()) + { + throw std::out_of_range ("TinyML connection index is outside the previous layer"); + } + + auto& connection = *weights[index]; + const double newDeltaWeight = eta * neuron->getOutputVal() * gradient + + alpha * connection.deltaweight; + + connection.deltaweight = newDeltaWeight; + connection.weight += newDeltaWeight; } } double Neuron::randomWeight() { - return static_cast(rand()) / RAND_MAX; + static thread_local std::mt19937 generator (std::random_device{}()); + static thread_local std::uniform_real_distribution distribution (0.0, 1.0); + return distribution (generator); } - double Neuron::sumDOW(const Layer& nextLayer) const + double Neuron::sumDOW (const Layer& nextLayer) const { + if (nextLayer.size() <= 1) + { + return 0.0; + } + + const std::size_t connectionCount = std::min (outputWeights.size(), nextLayer.size() - 1); double sum = 0.0; - for (unsigned i = 0; i < nextLayer.size() - 1; ++i) { + + for (std::size_t i = 0; i < connectionCount; ++i) + { sum += outputWeights[i]->weight * nextLayer[i]->gradient; } + return sum; } - double Neuron::transferFunctionDerivative(double x) + double Neuron::transferFunctionDerivative (double x) { return 1.0 - x * x; } - double Neuron::transferFunction(double x) + double Neuron::transferFunction (double x) { - return tanh(x); + return std::tanh (x); } - void Neuron::setOutputVal(double value) + void Neuron::setOutputVal (double value) { outputVal = value; } int Neuron::getIndex() const { - return index; + return static_cast (index); } } diff --git a/src/Network.cpp b/src/Network.cpp index 6c98fd2..2c2435d 100644 --- a/src/Network.cpp +++ b/src/Network.cpp @@ -1,66 +1,106 @@ -//**************************************************************************** -/* Copyright (C) Abhishek Shivakumar - All Rights Reserved - * Unauthorized copying of this file, via any medium is strictly prohibited - * Proprietary and confidential - * Written by Abhishek Shivakumar , 22/04/2022 -*****************************************************************************/ - -#include // For time() -#include // For assert() +// SPDX-License-Identifier: MIT +// Copyright (c) Abhishek Shivakumar + #include "Network.h" +#include +#include +#include + namespace ML { Network::Network (const std::vector& topology) { - srand (static_cast(time (NULL))); - unsigned numLayers = static_cast(topology.size()); + if (topology.size() < 2) + { + throw std::invalid_argument ("TinyML network topology requires at least input and output layers"); + } + + for (const unsigned width : topology) + { + if (width == 0) + { + throw std::invalid_argument ("TinyML network layers must contain at least one neuron"); + } + } + + const auto numLayers = static_cast (topology.size()); for (unsigned layerNum = 0; layerNum < numLayers; ++layerNum) { layers.emplace_back(); - unsigned numOutputs = (layerNum == topology.size() - 1) ? 0 : topology[layerNum + 1]; + const unsigned numOutputs = layerNum + 1 == numLayers ? 0 : topology[layerNum + 1]; - // Create neurons for this layer, including a bias neuron for (unsigned neuronNum = 0; neuronNum <= topology[layerNum]; ++neuronNum) { - layers.back().push_back(std::make_unique(numOutputs, neuronNum)); + layers.back().push_back (std::make_unique (numOutputs, neuronNum)); } - // Set the bias neuron's output to 0.0 - layers.back().back()->setOutputVal(0.0); + // Every non-output layer uses its final neuron as a conventional bias input. + layers.back().back()->setOutputVal (1.0); } } - void Network::normalizeWeights (int connection_index) + void Network::normalizeWeights (int connectionIndex) { - double sum_weights_squared = 0.0; + if (connectionIndex < 0) + { + throw std::invalid_argument ("TinyML connection index cannot be negative"); + } + + const auto index = static_cast (connectionIndex); + double sum = 0.0; + std::size_t count = 0; for (const Layer& layer : layers) { for (const auto& neuron : layer) { - sum_weights_squared += neuron->getOutputWeights()[connection_index]->weight; + const auto& weights = neuron->getOutputWeights(); + if (index < weights.size()) + { + sum += weights[index]->weight; + ++count; + } } } - double average = sum_weights_squared / 101.0; - sum_weights_squared = 0.0; + if (count == 0) + { + throw std::out_of_range ("TinyML connection index does not exist in this network"); + } + + const double mean = sum / static_cast (count); + double squaredNorm = 0.0; for (const Layer& layer : layers) { for (const auto& neuron : layer) { - neuron->getOutputWeights()[connection_index]->weight -= average; - sum_weights_squared += std::pow(neuron->getOutputWeights()[connection_index]->weight, 2); + auto& weights = neuron->getOutputWeights(); + if (index < weights.size()) + { + weights[index]->weight -= mean; + squaredNorm += weights[index]->weight * weights[index]->weight; + } } } + if (squaredNorm <= std::numeric_limits::epsilon()) + { + return; + } + + const double norm = std::sqrt (squaredNorm); for (const Layer& layer : layers) { for (const auto& neuron : layer) { - neuron->getOutputWeights()[connection_index]->weight /= std::sqrt(sum_weights_squared); + auto& weights = neuron->getOutputWeights(); + if (index < weights.size()) + { + weights[index]->weight /= norm; + } } } } @@ -69,81 +109,86 @@ namespace ML { for (std::size_t layerNum = 1; layerNum < layers.size(); ++layerNum) { + Layer& layer = layers[layerNum]; Layer& prevLayer = layers[layerNum - 1]; - for (auto& neuron : prevLayer) + for (std::size_t neuronNum = 0; neuronNum + 1 < layer.size(); ++neuronNum) { - neuron->updateInputWeights(prevLayer); + layer[neuronNum]->updateInputWeights (prevLayer); } } } void Network::backPropagate (const std::vector& targetVals) { - // Calculate overall net error (RMS of output neuron errors) Layer& outputLayer = layers.back(); - error = 0.0; + const std::size_t outputCount = outputLayer.size() - 1; - for (std::size_t n = 0; n < outputLayer.size() - 1; ++n) + if (targetVals.size() != outputCount) { - double delta = targetVals[n] - outputLayer[n]->getOutputVal(); - error += delta * delta; + throw std::invalid_argument ("TinyML target vector size does not match the output layer"); } - error /= outputLayer.size() - 1; // Average error squared - error = std::sqrt(error); // RMS + error = 0.0; + for (std::size_t n = 0; n < outputCount; ++n) + { + const double delta = targetVals[n] - outputLayer[n]->getOutputVal(); + error += delta * delta; + } - // Implement a recent average measurement - recentAverageError = (recentAverageError * recentAverageSmoothingFactor + error) / (recentAverageSmoothingFactor + 1.0); + error = std::sqrt (error / static_cast (outputCount)); + recentAverageError = (recentAverageError * recentAverageSmoothingFactor + error) + / (recentAverageSmoothingFactor + 1.0); - // Calculate output layer gradients - for (std::size_t n = 0; n < outputLayer.size() - 1; ++n) + for (std::size_t n = 0; n < outputCount; ++n) { - outputLayer[n]->calcOutputGradients(targetVals[n]); + outputLayer[n]->calcOutputGradients (targetVals[n]); } - // Calculate hidden layer gradients for (std::size_t layerNum = layers.size() - 2; layerNum > 0; --layerNum) { Layer& hiddenLayer = layers[layerNum]; Layer& nextLayer = layers[layerNum + 1]; - for (auto& neuron : hiddenLayer) + for (std::size_t n = 0; n + 1 < hiddenLayer.size(); ++n) { - neuron->calcHiddenGradients(nextLayer); + hiddenLayer[n]->calcHiddenGradients (nextLayer); } } - // Update connection weights for all layers from output to first hidden layer for (std::size_t layerNum = layers.size() - 1; layerNum > 0; --layerNum) { Layer& layer = layers[layerNum]; Layer& prevLayer = layers[layerNum - 1]; - for (std::size_t n = 0; n < layer.size() - 1; ++n) + for (std::size_t n = 0; n + 1 < layer.size(); ++n) { - layer[n]->updateInputWeights(prevLayer); + layer[n]->updateInputWeights (prevLayer); } } } - void Network::feedForward (std::vector inputVals) + void Network::feedForward (const std::vector& inputVals) { - assert(inputVals.size() == layers[0].size() - 1); + const std::size_t expectedInputs = layers.front().size() - 1; + if (inputVals.size() != expectedInputs) + { + throw std::invalid_argument ("TinyML input vector size does not match the input layer"); + } - // Assign input values to input neurons for (std::size_t i = 0; i < inputVals.size(); ++i) { - layers[0][i]->setOutputVal(inputVals[i]); + layers.front()[i]->setOutputVal (inputVals[i]); } - // Forward propagate for (std::size_t layerNum = 1; layerNum < layers.size(); ++layerNum) { Layer& prevLayer = layers[layerNum - 1]; - for (std::size_t n = 0; n < layers[layerNum].size() - 1; ++n) + Layer& layer = layers[layerNum]; + + for (std::size_t n = 0; n + 1 < layer.size(); ++n) { - layers[layerNum][n]->feedForward(prevLayer); + layer[n]->feedForward (prevLayer); } } } @@ -151,12 +196,12 @@ namespace ML void Network::getResults (std::vector& resultVals) const { resultVals.clear(); - for (const auto& neuron : layers.back()) + const Layer& outputLayer = layers.back(); + resultVals.reserve (outputLayer.size() - 1); + + for (std::size_t n = 0; n + 1 < outputLayer.size(); ++n) { - if (&neuron != &layers.back().back()) // Ignore the bias neuron - { - resultVals.push_back(neuron->getOutputVal()); - } + resultVals.push_back (outputLayer[n]->getOutputVal()); } } @@ -168,9 +213,9 @@ namespace ML { for (const auto& neuron : layer) { - for (const auto& weight : neuron->getOutputWeights()) + for (const auto& connection : neuron->getOutputWeights()) { - weights.push_back(weight->weight); + weights.push_back (connection->weight); } } } @@ -180,15 +225,28 @@ namespace ML void Network::putWeights (const std::vector& weights) { - std::size_t cWeight = 0; + std::size_t expectedCount = 0; + for (const Layer& layer : layers) + { + for (const auto& neuron : layer) + { + expectedCount += neuron->getOutputWeights().size(); + } + } + + if (weights.size() != expectedCount) + { + throw std::invalid_argument ("TinyML weight vector size does not match the network topology"); + } + std::size_t currentWeight = 0; for (Layer& layer : layers) { for (auto& neuron : layer) { - for (auto& weight : neuron->getOutputWeights()) + for (auto& connection : neuron->getOutputWeights()) { - weight->weight = weights[cWeight++]; + connection->weight = weights[currentWeight++]; } } } diff --git a/tests/core_smoke.cpp b/tests/core_smoke.cpp new file mode 100644 index 0000000..7911050 --- /dev/null +++ b/tests/core_smoke.cpp @@ -0,0 +1,53 @@ +#include + +#include +#include +#include +#include + +int main() +{ + ML::Model model ({ 2, 3, 1 }); + model.feedForward ({ 0.25, -0.5 }); + + const auto result = model.getResult(); + if (result.size() != 1 || !std::isfinite (result.front())) + { + return EXIT_FAILURE; + } + + const auto weights = model.getWeights(); + if (weights.empty()) + { + return EXIT_FAILURE; + } + + model.setWeights (weights); + + bool rejectedBadInput = false; + try + { + model.feedForward ({ 1.0 }); + } + catch (const std::invalid_argument&) + { + rejectedBadInput = true; + } + + if (!rejectedBadInput) + { + return EXIT_FAILURE; + } + + bool rejectedBadWeights = false; + try + { + model.setWeights ({ 1.0 }); + } + catch (const std::invalid_argument&) + { + rejectedBadWeights = true; + } + + return rejectedBadWeights ? EXIT_SUCCESS : EXIT_FAILURE; +} diff --git a/tests/install_consumer/CMakeLists.txt b/tests/install_consumer/CMakeLists.txt new file mode 100644 index 0000000..4933de4 --- /dev/null +++ b/tests/install_consumer/CMakeLists.txt @@ -0,0 +1,9 @@ +cmake_minimum_required(VERSION 3.20) + +project(TinyMLInstallConsumer LANGUAGES CXX) + +find_package(TinyML 1.0 CONFIG REQUIRED COMPONENTS Core) + +add_executable(tinyml_install_consumer main.cpp) +target_link_libraries(tinyml_install_consumer PRIVATE TinyML::Core) +target_compile_features(tinyml_install_consumer PRIVATE cxx_std_17) diff --git a/tests/install_consumer/main.cpp b/tests/install_consumer/main.cpp new file mode 100644 index 0000000..195eac2 --- /dev/null +++ b/tests/install_consumer/main.cpp @@ -0,0 +1,11 @@ +#include +#include + +int main() +{ + ML::Network network ({ 2, 2, 1 }); + network.feedForward ({ 0.0, 1.0 }); + std::vector result; + network.getResults (result); + return result.size() == 1 ? EXIT_SUCCESS : EXIT_FAILURE; +} From 8e6e6e7bf81d8c9cf66f412bd6b7dabb4cc406c6 Mon Sep 17 00:00:00 2001 From: Abhishek Shivakumar Date: Wed, 26 Aug 2026 08:54:16 +0100 Subject: [PATCH 2/2] Fix extended package export dependencies --- .github/workflows/ci.yml | 4 ++-- CMakeLists.txt | 13 ++++++++++++- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 471a139..9846378 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -44,7 +44,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v7 - name: Install build tools run: | @@ -84,7 +84,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v7 - name: Install build tools run: | diff --git a/CMakeLists.txt b/CMakeLists.txt index d3ae32c..6944e91 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,9 @@ cmake_minimum_required(VERSION 3.20) +if(POLICY CMP0135) + cmake_policy(SET CMP0135 NEW) +endif() + project( TinyML VERSION 1.0.0 @@ -180,7 +184,14 @@ if(TINYML_BUILD_EXTENDED) SOVERSION ${PROJECT_VERSION_MAJOR} ) tinyml_configure_target(TinyML) - target_link_libraries(TinyML PUBLIC TinyMLCore ${TINYML_XSIMD_TARGET}) + target_link_libraries( + TinyML + PUBLIC + $ + $ + $ + $ + ) if(UNIX AND NOT APPLE) target_link_libraries(TinyML PRIVATE m)