본문으로 건너뛰기

그래프

SiMa.ai의 Neat에서 Graph API는 애플리케이션을 구성하는 방법입니다. Graph는 입력부터 처리 노드를 거쳐 출력까지의 흐름을 설명합니다. 머신 러닝(ML) 분야에서 오셨다면, Graph를 모델을 중심으로 하는 앱을 위한 작은 모델 그래프로 생각하시면 됩니다.

  • 프레임, 텐서 또는 샘플이 입력을 통해 들어오고 출력을 통해 나갑니다. 처리 과정은 디코딩, 크기 조정, 전처리, 추론, 후처리, 분기 및 사용자 지정 로직과 같은 노드를 통해 중간 단계에서 수행됩니다.

하나의 Graph 경계 안에 있는 입력, 처리 단계 및 출력

  • 그래프는 독립적으로 실행하거나 더 큰 그래프 내에서 재사용할 수 있습니다.

더 큰 애플리케이션 그래프에 삽입되는 재사용 가능한 그래프 조각

Neat을 사용하면 런타임을 직접 코딩하는 대신 애플리케이션을 설명할 수 있습니다. 입력, 디코딩, 크기 조정, 전처리, 추론, 후처리, 출력과 같은 일반적인 작업에 대해 미리 빌드된 노드노드 그룹를 사용하세요. Graph에서 노드를 선언하고, 해당 매개변수를 설정하고, 애플리케이션에서 필요한 순서대로 연결합니다.

Neat은 내부적으로 GStreamer를 기반으로 실행 가능한 런타임 그래프를 구축합니다. Neat은 이러한 구현을 추상화하므로 GStreamer 요소, appsrc, appsink, 큐 또는 내부 런타임 포트를 관리하는 대신 공개 Graph API를 사용합니다.

노드, 그룹, 경계

Graph는 어셈블리 경계를 나타내고, Node는 기본 구성 요소입니다.

여기에는 다음이 포함됩니다.

  • 디코딩, 전처리, 후처리, 소스, 싱크 단계와 같은 원자 노드;
  • 미리 만들어진 노드 그룹으로, 재사용 가능한 노드들의 모음입니다.
  • Input("image")Output("classes")와 같은 경계 노드.

미리 빌드된 그룹과 경계 노드에 대한 자세한 규칙은 노드 → 미리 빌드된 노드 그룹노드 → 경계 노드를 참조하십시오.

Graph::build()를 호출하면 Neat은 공개 그래프를 하나의 실행 가능한 런타임 그래프로 축소하고, 엔드포인트 이름을 보존하여 진단 및 명명된 Run API에 활용합니다.

입력 및 출력 항목에 이름 지정

InputOutput 노드의 이름은 그래프 조각의 경계 엔드포인트를 정의합니다. 최종 구성된 Graph의 바깥쪽에 남는 경계가 공개 런타임 엔드포인트가 됩니다.

simaai::neat::Graph classifier("classifier");
classifier.add(simaai::neat::nodes::Input("image"));
classifier.add(model);
classifier.add(simaai::neat::nodes::Output("classes"));

여기서 image는 입력 엔드포인트이고, classes는 출력 엔드포인트입니다. 그래프 이름인 classifier는 진단 및 시각화를 위한 레이블일 뿐이며, 입력 또는 출력을 생성하지 않습니다.

빌드하기 전에 엔드포인트를 검사하십시오.

그래프를 생성하기 전에 공개 엔드포인트를 출력합니다. 유령 엔드포인트는 허용되지 않습니다. 이름이 이 목록에 없으면 나중에 Run에서 해당 이름을 허용하지 않습니다.

for (const auto& name : classifier.inputs()) {
std::cout << "graph input: " << name << "\n";
}
for (const auto& name : classifier.outputs()) {
std::cout << "graph output: " << name << "\n";
}

graph.build(...) 이후, 실행 중인 Run에서 run.input_names()run.output_names()를 확인합니다. 이름은 공개하려는 인터페이스와 일치해야 합니다.

소스에서 소유하거나 앱에서 푸시하는 토폴로지를 선택하세요.

모든 그래프는 하나의 질문에 답해야 합니다. 즉, 입력 데이터의 소유자는 누구입니까?

