Skip to main content

Run Your First Model

Run Your First Model — animated walkthrough overview

FieldValue
DifficultyBeginner
Estimated Read Time<5 minutes
Labelsmodel, inference, foundations

This is the entry chapter. The goal is the smallest possible end-to-end inference: take a compiled model, hand it one image, and print the predicted class index. No graphs, no threads, no streaming — just the three calls that every Neat program is built on.

A compiled model is a deployable .tar.gz archive containing an MPK inference contract: the model artifacts plus the runtime metadata Neat needs to execute it on the target device. You don't unpack it or wire up stages yourself — you point Neat at the archive, give it input, and read the output. By the end you will have run inference in three lines and printed a top1= class index.

Walkthrough

Load the model

The first line turns a path-on-disk into a live, runnable Model: construction loads the archive and prepares it for execution.

You pass build_options(size) as a second argument to declare the input contract this model expects — RGB color, 224×224, and the ImageNet normalization ResNet-50 was trained with. Declaring it here tells the runtime how to turn a raw image into the tensor the model wants.

tutorials/001_run_your_first_model/run_your_first_model.cpp
simaai::neat::Model model(model_path, build_options(size));

Prepare the input

Next we produce exactly one image to classify. If you pass --image, it is read, resized to 224×224, and converted to RGB to match the input contract; otherwise we synthesize a solid gray frame so the full load → run → read path still runs end to end without needing an asset on hand.

The frame is a cv::Mat, produced by load_rgb(...) or as a gray placeholder.

tutorials/001_run_your_first_model/run_your_first_model.cpp
cv::Mat input = image.empty() ? cv::Mat(size, size, CV_8UC3, cv::Scalar(99, 99, 99))
: load_rgb(image, size);

Run inference and read the result

The third line does the actual work: run() takes the input and a timeout_ms, executes the model synchronously, and returns the output. timeout_ms is the maximum wall-clock time to wait — 2000 ms here means "fail loudly if the device hasn't produced output in two seconds" rather than hanging forever. (Passing -1 blocks indefinitely; prefer a finite value in real code.) We then reduce the output to a single class index with argmax and print top1=.

run() returns a TensorList; read the first tensor's bytes via map_read().

tutorials/001_run_your_first_model/run_your_first_model.cpp
simaai::neat::TensorList outputs = model.run(std::vector<cv::Mat>{input}, /*timeout_ms=*/2000);

Run

Run it and you should see the predicted class index printed to stdout. Run the Python and C++ (prebuilt) commands from the Neat install root (the directory that contains share/ and lib/); run the build from source commands from the repo root.

C++ (prebuilt):

./lib/sima-neat/tutorials/tutorial_001_run_your_first_model \
--model /tmp/resnet_50.tar.gz

C++ (build from source):

./build.sh --target tutorial_001_run_your_first_model
./build/tutorials-standalone/tutorial_001_run_your_first_model \
--model /tmp/resnet_50.tar.gz

Expected output (the exact index depends on the image):

top1=285
[OK] 001_run_your_first_model

To integrate this chapter's C++ source into your own project with a custom CMakeLists.txt (no extras folder required), see How to Run Tutorials on the landing page.

For throughput, batching, or live streams, continue to chapter 002. Reference: Model.

In Practice

Where the tutorials and tests look for model archives (.tar.gz) and sample assets, and how to provide them locally. This is the prerequisite for every model-backed tutorial.

Ensure sima-cli is on PATH

Some tests invoke sima-cli from non-interactive shells. Use this once after installing sima-cli:

SIMA_CLI_BIN_DIR="<path-to-sima-cli-bin>"
grep -Fqx "export PATH=\"${SIMA_CLI_BIN_DIR}:\$PATH\"" ~/.bashrc || echo "export PATH=\"${SIMA_CLI_BIN_DIR}:\$PATH\"" >> ~/.bashrc
source ~/.bashrc

Then verify:

/bin/sh -c 'command -v sima-cli'

Model archive locations and environment variables

Extraction/runtime placement knobs:

  • SIMA_MPK_EXTRACT_ROOT=<dir> sets the base extract directory.
  • SIMA_MPK_CLEANUP_EXTRACTED=0 preserves extracted proc_* model data after process exit.
  • SIMA_MPK_EXTRACT_GC_STALE_PROC=0 disables dead-proc_* cleanup on startup.

ResNet50

