aboutsummaryrefslogtreecommitdiff
path: root/platform/linux-generic/example
diff options
context:
space:
mode:
Diffstat (limited to 'platform/linux-generic/example')
-rw-r--r--platform/linux-generic/example/Makefile.am5
-rw-r--r--platform/linux-generic/example/ml/.gitignore5
-rw-r--r--platform/linux-generic/example/ml/Makefile.am46
-rw-r--r--platform/linux-generic/example/ml/README.md94
-rw-r--r--platform/linux-generic/example/ml/example_digit.csv1
-rw-r--r--platform/linux-generic/example/ml/mnist-12.onnxbin0 -> 26143 bytes
-rw-r--r--platform/linux-generic/example/ml/mnist.c300
-rw-r--r--platform/linux-generic/example/ml/model_explorer.c88
-rw-r--r--platform/linux-generic/example/ml/model_read.c47
-rw-r--r--platform/linux-generic/example/ml/model_read.h29
-rwxr-xr-xplatform/linux-generic/example/ml/odp_ml_run_mnist.sh9
-rwxr-xr-xplatform/linux-generic/example/ml/odp_ml_run_model_explorer.sh8
-rwxr-xr-xplatform/linux-generic/example/ml/odp_ml_run_simple_linear.sh8
-rw-r--r--platform/linux-generic/example/ml/simple_linear.c281
-rw-r--r--platform/linux-generic/example/ml/simple_linear.onnxbin0 -> 214 bytes
15 files changed, 921 insertions, 0 deletions
diff --git a/platform/linux-generic/example/Makefile.am b/platform/linux-generic/example/Makefile.am
new file mode 100644
index 000000000..84f337387
--- /dev/null
+++ b/platform/linux-generic/example/Makefile.am
@@ -0,0 +1,5 @@
+SUBDIRS =
+
+if WITH_ML
+SUBDIRS += ml
+endif
diff --git a/platform/linux-generic/example/ml/.gitignore b/platform/linux-generic/example/ml/.gitignore
new file mode 100644
index 000000000..d845f6bb5
--- /dev/null
+++ b/platform/linux-generic/example/ml/.gitignore
@@ -0,0 +1,5 @@
+model_explorer
+simple_linear
+mnist
+*.log
+*.trs
diff --git a/platform/linux-generic/example/ml/Makefile.am b/platform/linux-generic/example/ml/Makefile.am
new file mode 100644
index 000000000..3692b704e
--- /dev/null
+++ b/platform/linux-generic/example/ml/Makefile.am
@@ -0,0 +1,46 @@
+include $(top_srcdir)/example/Makefile.inc
+
+LDADD += -lm
+
+bin_PROGRAMS = model_explorer simple_linear mnist
+
+simple_linear_SOURCES = simple_linear.c model_read.c model_read.h
+model_explorer_SOURCES = model_explorer.c model_read.c model_read.h
+mnist_SOURCES = mnist.c model_read.c model_read.h
+
+EXTRA_DIST = \
+ odp_ml_run_mnist.sh \
+ example_digit.csv \
+ mnist-12.onnx \
+ odp_ml_run_model_explorer.sh \
+ odp_ml_run_simple_linear.sh \
+ simple_linear.onnx \
+ README.md
+
+if test_example
+TESTS = \
+ odp_ml_run_mnist.sh \
+ odp_ml_run_model_explorer.sh \
+ odp_ml_run_simple_linear.sh
+endif
+
+# If building out-of-tree, make check will not copy the scripts and data to the
+# $(builddir) assuming that all commands are run locally. However this prevents
+# running tests on a remote target using LOG_COMPILER.
+# So copy all script and data files explicitly here.
+all-local:
+ if [ "x$(srcdir)" != "x$(builddir)" ]; then \
+ for f in $(EXTRA_DIST); do \
+ if [ -e $(srcdir)/$$f ]; then \
+ mkdir -p $(builddir)/$$(dirname $$f); \
+ cp -f $(srcdir)/$$f $(builddir)/$$f; \
+ fi \
+ done \
+ fi
+
+clean-local:
+ if [ "x$(srcdir)" != "x$(builddir)" ]; then \
+ for f in $(EXTRA_DIST); do \
+ rm -f $(builddir)/$$f; \
+ done \
+ fi
diff --git a/platform/linux-generic/example/ml/README.md b/platform/linux-generic/example/ml/README.md
new file mode 100644
index 000000000..fc6a57c0a
--- /dev/null
+++ b/platform/linux-generic/example/ml/README.md
@@ -0,0 +1,94 @@
+# ML examples
+
+Machine Learning API examples demonstrate how to use ODP ML API in different tasks:
+for example simple linear computation and predicting a handwritten digit in
+a given image.
+
+## Simple Linear
+
+This example runs on a very simple model of form y = 3 * x + 4 where x is given
+as the second argument.
+
+### Generate model
+
+```bash
+python3 <odp_directory>/platform/linux-generic/test/validation/api/ml/simple_linear_gen.py
+```
+
+### Run simple linear
+
+```bash
+$ ./simple_linear 3
+.
+.
+.
+y = 3 * 3 + 4: 13
+.
+```
+
+Or run the program with multiple threads, each thread inferences on one x given in
+the input. Thus, the number of threads is the number of numbers in the second argument.
+
+```bash
+$ ./simple_linear [2,4,5]
+.
+.
+.
+y = 3 * 2 + 4: 10
+y = 3 * 5 + 4: 19
+y = 3 * 4 + 4: 16
+.
+```
+
+## MNIST
+
+This example predicts a handwritten digit in a given image. Refer to
+https://github.com/onnx/models/tree/main/validated/vision/classification/mnist
+for more information. The model file is from
+https://github.com/onnx/models/raw/main/validated/vision/classification/mnist/model/mnist-12.onnx
+(SPDX-License-Identifier: MIT).
+
+### Prepare input data
+
+The input image is stored in a csv file which contains, comma separated, the
+digit label (a number from 0 to 9) and the 784 pixel values (a number from 0 to
+255). Pixel order is left to right and then top down. The MNIST dataset is
+available in this format at https://www.kaggle.com/oddrationale/mnist-in-csv.
+
+### Run mnist
+
+```bash
+$ ./mnist mnist-12.onnx example_digit.csv
+.
+.
+.
+predicted_digit: 4, expected_digit: 4
+.
+```
+
+## Model Explorer
+
+The example prints basic model information.
+
+### Run model_explorer
+
+```bash
+$ ./model_explorer simple_linear.onnx
+.
+.
+.
+Model info
+----------
+ Model handle: 0x7fe8426ce1d8
+ Name: model-explorer
+ Model version: 1
+ Model interface version: 0
+ Index: 0
+ Number of inputs: 1
+ Input[0]: Name: x, Data_type: int32, Shape: static [1], Size: 4
+ Number of outputs: 1
+ Output[0]: Name: y, Data_type: int32, Shape: static [1], Size: 4
+.
+.
+.
+```
diff --git a/platform/linux-generic/example/ml/example_digit.csv b/platform/linux-generic/example/ml/example_digit.csv
new file mode 100644
index 000000000..2ab0f4a0c
--- /dev/null
+++ b/platform/linux-generic/example/ml/example_digit.csv
@@ -0,0 +1 @@
+4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,8,55,9,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,36,215,98,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,36,249,144,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,34,246,148,0,0,0,0,0,0,0,7,8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,39,255,139,0,0,0,0,0,0,2,95,117,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,51,255,97,0,0,0,0,0,0,8,203,211,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,255,58,0,0,0,0,0,0,13,238,167,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,111,255,23,0,0,0,0,0,0,24,255,110,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,4,209,222,1,0,0,0,0,0,0,62,255,51,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,55,255,125,0,0,0,0,0,0,0,117,255,19,0,0,0,0,0,0,0,0,0,0,0,0,0,0,4,164,255,60,0,0,0,0,0,0,0,171,230,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,24,235,255,178,120,89,74,72,72,72,74,246,241,121,141,153,148,83,1,0,0,0,0,0,0,0,0,0,6,121,231,255,255,255,255,255,255,255,255,255,255,255,255,255,253,173,14,0,0,0,0,0,0,0,0,0,0,1,19,44,63,76,83,83,83,83,100,255,192,66,52,45,46,34,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,39,255,138,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,68,255,113,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,104,255,84,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,147,255,52,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,8,190,255,23,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,25,229,210,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,50,255,117,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,91,255,34,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,49,120,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
diff --git a/platform/linux-generic/example/ml/mnist-12.onnx b/platform/linux-generic/example/ml/mnist-12.onnx
new file mode 100644
index 000000000..6661bfe3c
--- /dev/null
+++ b/platform/linux-generic/example/ml/mnist-12.onnx
Binary files differ
diff --git a/platform/linux-generic/example/ml/mnist.c b/platform/linux-generic/example/ml/mnist.c
new file mode 100644
index 000000000..4c1066302
--- /dev/null
+++ b/platform/linux-generic/example/ml/mnist.c
@@ -0,0 +1,300 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright (c) 2023 Nokia
+ */
+
+#include <odp_api.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+#include <inttypes.h>
+
+#include "model_read.h"
+
+/**
+ * About MNIST model used in this example.
+ *
+ * The model predicts handwritten digits. It has one input and one output whose
+ * detailed information is as follows:
+ *
+ * Input:
+ * Name: Input3, type: float32, shape: [1, 1, 28, 28]
+ *
+ * Output:
+ * Name: Plus214_Output_0, type: float32, shape: [1, 10]
+ *
+ * Refer https://github.com/onnx/models/tree/main/validated/vision/classification/mnist
+ * for more information about the model.
+ *
+ * The model outputs the likelihood of each number before softmax, so we need to
+ * map the output to probabilities across the 10 classes with softmax function.
+ *
+ * In this example, the input image is stored in example_digit.csv file, which
+ * contains, comma separated, the digit label (a number from 0 to 9) and the 784
+ * pixel values (a number from 0 to 255). Pixel order is first left to right and
+ * then top down. The MNIST dataset is available in this format at
+ * https://www.kaggle.com/oddrationale/mnist-in-csv.
+ */
+
+#define MAX_MODEL_SIZE 30000
+#define INPUT_NUM_ELEMS 784 /* Total shape for input: 1 * 1 * 28 * 28 */
+#define OUTPUT_NUM_ELEMS 10 /* Total shape for output: 1 * 10 */
+
+static int read_digit_csv(const char *file_name, uint8_t *expected_digit, float *pixels)
+{
+ char *tmp;
+ char *token;
+ char *end;
+ FILE *digit_file;
+ size_t size, num_elem;
+ const char *delim = ","; /* Delimiter */
+ size_t num_pixel = 0;
+
+ /* Get the model file size in bytes */
+ digit_file = fopen(file_name, "rb");
+ fseek(digit_file, 0, SEEK_END);
+ size = ftell(digit_file);
+ rewind(digit_file);
+
+ tmp = malloc(size);
+ memset(tmp, 0, size);
+ num_elem = fread(tmp, size, 1, digit_file);
+
+ fclose(digit_file);
+ if (num_elem != 1) {
+ printf("Read digit file failed\n");
+ free(tmp);
+ return -1;
+ }
+
+ /* Get the first token which is the expected digit */
+ token = strtok(tmp, delim);
+ *expected_digit = (uint8_t)strtol(token, &end, 10);
+ if ((*expected_digit > 9) || (end == token)/*No numeric character*/) {
+ printf("Invalid digit %u or no numeric character available\n",
+ *expected_digit);
+ free(tmp);
+ return -1;
+ }
+
+ /* The rest 784 numbers are pixel values */
+ token = strtok(NULL, delim);
+ while (token != NULL) {
+ pixels[num_pixel] = strtof(token, NULL);
+ num_pixel++;
+ token = strtok(NULL, delim);
+ }
+
+ if (num_pixel != INPUT_NUM_ELEMS) {
+ printf("Wrong number of pixels: %zu (expected:784)\n", num_pixel);
+ free(tmp);
+ return -1;
+ }
+
+ free(tmp);
+ return 0;
+}
+
+static int prepare_run_params(const char *file_name, uint8_t *expected_digit,
+ odp_ml_data_seg_t *input, odp_ml_data_seg_t *output)
+{
+ input->size = INPUT_NUM_ELEMS * sizeof(float);
+ input->addr = malloc(input->size);
+ memset(input->addr, 0, input->size);
+
+ if (read_digit_csv(file_name, expected_digit, input->addr)) {
+ free(input->addr);
+ return -1;
+ }
+
+ output->size = OUTPUT_NUM_ELEMS * sizeof(float);
+ output->addr = malloc(output->size);
+ memset(output->addr, 0, output->size);
+
+ return 0;
+}
+
+static float array_max(float *arr, uint8_t arr_len)
+{
+ float max = arr[0];
+
+ for (size_t i = 1; i < arr_len; i++) {
+ if (arr[i] > max)
+ max = arr[i];
+ }
+
+ return max;
+}
+
+static void softmax(float *input, uint8_t input_len)
+{
+ float rowmax = array_max(input, input_len);
+
+ float input_exp[input_len];
+ float sum = 0.0f;
+
+ for (size_t i = 0; i != input_len; ++i) {
+ input_exp[i] = exp(input[i] - rowmax);
+ sum += input_exp[i];
+ }
+
+ for (size_t i = 0; i != input_len; ++i)
+ input[i] = input_exp[i] / sum;
+}
+
+static uint8_t index_of_max(float *arr, uint8_t arr_len)
+{
+ uint8_t i = 0;
+ uint8_t max_index = 0;
+ float max = arr[0];
+
+ for (i = 1; i < arr_len; i++) {
+ if (arr[i] > max) {
+ max = arr[i];
+ max_index = i;
+ }
+ }
+
+ return max_index;
+}
+
+int main(int argc, char *argv[])
+{
+ const char *model_file;
+ const char *input_file;
+ float *probabilities;
+ uint8_t expected_digit;
+ uint8_t predicted_digit;
+ odp_instance_t inst;
+ odp_ml_data_t data;
+ odp_ml_model_t ml_model;
+ odp_ml_data_seg_t input;
+ odp_ml_data_seg_t output;
+ odp_ml_capability_t capa;
+ odp_ml_config_t ml_config;
+ odp_ml_model_param_t model_param;
+ int ret = 0;
+
+ if (argc != 3) {
+ printf("Please provide an input image file for classification.\n"
+ "\nUsage:\n"
+ " %s model_file input_image\n"
+ "\nThis example classifies digit written on the input image.\n\n",
+ argv[0]);
+ return -1;
+ }
+
+ model_file = argv[1];
+ input_file = argv[2];
+
+ if (odp_init_global(&inst, NULL, NULL)) {
+ printf("Global init failed.\n");
+ return -1;
+ }
+
+ if (odp_init_local(inst, ODP_THREAD_CONTROL)) {
+ printf("Local init failed.\n");
+ return -1;
+ }
+
+ if (odp_ml_capability(&capa)) {
+ printf("odp_ml_capability() failed\n");
+ ret = -1;
+ goto odp_term;
+ }
+
+ if (MAX_MODEL_SIZE > capa.max_model_size) {
+ printf("Configured max model size %d exceeds max mode size %" PRIu64 " in capa\n",
+ MAX_MODEL_SIZE, capa.max_model_size);
+ ret = -1;
+ goto odp_term;
+ }
+
+ odp_ml_config_init(&ml_config);
+ ml_config.max_model_size = MAX_MODEL_SIZE;
+ ml_config.load_mode_mask = ODP_ML_COMPL_MODE_SYNC;
+ ml_config.run_mode_mask = ODP_ML_COMPL_MODE_SYNC;
+
+ if (odp_ml_config(&ml_config)) {
+ printf("odp_ml_config() failed\n");
+ ret = -1;
+ goto odp_term;
+ }
+
+ odp_ml_model_param_init(&model_param);
+ if (read_model_from_file(model_file, &model_param)) {
+ printf("Read model file failed\n");
+ ret = -1;
+ goto odp_term;
+ }
+
+ ml_model = odp_ml_model_create("mnist", &model_param);
+ free(model_param.model);
+ if (ml_model == ODP_ML_MODEL_INVALID) {
+ printf("odp_ml_model_create() failed\n");
+ ret = -1;
+ goto odp_term;
+ }
+
+ odp_ml_model_print(ml_model);
+
+ if (odp_ml_model_load(ml_model, NULL)) {
+ printf("odp_ml_model_load() failed\n");
+ ret = -1;
+ goto destroy_model;
+ }
+
+ data.num_input_seg = 1;
+ data.num_output_seg = 1;
+ data.input_seg = &input;
+ data.output_seg = &output;
+ if (prepare_run_params(input_file, &expected_digit, &input, &output)) {
+ printf("prepare_run_params() failed\n");
+ ret = -1;
+ goto unload;
+ }
+
+ if (odp_ml_run(ml_model, &data, NULL) != 1) {
+ printf("odp_ml_model_run() failed\n");
+ ret = -1;
+ goto free_model_io;
+ }
+
+ probabilities = output.addr;
+
+ /* Post-process the model output */
+ softmax(probabilities, OUTPUT_NUM_ELEMS);
+ predicted_digit = index_of_max(probabilities, OUTPUT_NUM_ELEMS);
+ printf("predicted_digit: %u, expected_digit: %u\n", predicted_digit, expected_digit);
+
+free_model_io:
+ free(input.addr);
+ free(output.addr);
+
+unload:
+ if (odp_ml_model_unload(ml_model, NULL)) {
+ printf("odp_ml_model_unload() failed\n");
+ ret = -1;
+ goto odp_term;
+ }
+
+destroy_model:
+ /* Destroy the model */
+ if (odp_ml_model_destroy(ml_model)) {
+ printf("odp_ml_model_destroy() failed\n");
+ ret = -1;
+ }
+
+odp_term:
+ if (odp_term_local()) {
+ printf("Local term failed.\n");
+ return -1;
+ }
+
+ if (odp_term_global(inst)) {
+ printf("Global term failed.\n");
+ return -1;
+ }
+
+ return ret;
+}
diff --git a/platform/linux-generic/example/ml/model_explorer.c b/platform/linux-generic/example/ml/model_explorer.c
new file mode 100644
index 000000000..bd449b032
--- /dev/null
+++ b/platform/linux-generic/example/ml/model_explorer.c
@@ -0,0 +1,88 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright (c) 2023 Nokia
+ */
+
+#include <odp_api.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "model_read.h"
+
+/**
+ * Read basic model information, e.g. inputs/outputs.
+ */
+
+int main(int argc, char *argv[])
+{
+ odp_instance_t inst;
+ odp_ml_model_t ml_model;
+ odp_ml_capability_t capa;
+ odp_ml_config_t ml_config;
+ odp_ml_model_param_t model_param;
+ int ret = 0;
+
+ if (argc != 2) {
+ printf("Please specify model path\n"
+ "\nUsage:\n"
+ " %s model_path\n"
+ "\nThis example prints model information\n\n",
+ argv[0]);
+ return -1;
+ }
+
+ if (odp_init_global(&inst, NULL, NULL)) {
+ printf("Global init failed.\n");
+ return -1;
+ }
+
+ if (odp_init_local(inst, ODP_THREAD_CONTROL)) {
+ printf("Local init failed.\n");
+ return -1;
+ }
+
+ if (odp_ml_capability(&capa)) {
+ printf("odp_ml_capability() failed\n");
+ ret = -1;
+ goto odp_term;
+ }
+
+ odp_ml_config_init(&ml_config);
+ ml_config.max_model_size = capa.max_model_size;
+ ml_config.load_mode_mask = ODP_ML_COMPL_MODE_SYNC;
+ ml_config.run_mode_mask = ODP_ML_COMPL_MODE_SYNC;
+
+ if (odp_ml_config(&ml_config)) {
+ printf("odp_ml_config() failed\n");
+ ret = -1;
+ goto odp_term;
+ }
+
+ odp_ml_model_param_init(&model_param);
+ if (read_model_from_file(argv[1], &model_param)) {
+ ret = -1;
+ goto odp_term;
+ }
+
+ ml_model = odp_ml_model_create("model-explorer", &model_param);
+ free(model_param.model);
+ if (ml_model == ODP_ML_MODEL_INVALID) {
+ printf("odp_ml_model_create failed.\n");
+ ret = -1;
+ goto odp_term;
+ }
+
+ odp_ml_model_print(ml_model);
+
+odp_term:
+ if (odp_term_local()) {
+ printf("Local term failed.\n");
+ return -1;
+ }
+
+ if (odp_term_global(inst)) {
+ printf("Global term failed.\n");
+ return -1;
+ }
+
+ return ret;
+}
diff --git a/platform/linux-generic/example/ml/model_read.c b/platform/linux-generic/example/ml/model_read.c
new file mode 100644
index 000000000..7aa20bf35
--- /dev/null
+++ b/platform/linux-generic/example/ml/model_read.c
@@ -0,0 +1,47 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright (c) 2023 Nokia
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <odp_api.h>
+
+#include "model_read.h"
+
+int read_model_from_file(const char *file_name, odp_ml_model_param_t *model_param)
+{
+ FILE *model_file;
+ /* Number of elements successfully read */
+ size_t num_elem;
+
+ /* Get the model file size in bytes */
+ model_file = fopen(file_name, "rb");
+ if (model_file == NULL) {
+ perror("Failed to open model file");
+ return -1;
+ }
+
+ fseek(model_file, 0, SEEK_END);
+ model_param->size = ftell(model_file);
+ rewind(model_file);
+
+ /* Allocate memory for model buffer */
+ model_param->model = malloc(model_param->size);
+ memset(model_param->model, 0, model_param->size);
+ if (!model_param->model) {
+ printf("Allocating memory for model buffer failed\n");
+ return -1;
+ }
+
+ /* Read the model file */
+ num_elem = fread(model_param->model, model_param->size, 1, model_file);
+ fclose(model_file);
+ if (num_elem != 1) {
+ printf("Read model file failed\n");
+ free(model_param->model);
+ return -1;
+ }
+
+ return 0;
+}
diff --git a/platform/linux-generic/example/ml/model_read.h b/platform/linux-generic/example/ml/model_read.h
new file mode 100644
index 000000000..df2062d5f
--- /dev/null
+++ b/platform/linux-generic/example/ml/model_read.h
@@ -0,0 +1,29 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright (c) 2023 Nokia
+ */
+
+#ifndef ODP_MODEL_READ_H_
+#define ODP_MODEL_READ_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <odp_api.h>
+
+/**
+ * Read model binaries from model file
+ *
+ * @param file_name The name of model file
+ * @param model_param Model parameter where model content and size are read to
+ *
+ * @retval 0 on success
+ * @retval < 0 on failure
+ */
+int read_model_from_file(const char *file_name, odp_ml_model_param_t *model_param);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/platform/linux-generic/example/ml/odp_ml_run_mnist.sh b/platform/linux-generic/example/ml/odp_ml_run_mnist.sh
new file mode 100755
index 000000000..f83d6f60d
--- /dev/null
+++ b/platform/linux-generic/example/ml/odp_ml_run_mnist.sh
@@ -0,0 +1,9 @@
+#!/bin/bash
+#
+# SPDX-License-Identifier: BSD-3-Clause
+# Copyright (c) 2023 Nokia
+#
+set -e
+
+# wget https://github.com/onnx/models/raw/main/validated/vision/classification/mnist/model/mnist-12.onnx
+./mnist${EXEEXT} mnist-12.onnx example_digit.csv
diff --git a/platform/linux-generic/example/ml/odp_ml_run_model_explorer.sh b/platform/linux-generic/example/ml/odp_ml_run_model_explorer.sh
new file mode 100755
index 000000000..7f9fed5a6
--- /dev/null
+++ b/platform/linux-generic/example/ml/odp_ml_run_model_explorer.sh
@@ -0,0 +1,8 @@
+#!/bin/bash
+#
+# SPDX-License-Identifier: BSD-3-Clause
+# Copyright (c) 2023 Nokia
+#
+set -e
+
+./model_explorer${EXEEXT} simple_linear.onnx
diff --git a/platform/linux-generic/example/ml/odp_ml_run_simple_linear.sh b/platform/linux-generic/example/ml/odp_ml_run_simple_linear.sh
new file mode 100755
index 000000000..b394b61a8
--- /dev/null
+++ b/platform/linux-generic/example/ml/odp_ml_run_simple_linear.sh
@@ -0,0 +1,8 @@
+#!/bin/bash
+#
+# SPDX-License-Identifier: BSD-3-Clause
+# Copyright (c) 2023 Nokia
+#
+set -e
+
+./simple_linear${EXEEXT} [2,4,5]
diff --git a/platform/linux-generic/example/ml/simple_linear.c b/platform/linux-generic/example/ml/simple_linear.c
new file mode 100644
index 000000000..3417219c7
--- /dev/null
+++ b/platform/linux-generic/example/ml/simple_linear.c
@@ -0,0 +1,281 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright (c) 2023 Nokia
+ */
+
+#include <odp_api.h>
+#include <odp/helper/odph_api.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <inttypes.h>
+
+#include "model_read.h"
+
+/**
+ * About model simple_linear.onnx used in this example.
+ *
+ * Model info:
+ * Inputs: name: x, type: int32, shape: [1]
+ * Outputs: name: y, type: int32, shape: [1]
+ *
+ * The model is of form y = 3 * x + 4 where x is given as the second argument.
+ * Thus when x = 5, the output y should be 19.
+ */
+
+#define NUM_INPUTS 1
+#define NUM_OUTPUTS 1
+#define MAX_NUM_WORKERS 10
+#define MAX_MODEL_SIZE 500
+
+typedef struct infer_param_t {
+ int32_t x;
+ odp_ml_model_t ml_model;
+} infer_param_t;
+
+typedef struct {
+ odp_shm_t shm;
+ /* Thread specific arguments */
+ infer_param_t infer_param[MAX_NUM_WORKERS];
+} thread_args_t;
+
+/* Global pointer to thread_args */
+static thread_args_t *thread_args;
+
+static int run_inference(void *infer_param)
+{
+ int32_t y;
+ odp_ml_data_t data;
+ odp_ml_data_seg_t input;
+ odp_ml_data_seg_t output;
+ infer_param_t *param = (infer_param_t *)infer_param;
+
+ data.num_input_seg = NUM_INPUTS;
+ data.input_seg = &input;
+ input.addr = &param->x;
+ input.size = sizeof(int32_t);
+
+ data.num_output_seg = NUM_OUTPUTS;
+ data.output_seg = &output;
+ output.addr = &y;
+ output.size = sizeof(int32_t);
+
+ while (1) {
+ int ret = odp_ml_run(param->ml_model, &data, NULL);
+
+ if (ret == 1)
+ break;
+
+ if (ret < 0) {
+ ODPH_ERR("odp_ml_model_run() failed: %d\n", ret);
+ return -1;
+ }
+ }
+
+ printf("y = 3 * %d + 4: %d\n", param->x, y);
+
+ return 0;
+}
+
+static int parse_argv1(char *argv1, uint32_t *num, int32_t *x)
+{
+ char *token;
+ int i;
+
+ if (!strstr(argv1, "[")) {
+ *num = 1;
+ *x = strtol(argv1, NULL, 10);
+ return 0;
+ }
+
+ token = strtok(argv1, "[,]");
+ if (token == NULL) {
+ ODPH_ERR("Invalid argv[1]\n");
+ return -1;
+ }
+ x[0] = strtol(token, NULL, 10);
+
+ for (i = 0; i < MAX_NUM_WORKERS; i++) {
+ token = strtok(NULL, "[,]");
+ if (token == NULL)
+ break;
+
+ x[i + 1] = strtol(token, NULL, 10);
+ }
+
+ if (i == MAX_NUM_WORKERS) {
+ ODPH_ERR("Too much xs, maximum number is: %d\n", MAX_NUM_WORKERS);
+ return -1;
+ }
+
+ *num = i + 1;
+ return 0;
+}
+
+int main(int argc, char *argv[])
+{
+ odp_shm_t shm;
+ int num_workers;
+ odp_instance_t inst;
+ odp_cpumask_t cpumask;
+ odp_ml_model_t ml_model;
+ odp_ml_capability_t capa;
+ odp_ml_config_t ml_config;
+ int32_t x[MAX_NUM_WORKERS];
+ odp_ml_model_param_t model_param;
+ odph_thread_t thread_tbl[MAX_NUM_WORKERS];
+ odph_thread_common_param_t thr_common;
+ odph_thread_param_t thr_param[MAX_NUM_WORKERS];
+ char cpumaskstr[ODP_CPUMASK_STR_SIZE];
+ int ret = 0;
+ uint32_t num = 0;
+
+ if (argc != 2) {
+ ODPH_ERR("Please specify x\n"
+ "\nUsage:\n"
+ " %s x\n"
+ "\nThis example runs inference on model y = 3x + 4\n\n",
+ argv[0]);
+ return -1;
+ }
+
+ if (parse_argv1(argv[1], &num, x))
+ return -1;
+
+ if (odp_init_global(&inst, NULL, NULL)) {
+ ODPH_ERR("Global init failed.\n");
+ return -1;
+ }
+
+ if (odp_init_local(inst, ODP_THREAD_CONTROL)) {
+ ODPH_ERR("Local init failed.\n");
+ return -1;
+ }
+
+ if (odp_ml_capability(&capa)) {
+ ODPH_ERR("odp_ml_capability() failed\n");
+ ret = -1;
+ goto odp_term;
+ }
+
+ if (MAX_MODEL_SIZE > capa.max_model_size) {
+ ODPH_ERR("Configured max model size %d exceeds max mode size %" PRIu64 " in capa\n",
+ MAX_MODEL_SIZE, capa.max_model_size);
+ ret = -1;
+ goto odp_term;
+ }
+
+ /* Set ML configuration parameter */
+ odp_ml_config_init(&ml_config);
+ ml_config.max_model_size = MAX_MODEL_SIZE;
+ ml_config.load_mode_mask = ODP_ML_COMPL_MODE_SYNC;
+ ml_config.run_mode_mask = ODP_ML_COMPL_MODE_SYNC;
+
+ if (odp_ml_config(&ml_config)) {
+ ODPH_ERR("odp_ml_config() failed\n");
+ ret = -1;
+ goto odp_term;
+ }
+
+ odp_ml_model_param_init(&model_param);
+ if (read_model_from_file("simple_linear.onnx", &model_param)) {
+ ret = -1;
+ goto odp_term;
+ }
+
+ ml_model = odp_ml_model_create("simple linear", &model_param);
+ free(model_param.model);
+ if (ml_model == ODP_ML_MODEL_INVALID) {
+ ODPH_ERR("odp_ml_model_create() failed\n");
+ ret = -1;
+ goto odp_term;
+ }
+
+ odp_ml_model_print(ml_model);
+ odp_ml_print();
+
+ if (odp_ml_model_load(ml_model, NULL)) {
+ ODPH_ERR("odp_ml_model_load() failed\n");
+ ret = -1;
+ goto destroy_model;
+ }
+
+ /* Reserve memory for args from shared mem */
+ shm = odp_shm_reserve("_thread_args", sizeof(thread_args_t),
+ ODP_CACHE_LINE_SIZE, 0);
+ if (shm == ODP_SHM_INVALID) {
+ ODPH_ERR("Error: shared mem reserve failed.\n");
+ ret = -1;
+ goto unload;
+ }
+
+ thread_args = odp_shm_addr(shm);
+ if (thread_args == NULL) {
+ ODPH_ERR("Error: shared mem alloc failed.\n");
+ ret = -1;
+ goto free_shm;
+ }
+ thread_args->shm = shm;
+ memset(thread_args, 0, sizeof(thread_args_t));
+
+ /* Prepare inference parameter */
+ for (uint32_t i = 0; i < num; i++) {
+ thread_args->infer_param[i].x = x[i];
+ thread_args->infer_param[i].ml_model = ml_model;
+ }
+
+ num_workers = odp_cpumask_default_worker(&cpumask, num);
+ (void)odp_cpumask_to_str(&cpumask, cpumaskstr, sizeof(cpumaskstr));
+
+ printf("num worker threads: %i\n", num_workers);
+ printf("first CPU: %i\n", odp_cpumask_first(&cpumask));
+ printf("cpu mask: %s\n", cpumaskstr);
+
+ /* Create and init worker threads */
+ memset(thread_tbl, 0, sizeof(thread_tbl));
+ odph_thread_common_param_init(&thr_common);
+ thr_common.instance = inst;
+ thr_common.cpumask = &cpumask;
+
+ for (int i = 0; i < num_workers; ++i) {
+ odph_thread_param_init(&thr_param[i]);
+ thr_param[i].start = run_inference;
+ thr_param[i].arg = &thread_args->infer_param[i];
+ thr_param[i].thr_type = ODP_THREAD_WORKER;
+ }
+
+ odph_thread_create(thread_tbl, &thr_common, thr_param, num_workers);
+
+ odph_thread_join(thread_tbl, num_workers);
+
+free_shm:
+ if (odp_shm_free(shm)) {
+ ODPH_ERR("Error: shm free global data\n");
+ return -1;
+ }
+
+unload:
+ /* Unload a model */
+ if (odp_ml_model_unload(ml_model, NULL)) {
+ ODPH_ERR("odp_ml_model_load() failed\n");
+ ret = -1;
+ }
+
+destroy_model:
+ if (odp_ml_model_destroy(ml_model)) {
+ ODPH_ERR("odp_ml_model_destroy() failed\n");
+ ret = -1;
+ }
+
+odp_term:
+ if (odp_term_local()) {
+ ODPH_ERR("Local term failed.\n");
+ return -1;
+ }
+
+ if (odp_term_global(inst)) {
+ ODPH_ERR("Global term failed.\n");
+ return -1;
+ }
+
+ return ret;
+}
diff --git a/platform/linux-generic/example/ml/simple_linear.onnx b/platform/linux-generic/example/ml/simple_linear.onnx
new file mode 100644
index 000000000..45c4b95b9
--- /dev/null
+++ b/platform/linux-generic/example/ml/simple_linear.onnx
Binary files differ