토폴로지다음과 같은 경우에 사용하세요.런타임 시의 형태
앱 푸시귀하의 앱에는 이미 프레임, 텐서 또는 샘플이 있습니다.nodes.input("name")을 추가한 다음, run.push("name", ...)을 사용하여 푸시하거나 Graph.run([...])을 사용합니다.
소스 소유그래프가 파일, 카메라, RTSP 또는 다른 소스 노드에서 데이터를 읽어야 합니다.소스 노드 또는 소스 그룹을 추가한 다음, 앱 입력을 푸시하지 않고 빌드하거나 실행합니다.

앱 푸시 그래프에서는 입력 엔드포인트의 이름을 image, left_camera, metadata, prompt처럼 애플리케이션 개념에 맞게 지정합니다. 소스 소유 그래프에서는 출력 계약을 확인하고 소스 경로가 내보내는 데이터를 가져옵니다.

그래프 옵션을 선택하세요.

GraphOptions를 사용하여 그래프 수준의 동작을 설정합니다. 나중에 RunOptions를 사용하여 런타임 동작을 설정합니다.

목표사용하세요.참고 사항
로그와 내보내기 파일에 그래프에 레이블을 추가합니다.Graph("name")이것은 최종 지점이 아니라 단순히 레이블입니다.
하나의 프로세스에서 여러 개의 그래프를 실행합니다.element_name_prefix / element_name_suffix생성된 요소 이름이 중복되지 않도록 하고, 문제 진단 결과를 읽기 쉽게 만드십시오.
그래프 진단 기능을 제어합니다.VerboseOptions먼저 실제 운영 환경에서의 결과물을 확인합니다. 문제 해결을 위한 증거를 수집할 때만 디버그 출력을 활성화합니다.
출력 대기열 동작을 선택하세요.OutputOptions::Latest(), EveryFrame(...) 또는 Clocked(...)신선도, 완벽함 또는 지정된 배송 시간을 선택하세요.
실시간 그래프 조각을 연결합니다.GraphLinkOptions와 함께 RealtimeLatestByStream을 사용합니다.각 스트림을 최신 상태로 유지하면서 디코딩된 프레임의 전송을 제어합니다.
바인딩된 콜백 작업callback_timeout_msC++ 콜백 스타일의 출력 처리 방식을 사용하여 콜백 함수의 실행 속도가 느려 그래프 문제로 오인되는 상황을 방지합니다.
고급 실행을 조정합니다.advanced_execution기본 그래프에 측정된 기준선이 설정된 후에만 사용하십시오.
simaai::neat::GraphOptions options;
options.element_name_prefix = "cam0_";

simaai::neat::Graph graph("cam0_detector", options);
graph.add(simaai::neat::nodes::Input("image"));
graph.add(model);
graph.add(simaai::neat::nodes::Output(
"detections",
simaai::neat::OutputOptions::Latest()));

Latest()는 앱이 모든 출력을 가져올 수 없을 때 최신 결과를 유지합니다. 모든 출력이 중요한 경우 EveryFrame(...)을 사용하십시오. 출력이 파이프라인 클록을 따라야 하는 경우 Clocked(...)를 사용하십시오.

RunOptions::queue_depth는 그래프 입력 및 내부 런타임 큐를 제어합니다. 이는 공개 Output의 큐 계약을 대체하지 않습니다. OutputOptions::max_buffersdrop은 해당 터미널 큐를 제어하며 우선 적용됩니다. 프레임워크에서 생성된 출력 큐는 RunOptions의 기본값으로 되돌아갑니다.

구축하기 전에 검증하세요.

실행을 시작하지 않고 그래프 생성 증거를 확인하려는 경우 graph.validate(...)를 사용하세요.

simaai::neat::GraphReport report = graph.validate();
std::cout << report.to_json() << "\n";

ValidateOptions.parse_launch는 생성된 파이프라인 문자열을 확인합니다. ValidateOptions.enforce_names는 이름이 지정되지 않았거나 예상치 못한 요소를 감지합니다. 그래프 옵션을 변경하기 전에 보고서를 참조하십시오. 먼저 증거를 확인하고, 그 후에 세부 설정을 조정하십시오.

그래프 작성

가장 간결한 정신 모델

애플리케이션을 구성하려면 먼저 Graph를 선언한 다음, 선형 체인을 위해 add()를 사용하거나, 명시적인 토폴로지를 위해 connect()를 사용합니다.

  • 일반적인 단일 경로 처리, 예를 들어 간단한 단일 모델 추론과 같은 작업에 add()를 사용하세요.
  • 노드 또는 조각이 연결되는 방식을 명시적으로 제어해야 할 때 connect()를 사용하세요.
