From 0ad639ae25c2f923baee588c93e2112a672b49ac Mon Sep 17 00:00:00 2001 From: Abhishek Shivakumar Date: Mon, 17 Aug 2026 10:44:18 +0100 Subject: [PATCH 1/5] Add repository maintenance automation and issue forms --- .github/ISSUE_TEMPLATE/bug.yml | 62 ++++++++++++++++++++++++++++++ .github/ISSUE_TEMPLATE/config.yml | 5 +++ .github/ISSUE_TEMPLATE/feature.yml | 47 ++++++++++++++++++++++ .github/dependabot.yml | 10 +++++ 4 files changed, 124 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/bug.yml create mode 100644 .github/ISSUE_TEMPLATE/config.yml create mode 100644 .github/ISSUE_TEMPLATE/feature.yml create mode 100644 .github/dependabot.yml diff --git a/.github/ISSUE_TEMPLATE/bug.yml b/.github/ISSUE_TEMPLATE/bug.yml new file mode 100644 index 0000000..7fb364e --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug.yml @@ -0,0 +1,62 @@ +name: Bug report +description: Report a reproducible correctness, build, API, or runtime problem +title: "[Bug]: " +labels: [] +body: + - type: markdown + attributes: + value: Thanks for taking the time to report a bug. Please keep performance-only regressions separate from correctness failures where possible. + - type: textarea + id: summary + attributes: + label: What happened? + description: Describe the observed behaviour and what you expected instead. + validations: + required: true + - type: textarea + id: reproduce + attributes: + label: Minimal reproduction + description: Include the smallest source/configuration and commands needed to reproduce the problem. + render: shell + validations: + required: true + - type: input + id: commit + attributes: + label: tinyML commit or release + placeholder: e.g. main at abc1234 or v1.0.0 + validations: + required: true + - type: dropdown + id: build + attributes: + label: Build tier + options: + - MIT core + - Extended/commercial tier + - Unsure + validations: + required: true + - type: textarea + id: environment + attributes: + label: Environment + description: OS, architecture, compiler and version, CMake version, CPU features, and relevant build flags. + placeholder: Ubuntu 24.04, x86_64, GCC 14, CMake 3.30, AVX2, Release + validations: + required: true + - type: textarea + id: logs + attributes: + label: Relevant logs or output + render: text + - type: checkboxes + id: checks + attributes: + label: Checks + options: + - label: I searched existing issues for the same problem. + required: true + - label: I have not included secrets, private data, or proprietary model/data content. + required: true diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..6918801 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,5 @@ +blank_issues_enabled: true +contact_links: + - name: Security vulnerability + url: https://github.com/godofecht/tinyML/security/advisories/new + about: Please report security-sensitive issues privately rather than opening a public issue. diff --git a/.github/ISSUE_TEMPLATE/feature.yml b/.github/ISSUE_TEMPLATE/feature.yml new file mode 100644 index 0000000..d7b9c2b --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature.yml @@ -0,0 +1,47 @@ +name: Feature request +description: Propose a new algorithm, API, platform, optimization, or repository capability +title: "[Feature]: " +labels: [] +body: + - type: textarea + id: problem + attributes: + label: Problem or use case + description: What concrete problem should tinyML solve? + validations: + required: true + - type: textarea + id: proposal + attributes: + label: Proposed solution + description: Describe the desired API, behaviour, algorithm, platform support, or workflow. + validations: + required: true + - type: dropdown + id: tier + attributes: + label: Intended tier + options: + - MIT core + - Extended/commercial tier + - Repository/tooling/docs + - Unsure + validations: + required: true + - type: textarea + id: constraints + attributes: + label: Constraints and trade-offs + description: Include real-time, memory, determinism, dependency, portability, licensing, or numerical constraints that matter. + - type: textarea + id: validation + attributes: + label: How should this be validated? + description: Tests, benchmarks, reference outputs, downstream examples, or acceptance criteria. + - type: checkboxes + id: checks + attributes: + label: Checks + options: + - label: I searched existing issues and the roadmap for an equivalent proposal. + required: true diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..9101b45 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,10 @@ +version: 2 +updates: + - package-ecosystem: github-actions + directory: / + schedule: + interval: weekly + open-pull-requests-limit: 5 + commit-message: + prefix: chore + include: scope From 57bf358eec8eaeea7c1d4933b4e7a89294c35592 Mon Sep 17 00:00:00 2001 From: Abhishek Shivakumar Date: Mon, 17 Aug 2026 10:45:36 +0100 Subject: [PATCH 2/5] Run Clang CI without hard-wired Release LTO --- .github/workflows/ci.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1a8ea68..fd6c0e8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -17,7 +17,7 @@ concurrency: jobs: build-and-test: - name: ${{ matrix.compiler }} / Release + name: ${{ matrix.compiler }} / ${{ matrix.build_type }} runs-on: ubuntu-latest strategy: fail-fast: false @@ -26,9 +26,11 @@ jobs: - compiler: GCC cc: gcc cxx: g++ + build_type: Release - compiler: Clang cc: clang cxx: clang++ + build_type: Debug env: CC: ${{ matrix.cc }} CXX: ${{ matrix.cxx }} @@ -43,7 +45,7 @@ jobs: sudo apt-get install -y ninja-build - name: Configure - run: cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release + run: cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} - name: Build run: cmake --build build --parallel 2 From 33ba897ba89d598b5aedc4850b3703fc545e6a70 Mon Sep 17 00:00:00 2001 From: Abhishek Shivakumar Date: Mon, 17 Aug 2026 10:46:07 +0100 Subject: [PATCH 3/5] Package usable headers and licenses in release archives --- .github/workflows/ci.yml | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fd6c0e8..6e4c9be 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -77,10 +77,13 @@ jobs: - name: Package run: | - mkdir -p release - cp build/libTinyML.a release/ - find build/bin -type f -executable -exec cp {} release/ \; 2>/dev/null || true - tar -czf tinyml-${{ github.ref_name }}-linux-x86_64.tar.gz -C release . + 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 - name: Publish GitHub release uses: softprops/action-gh-release@v2 From 2872fed66c76efa5e4b1c51f61aae3cd7452aa01 Mon Sep 17 00:00:00 2001 From: Abhishek Shivakumar Date: Mon, 17 Aug 2026 12:04:43 +0100 Subject: [PATCH 4/5] fix(ci): include algorithm in production API test --- tests/test_production_api.cpp | 218 +--------------------------------- 1 file changed, 2 insertions(+), 216 deletions(-) diff --git a/tests/test_production_api.cpp b/tests/test_production_api.cpp index e1c8272..a4fd4e4 100644 --- a/tests/test_production_api.cpp +++ b/tests/test_production_api.cpp @@ -7,6 +7,7 @@ #include "TinyMLAPI.h" #include +#include #include #include #include @@ -267,219 +268,4 @@ TEST_F(TinyMLAPITest, HardwareAcceleration) { // Results depend on the system, but should not crash EXPECT_TRUE(gpu_enabled || !gpu_enabled); // Always true, just checking no crash EXPECT_TRUE(metal_enabled || !metal_enabled); -} - -TEST_F(TinyMLAPITest, SystemInformation) { - ModelConfig config; - config.embed_dim = 64; - config.num_heads = 2; - config.num_layers = 1; - config.sequence_length = 128; - - EXPECT_TRUE(api_->create_model(config)); - - std::string model_info = api_->get_model_info(); - EXPECT_FALSE(model_info.empty()); - EXPECT_NE(model_info.find("Embedding Dimension"), std::string::npos); - - std::string system_info = api_->get_system_info(); - EXPECT_FALSE(system_info.empty()); - EXPECT_NE(system_info.find("CPU Cores"), std::string::npos); -} - -TEST_F(TinyMLAPITest, SelfTest) { - ModelConfig config; - config.embed_dim = 64; - config.num_heads = 2; - config.num_layers = 1; - config.sequence_length = 128; - - EXPECT_TRUE(api_->create_model(config)); - - EXPECT_TRUE(api_->run_self_test()); -} - -TEST_F(TinyMLAPITest, FactoryFunctions) { - // Test edge device factory - auto edge_api = create_tinyml_api_for_edge(); - EXPECT_NE(edge_api, nullptr); - - auto edge_config = edge_api->get_model_config(); - EXPECT_EQ(edge_config.embed_dim, 128); - EXPECT_EQ(edge_config.num_heads, 4); - - // Test mobile device factory - auto mobile_api = create_tinyml_api_for_mobile(); - EXPECT_NE(mobile_api, nullptr); - - auto mobile_config = mobile_api->get_model_config(); - EXPECT_EQ(mobile_config.embed_dim, 64); - EXPECT_EQ(mobile_config.num_heads, 2); - - // Test server device factory - auto server_api = create_tinyml_api_for_server(); - EXPECT_NE(server_api, nullptr); - - auto server_config = server_api->get_model_config(); - EXPECT_EQ(server_config.embed_dim, 512); - EXPECT_EQ(server_config.num_heads, 16); -} - -class TinyMLUtilsTest : public ::testing::Test { -protected: - void SetUp() override {} -}; - -TEST_F(TinyMLUtilsTest, PredefinedConfigs) { - auto configs = Utils::get_predefined_configs(); - EXPECT_EQ(configs.size(), 4); - - // Check mobile config - auto& mobile = configs[0]; - EXPECT_EQ(mobile.embed_dim, 64); - EXPECT_EQ(mobile.num_heads, 2); - EXPECT_EQ(mobile.num_layers, 1); - - // Check edge config - auto& edge = configs[1]; - EXPECT_EQ(edge.embed_dim, 128); - EXPECT_EQ(edge.num_heads, 4); - EXPECT_EQ(edge.num_layers, 2); - - // Check server config - auto& server = configs[2]; - EXPECT_EQ(server.embed_dim, 512); - EXPECT_EQ(server.num_heads, 16); - EXPECT_EQ(server.num_layers, 6); -} - -TEST_F(TinyMLUtilsTest, ConfigValidation) { - // Valid config - ModelConfig valid_config; - valid_config.embed_dim = 128; - valid_config.num_heads = 8; - valid_config.num_layers = 2; - valid_config.sequence_length = 256; - valid_config.dropout_rate = 0.1f; - valid_config.device = "cpu"; - - EXPECT_TRUE(Utils::validate_config(valid_config)); - - // Invalid embed_dim - ModelConfig invalid_embed = valid_config; - invalid_embed.embed_dim = 0; - EXPECT_FALSE(Utils::validate_config(invalid_embed)); - - // Invalid num_heads - ModelConfig invalid_heads = valid_config; - invalid_heads.num_heads = 0; - EXPECT_FALSE(Utils::validate_config(invalid_heads)); - - // Embed_dim not divisible by num_heads - ModelConfig invalid_divisible = valid_config; - invalid_divisible.embed_dim = 100; - invalid_divisible.num_heads = 3; - EXPECT_FALSE(Utils::validate_config(invalid_divisible)); - - // Invalid dropout rate - ModelConfig invalid_dropout = valid_config; - invalid_dropout.dropout_rate = 1.5f; - EXPECT_FALSE(Utils::validate_config(invalid_dropout)); -} - -TEST_F(TinyMLUtilsTest, ConfigSerialization) { - ModelConfig original; - original.embed_dim = 256; - original.num_heads = 8; - original.num_layers = 4; - original.sequence_length = 512; - original.dropout_rate = 0.1f; - original.use_quantization = true; - original.device = "gpu"; - - std::string config_str = Utils::config_to_string(original); - EXPECT_FALSE(config_str.empty()); - EXPECT_NE(config_str.find("embed_dim=256"), std::string::npos); - EXPECT_NE(config_str.find("num_heads=8"), std::string::npos); - - ModelConfig deserialized = Utils::config_from_string(config_str); - EXPECT_EQ(deserialized.embed_dim, original.embed_dim); - EXPECT_EQ(deserialized.num_heads, original.num_heads); - EXPECT_EQ(deserialized.num_layers, original.num_layers); - EXPECT_EQ(deserialized.sequence_length, original.sequence_length); - EXPECT_FLOAT_EQ(deserialized.dropout_rate, original.dropout_rate); - EXPECT_EQ(deserialized.use_quantization, original.use_quantization); - EXPECT_EQ(deserialized.device, original.device); -} - -TEST_F(TinyMLUtilsTest, PerformanceProfiler) { - Utils::PerformanceProfiler profiler; - - profiler.start_profiling(); - - // Simulate some work - std::this_thread::sleep_for(std::chrono::milliseconds(10)); - - profiler.end_profiling(); - - auto metrics = profiler.get_metrics(); - EXPECT_GT(metrics.avg_latency_ms, 10.0); // Should be at least 10ms - EXPECT_EQ(metrics.total_inferences, 1); - EXPECT_GT(metrics.throughput_tokens_per_sec, 0.0); - - profiler.reset(); - auto reset_metrics = profiler.get_metrics(); - EXPECT_EQ(reset_metrics.total_inferences, 0); -} - -// Integration test for the complete workflow -class TinyMLIntegrationTest : public ::testing::Test { -protected: - void SetUp() override { - api_ = create_tinyml_api_for_edge(); - } - - void TearDown() override { - api_->destroy_model(); - } - - std::unique_ptr api_; -}; - -TEST_F(TinyMLIntegrationTest, CompleteWorkflow) { - // Model should already be created by factory function - - // 1. Run self-test - EXPECT_TRUE(api_->run_self_test()); - - // 2. Optimize for edge deployment - DeploymentConfig deploy_config; - deploy_config.target_platform = "edge"; - deploy_config.enable_hardware_acceleration = false; - EXPECT_TRUE(api_->deploy_model(deploy_config)); - - // 3. Process some data - std::vector input(128, 0.1f); - auto result = api_->predict(input); - EXPECT_TRUE(result.success); - - // 4. Check performance - auto metrics = api_->get_performance_metrics(); - EXPECT_GT(metrics.total_inferences, 0); - - // 5. Export model - EXPECT_TRUE(api_->export_model("native", "integration_test_model.bin")); - - // 6. Load and verify - api_->destroy_model(); - EXPECT_TRUE(api_->load_model("integration_test_model.bin")); - EXPECT_TRUE(api_->run_self_test()); - - // Cleanup - std::remove("integration_test_model.bin"); -} - -int main(int argc, char** argv) { - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} +} \ No newline at end of file From 239633a67b749d29cf6d0340a1f4943b90888b63 Mon Sep 17 00:00:00 2001 From: Abhishek Shivakumar Date: Mon, 17 Aug 2026 12:05:54 +0100 Subject: [PATCH 5/5] fix(ci): restore production API tests and include algorithm --- tests/test_production_api.cpp | 217 +++++++++++++++++++++++++++++++++- 1 file changed, 216 insertions(+), 1 deletion(-) diff --git a/tests/test_production_api.cpp b/tests/test_production_api.cpp index a4fd4e4..1954077 100644 --- a/tests/test_production_api.cpp +++ b/tests/test_production_api.cpp @@ -268,4 +268,219 @@ TEST_F(TinyMLAPITest, HardwareAcceleration) { // Results depend on the system, but should not crash EXPECT_TRUE(gpu_enabled || !gpu_enabled); // Always true, just checking no crash EXPECT_TRUE(metal_enabled || !metal_enabled); -} \ No newline at end of file +} + +TEST_F(TinyMLAPITest, SystemInformation) { + ModelConfig config; + config.embed_dim = 64; + config.num_heads = 2; + config.num_layers = 1; + config.sequence_length = 128; + + EXPECT_TRUE(api_->create_model(config)); + + std::string model_info = api_->get_model_info(); + EXPECT_FALSE(model_info.empty()); + EXPECT_NE(model_info.find("Embedding Dimension"), std::string::npos); + + std::string system_info = api_->get_system_info(); + EXPECT_FALSE(system_info.empty()); + EXPECT_NE(system_info.find("CPU Cores"), std::string::npos); +} + +TEST_F(TinyMLAPITest, SelfTest) { + ModelConfig config; + config.embed_dim = 64; + config.num_heads = 2; + config.num_layers = 1; + config.sequence_length = 128; + + EXPECT_TRUE(api_->create_model(config)); + + EXPECT_TRUE(api_->run_self_test()); +} + +TEST_F(TinyMLAPITest, FactoryFunctions) { + // Test edge device factory + auto edge_api = create_tinyml_api_for_edge(); + EXPECT_NE(edge_api, nullptr); + + auto edge_config = edge_api->get_model_config(); + EXPECT_EQ(edge_config.embed_dim, 128); + EXPECT_EQ(edge_config.num_heads, 4); + + // Test mobile device factory + auto mobile_api = create_tinyml_api_for_mobile(); + EXPECT_NE(mobile_api, nullptr); + + auto mobile_config = mobile_api->get_model_config(); + EXPECT_EQ(mobile_config.embed_dim, 64); + EXPECT_EQ(mobile_config.num_heads, 2); + + // Test server device factory + auto server_api = create_tinyml_api_for_server(); + EXPECT_NE(server_api, nullptr); + + auto server_config = server_api->get_model_config(); + EXPECT_EQ(server_config.embed_dim, 512); + EXPECT_EQ(server_config.num_heads, 16); +} + +class TinyMLUtilsTest : public ::testing::Test { +protected: + void SetUp() override {} +}; + +TEST_F(TinyMLUtilsTest, PredefinedConfigs) { + auto configs = Utils::get_predefined_configs(); + EXPECT_EQ(configs.size(), 4); + + // Check mobile config + auto& mobile = configs[0]; + EXPECT_EQ(mobile.embed_dim, 64); + EXPECT_EQ(mobile.num_heads, 2); + EXPECT_EQ(mobile.num_layers, 1); + + // Check edge config + auto& edge = configs[1]; + EXPECT_EQ(edge.embed_dim, 128); + EXPECT_EQ(edge.num_heads, 4); + EXPECT_EQ(edge.num_layers, 2); + + // Check server config + auto& server = configs[2]; + EXPECT_EQ(server.embed_dim, 512); + EXPECT_EQ(server.num_heads, 16); + EXPECT_EQ(server.num_layers, 6); +} + +TEST_F(TinyMLUtilsTest, ConfigValidation) { + // Valid config + ModelConfig valid_config; + valid_config.embed_dim = 128; + valid_config.num_heads = 8; + valid_config.num_layers = 2; + valid_config.sequence_length = 256; + valid_config.dropout_rate = 0.1f; + valid_config.device = "cpu"; + + EXPECT_TRUE(Utils::validate_config(valid_config)); + + // Invalid embed_dim + ModelConfig invalid_embed = valid_config; + invalid_embed.embed_dim = 0; + EXPECT_FALSE(Utils::validate_config(invalid_embed)); + + // Invalid num_heads + ModelConfig invalid_heads = valid_config; + invalid_heads.num_heads = 0; + EXPECT_FALSE(Utils::validate_config(invalid_heads)); + + // Embed_dim not divisible by num_heads + ModelConfig invalid_divisible = valid_config; + invalid_divisible.embed_dim = 100; + invalid_divisible.num_heads = 3; + EXPECT_FALSE(Utils::validate_config(invalid_divisible)); + + // Invalid dropout rate + ModelConfig invalid_dropout = valid_config; + invalid_dropout.dropout_rate = 1.5f; + EXPECT_FALSE(Utils::validate_config(invalid_dropout)); +} + +TEST_F(TinyMLUtilsTest, ConfigSerialization) { + ModelConfig original; + original.embed_dim = 256; + original.num_heads = 8; + original.num_layers = 4; + original.sequence_length = 512; + original.dropout_rate = 0.1f; + original.use_quantization = true; + original.device = "gpu"; + + std::string config_str = Utils::config_to_string(original); + EXPECT_FALSE(config_str.empty()); + EXPECT_NE(config_str.find("embed_dim=256"), std::string::npos); + EXPECT_NE(config_str.find("num_heads=8"), std::string::npos); + + ModelConfig deserialized = Utils::config_from_string(config_str); + EXPECT_EQ(deserialized.embed_dim, original.embed_dim); + EXPECT_EQ(deserialized.num_heads, original.num_heads); + EXPECT_EQ(deserialized.num_layers, original.num_layers); + EXPECT_EQ(deserialized.sequence_length, original.sequence_length); + EXPECT_FLOAT_EQ(deserialized.dropout_rate, original.dropout_rate); + EXPECT_EQ(deserialized.use_quantization, original.use_quantization); + EXPECT_EQ(deserialized.device, original.device); +} + +TEST_F(TinyMLUtilsTest, PerformanceProfiler) { + Utils::PerformanceProfiler profiler; + + profiler.start_profiling(); + + // Simulate some work + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + + profiler.end_profiling(); + + auto metrics = profiler.get_metrics(); + EXPECT_GT(metrics.avg_latency_ms, 10.0); // Should be at least 10ms + EXPECT_EQ(metrics.total_inferences, 1); + EXPECT_GT(metrics.throughput_tokens_per_sec, 0.0); + + profiler.reset(); + auto reset_metrics = profiler.get_metrics(); + EXPECT_EQ(reset_metrics.total_inferences, 0); +} + +// Integration test for the complete workflow +class TinyMLIntegrationTest : public ::testing::Test { +protected: + void SetUp() override { + api_ = create_tinyml_api_for_edge(); + } + + void TearDown() override { + api_->destroy_model(); + } + + std::unique_ptr api_; +}; + +TEST_F(TinyMLIntegrationTest, CompleteWorkflow) { + // Model should already be created by factory function + + // 1. Run self-test + EXPECT_TRUE(api_->run_self_test()); + + // 2. Optimize for edge deployment + DeploymentConfig deploy_config; + deploy_config.target_platform = "edge"; + deploy_config.enable_hardware_acceleration = false; + EXPECT_TRUE(api_->deploy_model(deploy_config)); + + // 3. Process some data + std::vector input(128, 0.1f); + auto result = api_->predict(input); + EXPECT_TRUE(result.success); + + // 4. Check performance + auto metrics = api_->get_performance_metrics(); + EXPECT_GT(metrics.total_inferences, 0); + + // 5. Export model + EXPECT_TRUE(api_->export_model("native", "integration_test_model.bin")); + + // 6. Load and verify + api_->destroy_model(); + EXPECT_TRUE(api_->load_model("integration_test_model.bin")); + EXPECT_TRUE(api_->run_self_test()); + + // Cleanup + std::remove("integration_test_model.bin"); +} + +int main(int argc, char** argv) { + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +}