Skip to content

Commit fa0a3c3

Browse files
Merge pull request #707 from SKaiNET-developers/feature/696-minerva-docs-sample
Feature/696 minerva docs sample
2 parents d86f003 + 93507c4 commit fa0a3c3

16 files changed

Lines changed: 3011 additions & 60 deletions

File tree

README.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,12 +185,24 @@ deployment, the StableHLO path for native and edge targets.
185185
- Export trained models to standalone, optimized C99 with static memory allocation
186186
- Ready-to-use Arduino library output
187187

188+
### Edge AI: Minerva Secure MCU Export
189+
190+
- Export supported static MLP graphs to Minerva project bundles for secure MCU inference
191+
- Emits compiler NPZ input, libminerva weights, a manifest, host harness, firmware example, and host verification results
192+
- Start with the [Minerva export guide](docs/modules/ROOT/pages/how-to/minerva-export.adoc)
193+
188194
### Compiler: MLIR / StableHLO
189195

190196
- Lower Kotlin DSL to MLIR StableHLO dialect
191197
- Optimization passes: constant folding, operation fusion, dead code elimination
192198
- Valid IREE-compilable output with streaming API and public `HloGenerator`
193199

200+
### Choosing an Export Path
201+
202+
- Use **StableHLO** when you want portable MLIR/IREE-compatible graphs for native, accelerator, or ecosystem compiler flows.
203+
- Use **Arduino / C99 export** when you want standalone generated C with static memory allocation and no external secure runtime.
204+
- Use **Minerva export** when you need a secure MCU project bundle that goes through libminerva packaging and host verification.
205+
194206
---
195207

196208
## What's New in 0.28.1

docs/export/minerva.md

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
# Minerva Secure MCU Export
2+
3+
Minerva export packages a supported SKaiNET compute graph for secure MCU inference through libminerva. The maintained docs-site version is [`docs/modules/ROOT/pages/how-to/minerva-export.adoc`](../modules/ROOT/pages/how-to/minerva-export.adoc); this Markdown entrypoint keeps the repository path requested by the planning issue and is friendly to GitHub browsing.
4+
5+
## Setup
6+
7+
Inside this repository, use `project(":skainet-compile:skainet-compile-minerva")`. Published applications should import the SKaiNET BOM and add `sk.ainet.core:skainet-compile-minerva`.
8+
9+
Configure libminerva through `MinervaExportOptions` or the JVM sample environment:
10+
11+
```bash
12+
export MINERVA_COMPILER_SCRIPT=/opt/libminerva/tools/compile_model.py
13+
export MINERVA_RUNTIME_ROOT=/opt/libminerva
14+
export MINERVA_CALIBRATION_NPZ=/secure/project/calibration.npz
15+
export MINERVA_KEY_FILE=/secure/project/device.key
16+
export MINERVA_RUN_CMAKE=true
17+
export MINERVA_RUN_CTEST=true
18+
```
19+
20+
Do not commit real device keys. `include/secrets.example.h` contains placeholders only.
21+
22+
## Compatibility
23+
24+
| Area | Phase-one support |
25+
|---|---|
26+
| Host platform | JVM export path |
27+
| Target | `MinervaTarget.ATMEGA328P` |
28+
| Quantization | `MinervaQuantization.Q8` |
29+
| Graphs | Static, single-path, sequential MLPs |
30+
| Shapes | Fully known rank-2 tensor shapes |
31+
| Pattern | `Input -> MatMul -> Add? -> activation?`, repeated in sequence |
32+
| Activations | `Relu`, `Sigmoid`, `Tanh` after a dense layer |
33+
| Out of scope | CNNs, attention, recurrent models, dynamic shapes, branching graphs, transformers, arbitrary ONNX operators |
34+
35+
## Export API
36+
37+
```kotlin
38+
val options = MinervaExportOptions(
39+
outputDir = "build/minerva",
40+
projectName = "TinySecureMlp",
41+
compilerScript = "/opt/libminerva/tools/compile_model.py",
42+
runtimeRoot = "/opt/libminerva",
43+
calibrationNpz = "/secure/project/calibration.npz",
44+
keyFile = "/secure/project/device.key"
45+
)
46+
47+
val result = MinervaExportFacade().exportGraph(graph, options)
48+
val bundle = result.requireSuccess()
49+
println(bundle.outputDir)
50+
```
51+
52+
If the compiler script is missing, export still runs compatibility validation, lowering, and NPZ generation before returning a typed compiler prerequisite failure.
53+
54+
## Generated Layout
55+
56+
```text
57+
build/minerva/TinySecureMlp/
58+
manifest.json
59+
generated/
60+
model.npz
61+
weights.c
62+
include/
63+
weights.h
64+
secrets.example.h
65+
host/
66+
CMakeLists.txt
67+
main.c
68+
firmware/
69+
main.c
70+
```
71+
72+
## Host Verification and CI
73+
74+
Host verification checks package structure, generated weight files, `model.npz` integrity, placeholder secret hygiene, and SKaiNET reference output generation. Add these metadata keys to opt into CMake, CTest, and parity comparison with a host output file:
75+
76+
```kotlin
77+
metadata = mapOf(
78+
MinervaHostVerificationMetadata.RUN_CMAKE_BUILD to "true",
79+
MinervaHostVerificationMetadata.RUN_CTEST to "true",
80+
MinervaHostVerificationMetadata.HOST_OUTPUT_PATH to "host-output.txt"
81+
)
82+
```
83+
84+
CI recipe:
85+
86+
```bash
87+
./gradlew :skainet-compile:skainet-compile-minerva:jvmTest
88+
./gradlew :skainet-compile:skainet-compile-minerva:minervaHostVerification \
89+
-Pminerva.hostVerification.enabled=true \
90+
-Pminerva.runtimeRoot="$MINERVA_RUNTIME_ROOT" \
91+
-Pminerva.compilerScript="$MINERVA_COMPILER_SCRIPT"
92+
cmake -S build/minerva/TinySecureMlp/host -B build/minerva/TinySecureMlp/host/build
93+
ctest --test-dir build/minerva/TinySecureMlp/host/build --output-on-failure
94+
```
95+
96+
## ONNX to Minerva
97+
98+
Use the existing ONNX loader to inspect a model and reject unsupported operators before constructing a compatible SKaiNET `ComputeGraph`:
99+
100+
```kotlin
101+
val loaded = OnnxLoader.fromModelSource {
102+
File(path).inputStream().asSource()
103+
}.load()
104+
val graph = loaded.proto.graph ?: error("ONNX model has no graph")
105+
val ops = graph.node.map { it.opType }.toSet()
106+
require(ops.all { it in setOf("MatMul", "Gemm", "Add", "Relu", "Sigmoid", "Tanh") })
107+
```
108+
109+
The first phase does not include a general ONNX-to-Minerva importer.
110+
111+
## Firmware Integration
112+
113+
The generated firmware example intentionally contains placeholders. Confirm the libminerva inference entry point and output-authentication API names against the pinned libminerva version used by your product build before flashing firmware.
114+
115+
## Maintained JVM Sample
116+
117+
`sk.ainet.compile.minerva.examples.MinervaTinyMlpExportSample` builds a tiny two-layer MLP, reads Minerva paths from environment variables, invokes the export facade, and prints bundle and verification status. `MinervaTinyMlpExportSampleTest` validates the sample graph and NPZ generation without real device keys.
118+
119+
## Export Path Choice
120+
121+
- Use StableHLO for portable MLIR/IREE-compatible compiler flows.
122+
- Use Arduino / C99 export for standalone generated C with static memory allocation.
123+
- Use Minerva export for secure MCU bundles compiled by libminerva and checked by host verification.

