Skip to main content

Embed a Model Inside a Graph

Embed a Model Inside a Graph — animated walkthrough overview

FieldValue
DifficultyAdvanced
Estimated Read Time20-25 minutes
Labelsgraph, hybrid, model, mpk

Chapter 003 built a graph out of bare input/output nodes; chapter 001 ran a model as a standalone object. This chapter joins the two: a Model is itself a graph-compatible node, so you can compose it into a public Graph exactly like any other stage. That is the bridge pattern production systems reach for when they need graph-level control — multiple inputs, named outputs, custom routing — while still treating model execution as one reusable fragment.

The key idea is that you never touch the low-level runtime graph, StageModelExecutorOptions, or internal node IDs. You hand the model to graph.add(...), and NEAT lowers that fragment (preprocess / inference / postprocess as needed) into the correct internal execution plan at build time. By the end you will have composed a model into a public graph, printed the composed topology, and read back the model's output cardinality.

Walkthrough

Load the model

Construction loads the compiled archive and prepares it for execution, just as in chapter 001. Here we take only the path — no options object — because this chapter is about composition, not preprocessing. The resulting Model is now an object the graph layer understands.

tutorials/014_embed_model_inside_graph/embed_model_inside_graph.cpp
simaai::neat::Model model(model_path);

Compose the model into a graph

This is the whole point of the chapter. A fresh Graph gets three nodes added in order: a named input boundary, the model itself, and a named output boundary. Because Model is graph-compatible, add(model) appends the entire model route as a single fragment — there is no special API and no reaching into the runtime. Printing graph.describe() shows the composed topology so you can confirm the model slotted in between the named boundaries.

Boundaries come from simaai::neat::nodes::Input("image") and nodes::Output("result"); the model is passed directly to graph.add(model).

tutorials/014_embed_model_inside_graph/embed_model_inside_graph.cpp
simaai::neat::Graph graph;
graph.add(simaai::neat::nodes::Input("image"));
graph.add(model);
graph.add(simaai::neat::nodes::Output("result"));

std::cout << graph.describe() << "\n";

Inspect the model

Finally we read back what the model fragment actually contributes. This confirms the model loaded correctly and lets you see the output topology the graph will produce downstream.

model.info() returns an info struct; we print model_name plus output_topology.physical_outputs and logical_outputs so the wiring of the model's outputs is explicit.

tutorials/014_embed_model_inside_graph/embed_model_inside_graph.cpp
const auto info = model.info();
std::cout << "model=" << (info.model_name.empty() ? "<unnamed>" : info.model_name)
<< " physical_outputs=" << info.output_topology.physical_outputs
<< " logical_outputs=" << info.output_topology.logical_outputs << "\n";

Run

This chapter needs a model archive (yolo_v8s). 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_014_embed_model_inside_graph \
--model /tmp/yolo_v8s.tar.gz

C++ (build from source):

./build.sh --target tutorial_014_embed_model_inside_graph
./build/tutorials-standalone/tutorial_014_embed_model_inside_graph \
--model /tmp/yolo_v8s.tar.gz

Expected output (the C++ build also prints the composed graph description first):

model=yolo_v8s physical_outputs=1 logical_outputs=1
[OK] 014_embed_model_inside_graph

(The Python build prints the graph description followed by model fragment added to public Graph.)

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.

Full source

Show the complete C++ and Python programs
tutorials/014_embed_model_inside_graph/embed_model_inside_graph.cpp
// Hybrid graph composition: a Model is added directly to a public Graph.
//
// Usage:
// tutorial_014_embed_model_inside_graph --model /path/to/model.tar.gz

#include "neat.h"

#include <iostream>
#include <string>

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;
}

} // namespace

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

simaai::neat::Model model(model_path);

// CORE LOGIC
// Model is now a Graph-compatible object. `graph.add(model)` appends the
// model route fragment (preprocess/inference/postprocess as needed) without
// exposing the internal low-level runtime graph.
simaai::neat::Graph graph;
graph.add(simaai::neat::nodes::Input("image"));
graph.add(model);
graph.add(simaai::neat::nodes::Output("result"));

std::cout << graph.describe() << "\n";

const auto info = model.info();
std::cout << "model=" << (info.model_name.empty() ? "<unnamed>" : info.model_name)
<< " physical_outputs=" << info.output_topology.physical_outputs
<< " logical_outputs=" << info.output_topology.logical_outputs << "\n";
std::cout << "[OK] 014_embed_model_inside_graph\n";
return 0;
} catch (const std::exception& e) {
std::cerr << "[FAIL] " << e.what() << "\n";
return 1;
}
}

Source