simaai::neat::Graph g;
g.add(...); // continue the same linear chain
g.connect(...); // add explicit graph topology

auto run = g.build();

add()를 사용하여 구축합니다.

다음 예제에서는 이미지를 읽고, 추론을 실행하고, 모델에서 예측된 클래스를 출력합니다.

image -> model inference -> classes

다음은 코드의 모습입니다.

simaai::neat::Model model("resnet50.tar.gz"); // Load a compiled model and prepare its Graph route.

simaai::neat::Graph g("classifier"); // instantiate the Graph that will describe the app
g.add(simaai::neat::nodes::Input("image")); // adds an input Node named "image"
g.add(model); // adds the model Nodes connected to the Input
g.add(simaai::neat::nodes::Output("classes")); // adds an output Node named "classes" connected to 'model'

auto run = g.build(); // build the Graph
노트

이전에 추가된 노드 다음에 간단한 선형 순서로 노드를 추가하려면 add()를 사용하세요.

connect()를 사용하여 구축합니다.

토폴로지에 대한 명시적인 제어가 필요할 때 connect()를 사용합니다. 각 add()는 이전 노드 또는 조각 다음에 새 노드 또는 조각을 연결합니다. 두 가지 방법을 모두 사용하는 경우, connect()는 관련 암시적 연결을 사용자가 지정한 특정 연결로 대체합니다. 또한 명명된 엔드포인트, 노드, 모델 또는 재사용 가능한 그래프 조각 간에 connect()를 직접 사용할 수도 있습니다.

connect()를 사용하여 팬아웃을 수행합니다.

팬아웃은 하나의 입력을 둘 이상의 출력으로 보냅니다. 먼저 그래프에 엔드포인트를 추가하여 존재하도록 합니다.

simaai::neat::Graph fan_out_graph("fan_out_graph");
fan_out_graph.add(simaai::neat::nodes::Input("image_input"));
fan_out_graph.add(simaai::neat::nodes::Output("original_image"));
fan_out_graph.add(simaai::neat::nodes::Output("model_image"));

개념적으로 fan_out_graph는 다음과 같은 모습일 것입니다.

image_input --> original_image --> model_image

그런 다음 connect()를 사용하여 기본 선형 배선을 원하는 토폴로지로 변경합니다.

fan_out_graph.connect("image_input", "original_image");
fan_out_graph.connect("image_input", "model_image");

개념적으로 fan_out_graph는 이제 다음과 같은 모습일 것입니다.

/--> original_image
image_input
\--> model_image

Branch()를 사용합니다.

팬아웃이 일반적이므로, Neat은 보조 도구로 graphs::Branch()를 제공합니다. 이를 통해 입력, 출력, 그리고 connect() 호출을 내부적으로 생성합니다.

auto fan_out_graph = simaai::neat::graphs::Branch(
"image_input",
{"original_image", "model_image"});

Python은 동일한 엔드포인트 이름을 사용합니다.

fan_out_graph = pyneat.graphs.branch(
"image_input",
["original_image", "model_image"],
)

fan_out_graph는 여전히 일반적인 Graph 조각입니다. 다른 Graph와 마찬가지로 더 큰 애플리케이션에 연결할 수 있습니다.

노트

분기 처리 방식은 역압 현상을 유발할 수 있습니다. 하나의 분기에서 데이터 처리가 중단되면, 선택된 런타임 정책에 따라 데이터 생성 속도를 늦추거나 완전히 중단시킬 수 있습니다. Branch()는 의도치 않은 중복 출력을 숨기는 대신, 분기 처리 방식을 명확하게 드러냅니다.

connect()를 사용하여 팬인(fan-in)을 수행합니다.

팬-인(fan-in)은 여러 입력을 하나의 출력으로 결합합니다. 단순한 팬-아웃(fan-out)과 달리, 팬-인은 샘플이 어떻게 매칭되는지도 정의해야 합니다. 이러한 정책은 출력 엔드포인트에 적용됩니다.

먼저 출력을 구성하고 엔드포인트를 추가하여 그래프에 존재하도록 합니다.

simaai::neat::OutputOptions render_options;
render_options.combine_policy = simaai::neat::CombinePolicy::ByFrame;

simaai::neat::Graph render_inputs_graph("render_inputs_graph");
render_inputs_graph.add(simaai::neat::nodes::Input("image"));
render_inputs_graph.add(simaai::neat::nodes::Input("bbox"));
render_inputs_graph.add(simaai::neat::nodes::Output("render_inputs", render_options));