docs/modules/ROOT/nav.adoc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
** xref:how-to/io-readers.adoc[Load models (GGUF, SafeTensors, ONNX)]
1313
** xref:how-to/java-model-training.adoc[Train a model from Java]
1414
** xref:how-to/arduino-c-codegen.adoc[Generate C for Arduino]
15+
** xref:how-to/minerva-export.adoc[Export secure MCU bundles with Minerva]
1516
* Reference
1617
** xref:reference/architecture.adoc[Architecture]
1718
** xref:reference/graph-export-architecture.adoc[Graph export architecture]
Lines changed: 239 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,239 @@
1+
= Minerva Secure MCU Export
2+
3+
Minerva export packages a supported SKaiNET compute graph for secure MCU inference through libminerva. Phase one is JVM-first and intentionally narrow: static sequential MLP graphs, Q8 quantization, and the ATmega328P target.
4+
5+
Use this backend when the output must be a Minerva project bundle with compiler input, generated weights, firmware and host harnesses, a manifest, and host verification metadata.
6+
7+
== When to Use Each Export Path
8+
9+
[cols="1,2",options="header"]
10+
|===
11+
| Export path | Use it when
12+
13+
| StableHLO
14+
| You need a portable MLIR/IREE-compatible graph for native, accelerator, or ecosystem compiler flows.
15+
16+
| Arduino / C99
17+
| You need standalone generated C with static memory allocation and no external secure runtime.
18+
19+
| Minerva
20+
| You need a secure MCU project bundle that is compiled by libminerva and checked by host verification.
21+
|===
22+
23+
== Setup
24+
25+
Inside this repository, use the Minerva module directly:
26+
27+
[source,kotlin]
28+
----
29+
dependencies {
30+
implementation(project(":skainet-compile:skainet-compile-minerva"))
31+
}
32+
----
33+
34+
For a published application, use the SKaiNET BOM and the Minerva artifact:
35+
36+
[source,kotlin]
37+
----
38+
dependencies {
39+
implementation(platform("sk.ainet:skainet-bom:0.28.1"))
40+
implementation("sk.ainet.core:skainet-compile-minerva")
41+
}
42+
----
43+
44+
Configure libminerva through `MinervaExportOptions` or environment variables used by the maintained JVM sample:
45+
46+
[source,bash]
47+
----
48+
export MINERVA_COMPILER_SCRIPT=/opt/libminerva/tools/compile_model.py
49+
export MINERVA_RUNTIME_ROOT=/opt/libminerva
50+
export MINERVA_CALIBRATION_NPZ=/secure/project/calibration.npz
51+
export MINERVA_KEY_FILE=/secure/project/device.key
52+
export MINERVA_RUN_CMAKE=true
53+
export MINERVA_RUN_CTEST=true
54+
----
55+
56+
`MINERVA_KEY_FILE` and the generated `include/secrets.example.h` are placeholders for integration. Do not commit real device keys or derived secrets.
57+
58+
== Compatibility Matrix
59+
60+
[cols="1,2",options="header"]
61+
|===
62+
| Area | Phase-one support
63+
64+
| Host platform
65+
| JVM export path.
66+
67+
| Target
68+
| `MinervaTarget.ATMEGA328P`.
69+
70+
| Quantization
71+
| `MinervaQuantization.Q8`.
72+
73+
| Graph shape
74+
| Static, single-path, sequential MLPs.
75+
76+
| Tensor shapes
77+
| Fully known rank-2 shapes.
78+
79+
| Layer pattern
80+
| `Input -> MatMul -> Add? -> activation?`, repeated in sequence.
81+
82+
| Activations
83+
| `Relu`, `Sigmoid`, and `Tanh` after a dense layer.
84+
85+
| Out of scope
86+
| CNNs, attention blocks, recurrent models, dynamic shapes, branching graphs, transformers, and arbitrary ONNX operators.
87+
|===
88+
89+
== Export API
90+
91+
The public entry point is `MinervaExportFacade`. The facade accepts an already-built `ComputeGraph`, or it can record a representative forward pass for compatible SKaiNET models.
92+
93+
[source,kotlin]
94+
----
95+
import sk.ainet.compile.minerva.MinervaExportFacade
96+
import sk.ainet.compile.minerva.MinervaExportOptions
97+
98+
val options = MinervaExportOptions(
99+
outputDir = "build/minerva",
100+
projectName = "TinySecureMlp",
101+
compilerScript = "/opt/libminerva/tools/compile_model.py",
102+
runtimeRoot = "/opt/libminerva",
103+
calibrationNpz = "/secure/project/calibration.npz",
104+
keyFile = "/secure/project/device.key"
105+
)
106+
107+
val result = MinervaExportFacade().exportGraph(graph, options)
108+
val bundle = result.requireSuccess()
109+
println(bundle.outputDir)
110+
----
111+
112+
If `compilerScript` is missing, export still performs compatibility checks, Minerva lowering, and NPZ schema creation before returning a typed compiler prerequisite failure. That makes local validation possible before libminerva is installed.
113+
114+
== Generated Project Layout
115+
116+
A successful export writes a project directory under `outputDir/projectName`:
117+
118+
[source,text]
119+
----
120+
build/minerva/TinySecureMlp/
121+
manifest.json
122+
generated/
123+
model.npz
124+
weights.c
125+
include/
126+
weights.h
127+
secrets.example.h
128+
host/
129+
CMakeLists.txt
130+
main.c
131+
firmware/
132+
main.c
133+
----
134+
135+
The manifest records the target, quantization, libminerva root, compiler command summary, NPZ schema version, layer count, and generated files. `secrets.example.h` contains placeholder values only.
136+
137+
== Host Verification
138+
139+
Host verification always checks the package structure, generated weight files, `model.npz` integrity, and placeholder secret hygiene. It also computes the SKaiNET reference output for a deterministic reference input.
140+
141+
Use these metadata keys to opt into external host checks:
142+
143+
[source,kotlin]
144+
----
145+
metadata = mapOf(
146+
MinervaHostVerificationMetadata.RUN_CMAKE_BUILD to "true",
147+
MinervaHostVerificationMetadata.RUN_CTEST to "true",
148+
MinervaHostVerificationMetadata.HOST_OUTPUT_PATH to "host-output.txt"
149+
)
150+
----
151+
152+
`RUN_CMAKE_BUILD` configures and builds `host/CMakeLists.txt`. `RUN_CTEST` runs the packaged CTest smoke test. `HOST_OUTPUT_PATH` lets a real host run write comma- or whitespace-separated float outputs that are compared with the SKaiNET reference output using `hostVerificationTolerance`.
153+
154+
Local CI recipe:
155+
156+
[source,bash]
157+
----
158+
./gradlew :skainet-compile:skainet-compile-minerva:jvmTest
159+
./gradlew :skainet-compile:skainet-compile-minerva:minervaHostVerification \
160+
-Pminerva.hostVerification.enabled=true \
161+
-Pminerva.runtimeRoot="$MINERVA_RUNTIME_ROOT" \
162+
-Pminerva.compilerScript="$MINERVA_COMPILER_SCRIPT"
163+
cmake -S build/minerva/TinySecureMlp/host -B build/minerva/TinySecureMlp/host/build
164+
ctest --test-dir build/minerva/TinySecureMlp/host/build --output-on-failure
165+
----
166+
167+
The Gradle `minervaHostVerification` task is gated. It only runs when the `minerva.hostVerification.enabled`, `minerva.runtimeRoot`, and `minerva.compilerScript` properties are present.
168+
169+
== Firmware Integration
170+
171+
The generated firmware example intentionally contains integration placeholders. Wire it to the runtime API exposed by your pinned libminerva checkout and keep the real secret configuration in a private header that is excluded from source control.
172+
173+
Before flashing firmware:
174+
175+
* Replace `secrets.example.h` with a private header outside the repository.
176+
* Confirm the libminerva inference entry point and output-authentication API names against the libminerva version used by your product build.
177+
* Re-run host verification after any compiler, runtime, calibration, or key change.
178+
* Keep `manifest.json` with release artifacts so the compiler command and schema version remain auditable.
179+
180+
== ONNX to Minerva Workflow
181+
182+
Use the existing ONNX loader path to inspect the model and extract a supported static MLP graph before calling Minerva export. The first phase does not include a general ONNX-to-Minerva importer; unsupported ONNX operators should be rejected before export.
183+
184+
[source,kotlin]
185+
----
186+
import kotlinx.io.asSource
187+
import sk.ainet.io.onnx.OnnxLoader
188+
import java.io.File
189+
190+
suspend fun loadOnnxForMinerva(path: String) {
191+
val loaded = OnnxLoader.fromModelSource {
192+
File(path).inputStream().asSource()
193+
}.load()
194+
val graph = loaded.proto.graph ?: error("ONNX model has no graph")
195+
196+
val ops = graph.node.map { it.opType }.toSet()
197+
require(ops.all { it in setOf("MatMul", "Gemm", "Add", "Relu", "Sigmoid", "Tanh") }) {
198+
"ONNX graph contains operators outside the Minerva phase-one scope: $ops"
199+
}
200+
201+
// Convert the inspected static MLP to a SKaiNET ComputeGraph, then call MinervaExportFacade.
202+
}
203+
----
204+
205+
== Maintained JVM Sample
206+
207+
The maintained sample is `sk.ainet.compile.minerva.examples.MinervaTinyMlpExportSample` in the Minerva module. It builds a tiny two-layer MLP, reads Minerva paths from environment variables, invokes the export facade, and prints bundle and verification status.
208+
209+
Run the sample after configuring libminerva:
210+
211+
[source,bash]
212+
----
213+
./gradlew :skainet-compile:skainet-compile-minerva:jvmTest
214+
./gradlew :skainet-compile:skainet-compile-minerva:jvmJar
215+
----
216+
217+
The sample graph is covered by `MinervaTinyMlpExportSampleTest`, which validates compatibility, lowering, and NPZ generation without requiring real device keys.
218+
219+
== Troubleshooting
220+
221+
[cols="1,2",options="header"]
222+
|===
223+
| Symptom | Fix
224+
225+
| `minerva.compiler.script_missing`
226+
| Set `MinervaExportOptions.compilerScript` or `MINERVA_COMPILER_SCRIPT`.
227+
228+
| `minerva.compiler.runtime_root_not_found`
229+
| Point `runtimeRoot` or `MINERVA_RUNTIME_ROOT` at the libminerva checkout or install directory.
230+
231+
| Compatibility fails for an unsupported operation
232+
| Reduce the graph to the phase-one MLP pattern, or use StableHLO for a general compiler flow.
233+
234+
| CMake or CTest verification fails
235+
| Inspect `minerva-host-verification.log`, confirm CMake is installed, and confirm the generated host harness is linked against the pinned libminerva runtime.
236+
237+
| Secret leak check fails
238+
| Remove real secrets from generated files and regenerate the bundle. Only placeholders belong in source control.
239+
|===

0 commit comments

Comments
 (0)