GitHub

Docs / Getting started / Hello Inference in 20 lines

Hello Inference in 20 linesv0.2.0

samples/hello_inference is the end-to-end smoke test of the runtime: it initializes SynapticOS, registers a model, loads it to the NPU, runs a single inference through the scheduler, and prints the top prediction. It runs unchanged on the FRDM-MCXN947 and on QEMU.

What the sample does

The sample exercises every layer of the runtime in order:

  1. syn_init() — bring up the runtime: tensor arena, NPU and DSP HALs, profiler.
  2. syn_model_register() — register a model named test_classify (v1.0.0) in the model registry and get back a handle.
  3. syn_hal_npu_load_model() — load the model binary into the NPU HAL, then mark it loaded with syn_model_load().
  4. syn_mem_tensor_alloc() — allocate a 1x16x16x3 INT8 input tensor (768 bytes) from the arena and fill it with a gradient test pattern.
  5. syn_infer_run_sync() — run one inference through the Phase 2 scheduler, timed with k_cycle_get_32(). The profiling marks fire at the stage boundaries, so syn prof last has real data afterwards. (The v0.1.0 sample drove the NPU HAL directly instead.)
  6. syn_hal_dsp_argmax() — find the top class in the output vector via the DSP HAL.
  7. syn_mem_print_stats() / syn_prof_print_summary() — dump memory and profiling statistics, then keep the shell alive.

The model binary is still a 64-byte placeholder and the 1x16x16x3 input size is chosen to fit both the QEMU stub (1 KB input limit) and the FRDM hardware.

The code

The essential path from samples/hello_inference/src/main.c, trimmed of error handling and logging:

c
#include <synaptic/syn_api.h>

syn_init();                                          /* 1. runtime up */

syn_model_info_t model_info = {0};                    /* 2. register model */
strncpy(model_info.name, "test_classify", sizeof(model_info.name));
strncpy(model_info.version, "1.0.0", sizeof(model_info.version));
syn_model_handle_t handle;
syn_model_register(&model_info, &handle);

syn_hal_npu_load_model(dummy_model, sizeof(dummy_model));  /* 3. load to NPU */
syn_model_load(handle);

uint32_t input_shape[] = {1, 16, 16, 3};              /* 4. input tensor */
syn_tensor_t *input = syn_mem_tensor_alloc(input_shape, 4,
                                           SYN_NPU_DTYPE_INT8,
                                           SYN_MEM_EPHEMERAL);

int8_t output_buf[256];                               /* 5. run inference */
syn_tensor_t output = {
    .data = output_buf,
    .size = sizeof(output_buf),
};
syn_infer_run_sync(handle, input, &output, SYN_PRIORITY_NORMAL);

uint32_t top_class = 0;                               /* 6. DSP argmax */
syn_hal_dsp_argmax(output_buf, output.size, &top_class);

The application side is minimal: prj.conf enables CONFIG_SYNAPTIC=y, CONFIG_SYNAPTIC_PROFILING=y, and CONFIG_SYNAPTIC_SHELL=y, and CMakeLists.txt links the app against the synaptic_os library.

Expected output

Captured on the FRDM-MCXN947 during the Phase 1 verification (v0.1.0 firmware, direct-HAL path):

serial
<inf> syn_model: Registered model 'test_classify' (handle=1)
<inf> hello_inference: Model loaded to NPU
<inf> hello_inference: Input tensor: 1x16x16x3 (768 bytes)
<inf> hello_inference: Inference completed in 1038 us
<inf> hello_inference: Prediction: class 0 (confidence 127)
<inf> hello_inference: === Hello Inference complete ===
<inf> hello_inference: Use 'syn' shell commands to inspect runtime.

Phase 1 end-to-end timing was 1038 us on the FRDM-MCXN947 versus 781 us on QEMU (direct-HAL path). The 257 us delta is dominated by the real MCU clock and memory bus versus QEMU's idealised model; the prediction itself is identical on both targets (class 0, confidence 127). In v0.2.0, where the same inference routes through the scheduler via syn_infer_run_sync(), QEMU measures 1361 us wall time against the same 781 us direct-HAL baseline — the difference is job submission, the scheduler thread, and completion signaling. On the FRDM board (2026-07-12 verification) the scheduler path measures 1130 us wall against the 1038 us Phase 1 direct-HAL capture: about 92 us for the full scheduler path on hardware, of which the dispatch overhead visible inside the profile is only ~1 us (total 1069 us vs 1068 us NPU). All NPU timings are the deterministic stub backend.

Why the output is deterministic

The NPU backend is a stub (the eIQ Neutron SDK is not yet integrated). On invoke, the stub produces a 10-class output vector by summing the input bytes and picking the winner as sum % 10, setting that class to 127 (maximum INT8 confidence) and all others to zero.

Because the sample fills its 768-byte input with the fixed gradient pattern data[i] = i & 0xFF, the byte sum is always the same, so every run — on either target — yields class 0 with confidence 127. This determinism is deliberate: it is what lets the CI suite assert exact inference results on QEMU.

Note · Backend status

The Neutron NPU driver is still a deterministic stub (the eIQ SDK integration lands in a later phase). The PowerQuad DSP driver, by contrast, drives real hardware as of v0.2.0: the FFT and Q15 matrix multiply run on the PowerQuad with boot-time self-calibration and software fallback — syn dsp bench measures 5.51× (FFT) and 1.66× (matmul) vs software on the board. The HAL API is the stable surface.