개념적으로 render_inputs_graph는 다음과 같은 모습일 것입니다.

image -> bbox -> render_inputs

그런 다음 connect()를 사용하여 기본 선형 배선을 원하는 토폴로지로 변경합니다.

render_inputs_graph.connect("image", "render_inputs");
render_inputs_graph.connect("bbox", "render_inputs");

개념적으로 render_inputs_graph는 이제 다음과 같은 모습일 것입니다.

image ----\
render_inputs
bbox -----/

Combine()를 사용합니다.

팬인(fan-in)이 일반적이므로, Neat은 보조 기능으로 graphs::Combine()을 제공합니다. 이를 통해 입력, 출력, 결합 정책, 그리고 connect() 호출을 내부적으로 생성합니다.

auto render_inputs_graph = simaai::neat::graphs::Combine(
{"image", "bbox"},
"render_inputs",
simaai::neat::CombinePolicy::ByFrame);

파이썬은 결합 금지 정책에 대해 None_을 사용하는데, 이는 None이 예약어이기 때문입니다.

render_inputs_graph = pyneat.graphs.combine(
["image", "bbox"],
"render_inputs",
pyneat.CombinePolicy.ByFrame,
)

render_inputs_graph는 여전히 일반적인 Graph 조각입니다. 다른 Graph와 마찬가지로 더 큰 애플리케이션에 연결할 수 있습니다.

graphs::Combine()는 기본 OutputOptions를 사용하여 출력을 생성합니다. 즉, 4개의 대기 중인 샘플, 차단 오버플로, 클록 동기화 없음입니다. 해당 출력이 공개 터미널로 유지되는 경우, 출력을 생성하는 동안 해당 출력을 비워야 합니다. 유한한 배치 크기를 먼저 푸시한 다음 가져와야 하는 경우, 위에 표시된 명시적 팬인 구조를 만들고 해당 배치의 크기로 해당 출력의 설정을 OutputOptions::EveryFrame(...)으로 구성합니다. Python에서는 pyneat.OutputOptions.every_frame(...)를 사용합니다.

노트

CombinePolicy는 Neat에게 들어오는 샘플을 어떻게 매칭할지 알려줍니다.

  • ByFrame: 동일한 frame_id를 가진 샘플들을 결합합니다.
  • ByPts: 동일한 프레젠테이션 타임스탬프(pts_ns)를 가진 샘플을 결합합니다.
  • None: 여러 프로듀서를 결합하지 마세요. 그래프가 제대로 작동하지 않고 명시적인 정책을 요구합니다.
  • 파이썬 철자: pyneat.CombinePolicy.None_, ByFrame 또는 ByPts.

숨겨진 대체 기능은 없습니다. ByFrame을 사용하면 누락된 프레임 ID가 오류로 처리됩니다. ByPts를 사용하면 누락된 타임스탬프가 오류로 처리됩니다. 이를 통해 Neat이 잘못된 샘플을 조용히 결합하는 것을 방지할 수 있습니다.

분기 및 병합 예제 완료

이제 모든 조각을 합쳐 보세요. 입력 이미지는 두 개의 경로로 분할됩니다. 한 경로는 모델을 거쳐 바운딩 박스를 생성합니다. 다른 경로는 원본 이미지를 보존하여 후속 렌더링 단계에서 사용할 수 있도록 합니다.

/--> model_image -> model -> bbox --\
image_input render_inputs
\----------------> original_image --/

전반적인 절차는 다음과 같습니다.

  1. 먼저 입력 팬아웃 Graph 조각을 선언합니다.
  2. model_image를 입력으로 받아 bbox를 출력하는 모델 추론 Graph 조각을 생성합니다.
  3. 원본 이미지 경로와 bbox 경로를 결합하여 render_inputs를 만듭니다.
  4. 모두 연결하여 최종 결과물을 만드십시오. Graph 라고 불리는 app