Search order:

  1. SIMA_RESNET50_TAR (per-model override)
  2. SIMA_MODEL_TAR (shared fallback for model-archive tests/examples)
  3. tmp/resnet_50.tar.gz
  4. Local files moved into tmp/ if found: resnet_50.tar.gz, resnet-50.tar.gz

Download (if sima-cli is available):

sima-cli modelzoo get resnet_50

Sample images

Default image candidates used in tutorials/tests:

  • tmp/coco_sample.jpg (downloaded if missing)
  • test.jpg
  • tests/assets/preproc_dynamic/ilena_488.jpg

You can override the COCO image URL used by tests with:

SIMA_COCO_URL=<custom_url>

Where tests download to

Tests and examples generally place downloaded assets under tmp/ in the repo root. Tutorials will skip gracefully if required assets are missing.

Troubleshooting assets

  • If a tutorial prints SKIP: missing ..., provide the asset or pass a flag (e.g., --model <path>, --image <path>).
  • If sima-cli is unavailable, set the env vars to point to local model archives.

Full source

Show the complete C++ and Python programs
tutorials/001_run_your_first_model/run_your_first_model.cpp
// Run a ResNet-50 model on an image in three lines of Neat.
//
// Usage:
// tutorial_001_run_your_first_model --model /path/to/resnet_50.tar.gz [--image /path/to.jpg]

#include "neat.h"

#include <opencv2/core.hpp>
#include <opencv2/imgcodecs.hpp>
#include <opencv2/imgproc.hpp>

#include <cstring>
#include <filesystem>
#include <iostream>
#include <stdexcept>
#include <string>

namespace fs = std::filesystem;

namespace {

bool get_arg(int argc, char** argv, const std::string& key, std::string& out) {
for (int i = 1; i + 1 < argc; ++i) {
if (key == argv[i]) {
out = argv[i + 1];
return true;
}
}
return false;
}

cv::Mat load_rgb(const fs::path& image_path, int size) {
cv::Mat bgr = cv::imread(image_path.string(), cv::IMREAD_COLOR);
if (bgr.empty())
throw std::runtime_error("failed to read image: " + image_path.string());
if (bgr.cols != size || bgr.rows != size) {
cv::resize(bgr, bgr, cv::Size(size, size), 0, 0, cv::INTER_AREA);
}
cv::Mat rgb;
cv::cvtColor(bgr, rgb, cv::COLOR_BGR2RGB);
if (!rgb.isContinuous())
rgb = rgb.clone();
return rgb;
}

simaai::neat::Model::Options build_options(int size) {
simaai::neat::Model::Options opt;
opt.preprocess.color_convert.input_format = simaai::neat::PreprocessColorFormat::RGB;
opt.preprocess.input_max_width = size;
opt.preprocess.input_max_height = size;
opt.preprocess.input_max_depth = 3;
opt.preprocess.normalize.mean = {0.485f, 0.456f, 0.406f};
opt.preprocess.normalize.stddev = {0.229f, 0.224f, 0.225f};
return opt;
}

int top1_from_output(const simaai::neat::TensorList& out) {
if (out.empty())
throw std::runtime_error("no tensor output");
const simaai::neat::Mapping m = out.front().map_read();
const size_t n = m.size_bytes / sizeof(float);
const float* p = reinterpret_cast<const float*>(m.data);
int best = 0;
for (size_t i = 1; i < n && i < 1000; ++i) {
if (p[i] > p[best])
best = static_cast<int>(i);
}
return best;
}

} // namespace

int main(int argc, char** argv) {
try {
std::string model_path, image;
if (!get_arg(argc, argv, "--model", model_path)) {
std::cerr << "Usage: tutorial_001_run_your_first_model --model <path> [--image <path>]\n";
return 1;
}
get_arg(argc, argv, "--image", image);

const int size = 224;

// CORE LOGIC
// The three-line Neat story:
simaai::neat::Model model(model_path, build_options(size));
cv::Mat input = image.empty() ? cv::Mat(size, size, CV_8UC3, cv::Scalar(99, 99, 99))
: load_rgb(image, size);
simaai::neat::TensorList outputs = model.run(std::vector<cv::Mat>{input}, /*timeout_ms=*/2000);

std::cout << "top1=" << top1_from_output(outputs) << "\n";
std::cout << "[OK] 001_run_your_first_model\n";
return 0;
} catch (const std::exception& e) {
std::cerr << "[FAIL] " << e.what() << "\n";
return 1;
}
}

Source