분기 및 병합 예제 구성

  1. 먼저 위에서 설명한 대로 Branch()를 사용하여 팬아웃을 생성합니다.

    auto image_input_graph = simaai::neat::graphs::Branch(
    "image_input",
    {"model_image", "original_image"});

    image_input_graph는 다음과 같이 구성됩니다.

    /--> original_image
    image_input
    \--> model_image
  2. model_inference_graph를 생성합니다.

    simaai::neat::Graph model_inference_graph("model_inference_graph");
    model_inference_graph.add(simaai::neat::nodes::Input("model_image"));
    model_inference_graph.add(model);
    model_inference_graph.add(simaai::neat::nodes::Output("bbox"));

    model_inference_graph는 다음과 같이 구성됩니다.

    model_image --> model --> bbox
    노트

    이 예제에서는 선택한 모델 경로에서 디코딩된 BBOX 데이터를 출력한다고 가정합니다. 모델이 원시 추론 텐서를 출력하는 경우, Output("bbox") 전에 모델에 특화된 SimaBoxDecode 단계를 추가합니다.

  3. 다음으로 팬인 render_graph를 생성합니다.

    auto render_graph = simaai::neat::graphs::Combine(
    {"original_image", "bbox"},
    "render_inputs",
    simaai::neat::CombinePolicy::ByFrame);

    그러면 render_graph가 다음과 같이 구성됩니다.

    original_image ----\
    render_inputs
    bbox --------------/
  4. 마지막으로, 조각들을 연결하여 하나의 Graph를 만들어 전체 애플리케이션을 구성합니다.

    simaai::neat::Graph app("app");
    app.connect(image_input_graph, model_inference_graph);
    app.connect(image_input_graph, render_graph);
    app.connect(model_inference_graph, render_graph);

전체 예제:

simaai::neat::Model model("yolov8s_model.tar.gz");

auto image_input_graph = simaai::neat::graphs::Branch(
"image_input",
{"model_image", "original_image"});

simaai::neat::Graph model_inference_graph("model_inference_graph");
model_inference_graph.add(simaai::neat::nodes::Input("model_image"));
model_inference_graph.add(model);
model_inference_graph.add(simaai::neat::nodes::Output("bbox"));

auto render_graph = simaai::neat::graphs::Combine(
{"original_image", "bbox"},
"render_inputs",
simaai::neat::CombinePolicy::ByFrame);

simaai::neat::Graph app("app");
app.connect(image_input_graph, model_inference_graph);
app.connect(image_input_graph, render_graph);
app.connect(model_inference_graph, render_graph);

auto run = app.build();

auto image_sample =
simaai::neat::make_tensor_sample("image_input", image_tensor);
image_sample.frame_id = 0;
run.push("image_input", image_sample);

auto inputs = run.pull("render_inputs");

실행 가능한 예제는 일치하는 original_imagebbox 값을 포함하는 render_inputs에서 중단됩니다. 후속 렌더링 또는 출력 노드는 해당 결합된 결과를 활용하여 렌더링된 이미지를 파일에 저장하거나, 화면에 표시하거나, 다른 곳으로 전송할 수 있습니다.

런타임에 명명된 엔드포인트를 사용하는 방법

Graph가 생성된 후에는 동일한 엔드포인트 이름을 사용하여 데이터를 보내고 결과를 읽어옵니다.

그래프에 여러 개의 공개 입력 또는 출력이 있는 경우, 엔드포인트 이름을 push() 또는 pull()에 전달합니다.

run.push("image", simaai::neat::TensorList{image_tensor});
run.push("metadata", simaai::neat::TensorList{metadata_tensor});

auto classes = run.pull("classes");
auto preview = run.pull("preview");

정확히 하나의 공개 입력 또는 출력을 가진 그래프의 경우, 런타임에 이름은 선택 사항입니다.

run.push(simaai::neat::TensorList{image_tensor});
auto classes = run.pull();

둘 이상의 입력 또는 출력이 사용 가능한 경우, 이름이 지정되지 않은 push(...) 또는 pull() 작업이 실패하고, 사용자가 의도한 엔드포인트를 추측하는 대신 사용 가능한 엔드포인트 이름을 나열합니다.

실시간으로 조각들을 연결합니다.

런타임 정책이나 명시적인 원본 프레임 허용 제한이 필요한 연결을 설정할 때는 GraphLinkOptions를 사용합니다.

실시간 멀티스트림 팬인(fan-in)의 경우, RealtimeLatestByStream은 각 Sample::stream_id에 대해 최신 샘플을 유지하고 준비된 스트림을 공정하게 다운스트림으로 스케줄링합니다. 소스에서 stream_id를 스탬프하지 않으면 링크에 안정적인 ID를 스탬프합니다. 기본 정책을 사용하는 실시간 팬인은 자동으로 최신-스트림 방식으로 업그레이드됩니다.

simaai::neat::GraphLinkOptions link;
link.policy = simaai::neat::GraphLinkPolicy::RealtimeLatestByStream;
link.queue_depth = 4;
link.stream_id = "camera-0";
link.max_inflight_per_stream = 4;
link.max_inflight_total = 8;

app.connect(camera_fragment, detector_fragment, link);

RealtimeLatestByStream은 각 스트림당 항상 하나의 대기 중인 샘플을 유지합니다. queue_depth는 소스 호환성을 위해 GraphLinkOptions에 유지되며, 이 정책을 위해 예약되어 있습니다. 이 값을 변경해도 하나의 슬롯 제한이 증가하지 않습니다.

일반적인 build() API를 통해 채널 수가 많은 실시간 그래프를 생성합니다.

auto run = app.build(run_options);
run = app.build(run_options)

퓨전은 빌드 모드가 아닌 내부 컴파일러의 결정입니다. 일반적인 build()는 적격한 비공개 소스 분기, 실시간 멀티플렉서, 모델 소비자를 하나의 GStreamer 파이프라인에 유지하여 앱싱크/앱소스 장치-메모리 핸드오프를 방지합니다. 적격하지 않은 최신 스트림별 토폴로지는 일반적인 분할된 런타임을 사용합니다.

상위 수준 그래프에서 graph_options.advanced_execution.internal_queue_depth를 설정하여 모델 단계를 겹치게 합니다. 양수 값을 설정하면 CVU, MLA 및 디코딩 단계 전에 경계가 있고 누수가 없는 전역 큐가 삽입되지만, 최종 출력 단계 전에는 삽입되지 않습니다. GraphLinkOptions::max_inflight_per_stream은 각 링크에 대해 퓨전된 멀티플렉서로 전달되므로, 비공개 환경 전환 없이 최신 상태를 유지하면서 1로 유지할 수 있습니다. 단일 체인 소비자 경로를 유지하려면 internal_queue_depth를 설정하지 않거나 0로 설정합니다.

C++와 Python 모두 GraphLinkOptions에서 이러한 필드를 노출하며, 이는 일반적인 connect(...)에 전달됩니다. 별도의 실시간 연결 방법이나 빌드 모드는 필요하지 않습니다.

필드다음과 같은 경우에 사용하세요.
policy링크는 각 스트림에 대해 최신 프레임을 사용해야 합니다.
queue_depth호환성 필드입니다. RealtimeLatestByStream은 이 필드를 예약하고 각 스트림에 대해 항상 하나의 보류 중인 샘플을 유지합니다.
stream_id상위 조각은 Sample::stream_id를 표시하지 않지만, 이 링크는 하나의 안정적인 스트림을 나타냅니다.
max_inflight_per_stream실시간 스트림 링크는 디코더에서 생성된 원본 샘플을 전송하며, 각 스트림별로 명시적인 다운스트림 허용 한도가 필요합니다. 필드의 기본값은 -1이며, 코어는 4를 사용합니다. 통합된 소스 최적화는 결정된 값을 해당 멀티플렉스 스트림에 적용합니다.
max_inflight_total스트림별 실시간 링크에는 스트림 전체에 적용되는 엄격한 전역 제한이 필요합니다. 기본값은 -1이며, 환경 변수를 통해 값을 변경하지 않으면 Core에서 min(max_inflight_per_stream * stream_count, 8) 값을 파생합니다.
미리 보기 애플리케이션을 업그레이드하고 저장된 그래프를 업데이트합니다.

이번 버전에서는 GraphLinkOptions에 허용 필드를 추가합니다. 현재 Neat Library C++ ABI 및 공유 라이브러리 SONAME은 4입니다. 해당 Core 패키지에 맞춰 C++ 애플리케이션과 플러그인을 다시 빌드하십시오. 이전 libsima_neat SONAME으로 빌드된 바이너리와 libsima_neat.so.4를 혼합하지 마십시오.

다음과 같이 미리보기 소스 코드를 마이그레이션하십시오.

미리 보기 API현재 API
RealtimeGraphLinkOptionsGraphLinkOptions
graph.connect_realtime(from, to, link)graph.connect(from, to, link)
graph.build_fused_realtime_sources(options) (C++)graph.build(options)
graph.build_fused_realtime_source(options) (Python)graph.build(options)
RealtimeEveryFrameByStream신선도와 교체 주기가 허용 가능한 경우, RealtimeLatestByStream을(를) 사용합니다.

RealtimeLatestByStream은 새로 고침 정책이며, RealtimeEveryFrameByStream을 대체하는 무손실 정책이 아닙니다. 각 스트림당 하나의 보류 중인 샘플을 유지하고, 더 최신 프레임이 도착하면 해당 샘플을 대체합니다.

Graph::load()"link_policy": "realtime_every_frame_by_stream"을 포함하는 저장된 JSON을 거부합니다. 업데이트된 소스에서 그래프를 다시 생성합니다. JSON이 유일한 소스인 경우, 오래된 프레임을 삭제하는 것이 허용될 때만 정책을 realtime_latest_by_stream으로 변경한 다음, 이 버전으로 다시 로드하고 저장합니다.

하나의 스트림에서 여러 스트림으로 확장

다중 스트림 그래프는 튜닝하기 전에 먼저 고유성을 확보해야 합니다. stream_idframe_id를 보존하여 런타임 지표, 결합 정책 및 삭제 보고서를 통해 각 스트림을 서로 구별할 수 있도록 합니다.

패턴다음과 같은 경우에 사용하세요.보다
하나의 스트림 → 하나의 모델 → 하나의 결과귀하는 그래프가 제대로 작동하는지 확인하고 있습니다.출력 형태, 데이터 유형, 그리고 엔드포인트 이름입니다.
여러 스트림 → 하나의 모델 경로결합된 입력 속도는 하나의 모델 경로에 적합합니다.스트림별 공정성 및 오래된 스트림.
다양한 스트림 → 다중 모델 경로하나의 모델로는 한계가 있습니다.스트림 분할, 경로 이름 지정, 출력 회계 처리.
하나의 스트림 → 여러 모델서로 다른 결정에는 동일한 정보가 필요합니다.브랜치 수준의 지연 시간과 대상에 맞춰 정규화된 FPS(초당 프레임 수).
다수의 스트림 → 모델 + 메타데이터/비디오 출력생산 결과물에는 여러 가지 아티팩트가 있습니다.미리보기 또는 원격 측정 데이터와 별개로 대상 출력 수를 계산합니다.

최신 데이터가 중요한 실시간 출력에는 OutputOptions::Latest()를 사용합니다. 오프라인 또는 손실 없는 출력에는 EveryFrame(...)를 사용합니다. 팬인(fan-in)의 경우, 모든 입력에 frame_id가 있는 경우에만 CombinePolicy::ByFrame를 사용하고, 타임스탬프가 있는 경우에만 CombinePolicy::ByPts를 사용합니다.

런타임 큐, 삭제 정책, 측정 또는 드레인 동작이 필요한 경우, 그래프 실행로 이동합니다. Graph는 토폴로지를 설명하는 곳이고, Run은 이를 실행하는 곳입니다.

실제 예시

재사용 가능한 모델 경로

simaai::neat::Graph make_classifier(simaai::neat::Model& model) {
simaai::neat::Graph classifier_route("classifier"); // create a reusable Graph fragment
classifier_route.add(simaai::neat::nodes::Input("image")); // declare the fragment's input
classifier_route.add(model); // add the model inference route
classifier_route.add(simaai::neat::nodes::Output("classes")); // declare the fragment's output
return classifier_route; // return the fragment for reuse
}

단독으로 사용:

auto classifier_route = make_classifier(model); // create the classifier fragment
auto run = classifier_route.build(); // build it as a standalone application
run.push("image", simaai::neat::TensorList{image}); // send data to its named input
auto classes = run.pull("classes"); // read from its named output

더 큰 애플리케이션 내에서 사용:

simaai::neat::Graph app("app"); // create the larger application Graph
app.connect(camera, classifier_route); // connect the camera fragment to the classifier
app.connect(classifier_route, telemetry); // forward classification results to telemetry

더 큰 애플리케이션에서 분류기 경로의 경계 노드는 내부 선언입니다. 최종 그래프의 외부 영역에 있는 경우를 제외하고는 추가적인 공개 푸시/풀 엔드포인트가 되지 않습니다.

연결 어댑터

때로는 조각이 단순히 경계의 이름을 변경하는 것일 수도 있습니다.

simaai::neat::Graph adapter("adapter"); // create a reusable adapter fragment
adapter.add(simaai::neat::nodes::Input("raw")); // declare the incoming endpoint name
adapter.add(simaai::neat::nodes::Output("image")); // declare the outgoing endpoint name
adapter.connect("raw", "image"); // pass data directly between the endpoints

다른 그래프 내에서 사용될 때, 이 기능은 직접적인 연결로 컴파일될 수 있습니다. Neat은 가독성과 문제 해결을 위해 이름을 유지하지만, 불필요한 런타임 작업을 생성하지는 않습니다.

고급 그래프 도구

기본 그래프 경로가 제대로 작동한 후에 다음 도구들을 사용하세요. 이 도구들은 API를 처음부터 사용하는 방법이 아니라 참고 자료입니다.

도구언어다음과 같은 경우에 사용하세요.
add_output_tensor(...)C++과 파이썬고급 어댑터: 이미지 또는 비디오 출력을 가져오기 전에 요청된 형식, 크기 또는 프레임 속도로 CPU에 친숙한 UInt8 텐서로 변환해야 합니다.
Graph::save(...) / Graph::load(...); Python graph.save(...) / pyneat.Graph.load(...)C++과 파이썬그래프 구성을 유지하고 다시 로드하려는 것입니다.
graph.custom(...)C++과 파이썬선형 그래프에 백엔드 조각을 연결해야 합니다.
nodes::Custom(...) / pyneat.nodes.custom(...)C++과 파이썬명시적인 토폴로지 내부에 사용자 지정 노드가 필요합니다.
run_rtsp(...)C++과 파이썬이 그래프는 RTSP를 통해 H.264 출력을 제공해야 합니다.
set_tensor_callback(...)C++C++ 코드는 풀 루프 대신 콜백 기반의 텐서 소비 방식을 사용해야 합니다. 콜백 지연 시간이 중요한 경우 GraphOptions.callback_timeout_ms와 함께 사용하십시오.

우선 공개 노드와 그룹을 사용하는 것이 좋습니다. 해당 작업에 사용할 안정적인 노드가 없을 때만 사용자 지정 조각을 사용하는 것을 고려하십시오.

그래프 정의를 저장하고 다시 로드합니다.

구성된 정의를 다른 도구에 전달하려 하거나, CI 아티팩트를 보관하거나, 변경 사항에 따른 그래프 구성 방식을 비교하려는 경우 그래프를 저장합니다. 저장된 정의가 실행의 기준이 될 때 해당 정의를 불러옵니다.

graph.save("classifier.neat.json");

simaai::neat::Graph loaded = simaai::neat::Graph::load("classifier.neat.json");
auto run = loaded.build();

저장된 그래프 파일을 애플리케이션에서 의도적으로 버전 관리를 하지 않는 한 빌드 아티팩트로 취급합니다. 이 파일들은 그래프 구성을 나타내며, 모델 계약, 입력 데이터 또는 런타임 측정값을 대체하지 않습니다.

최고의 방법

엔드포인트 명명

애플리케이션에서 엔드포인트의 의미를 명확하게 나타내는 이름을 선택하세요.

nodes::Input("image"); // describes the data entering the Graph
nodes::Input("left_camera"); // identifies the input's application role
nodes::Output("classes"); // describes a classification result
nodes::Output("detections"); // describes a detection result
nodes::Output("preview"); // describes the output's intended use

내부 런타임 세부 사항을 기반으로 하는 이름은 사용하지 마세요.

nodes::Input("appsrc0"); // exposes an internal GStreamer implementation detail
nodes::Output("sink1"); // describes runtime wiring instead of application meaning
nodes::Output("out"); // acceptable for small tests, but unclear in applications

프래그먼트에 여러 개의 이름이 지정되지 않은 출력이 포함된 경우, Neat는 classes_0, classes_1classes_2와 같은 결정론적 접미사를 할당합니다. 애플리케이션 코드에서는 명시적인 이름을 사용하는 것이 좋습니다.

일반적인 지침

  • 애플리케이션 및 재사용 가능한 구성 요소에 Graph를 사용하세요.
  • 모델의 기본 경로를 사용하려는 경우 Graph::add(model)에서 Model을 직접 사용하십시오.
  • 프래그먼트의 공개 계약을 선언하기 위해 이름이 지정된 InputOutput 노드를 사용하십시오.
  • 직선형 체인을 만들 때는 add()를 사용하세요.
  • 명시적인 토폴로지와 조각 구성을 위해 connect()를 사용하세요.
  • 다중 입력 또는 다중 출력 앱의 경우 이름이 지정된 run.push("name", ...)run.pull("name")을 사용하세요.
  • 애플리케이션에 필요한 노드, 노드 그룹, 매개변수 및 연결을 선언하고, Neat가 낮은 수준의 런타임 세부 사항을 처리하도록 합니다.

참조:

튜토리얼