メインコンテンツまでスキップ

カスタムデータグラフを作成する

項目
カテゴリグラフとパイプライン
難易度中級
推定所要時間15-20 minutes
ラベルgraph, traversal, metadata

第 3 章では、匿名の入力 → 出力グラフを構築し、位置情報を用いて実行しました。 run() 呼び出し。実際のオーケストレーション(ファンアウト、ファンイン、ストリームごとのルーティング)では、位置ではなく名前でエンドポイントを扱う必要があります。この章では、可能な限り小さなグラフで、名前付きエンドポイントの概念を紹介します。これにより、マルチストリームおよび埋め込みモデルの章でそれらを拡張する前に、名前付けと接続の仕組みを分離して確認できます。

公開 Graph アプリケーションの構成面は次のとおりです。 add(...) ノード、 connect(...) 名前付きのエンドポイント、 build() 一度、再利用可能なものに Runそして push("image", ...) そして pull("out", ...) 名前で。最終的には、1つのテンソルをプッシュすることになります。 Sample 名前付きのグラフを通じて、その内容を確認しました。 stream_id, frame_idおよび pts_ns 変更なしで出力されました。これは、ランタイムがメタデータを最初から最後まで完全に保持することの証明です。

ウォークスルー

グラフを構成します

ノードを2つ追加します。 Input("image") 「push」という名前の終点(エンドポイント)を宣言します。 image; Output("out") 「pull」という名前の終端点を宣言します。 out名前は契約内容を表します。これは、まさにあなたが渡す文字列そのものです。 push(...) そして pull(...) 後で。エンドポイントに名前を付ける(追加順に依存するのではなく)ことで、複数の入力または出力を持つ大規模なグラフを操作する際に、曖昧さをなくすことができます。

ノードは simaai::neat::nodes::Input("image")nodes::Output("out") から取得します。

tutorials/013_build_a_custom_data_graph/build_a_custom_data_graph.cpp
// `Graph` is the public composition type. Input("image") declares the name
// used by Run::push("image", ...). Output("out") declares the name used by
// Run::pull("out", ...).
simaai::neat::Graph graph;
graph.add(simaai::neat::nodes::Input("image"));
graph.add(simaai::neat::nodes::Output("out"));

エンドポイントを接続する

connect("image", "out") エッジを宣言します:フレームがプッシュされるのは image 流れ out2つのノードだけで、これがネットワーク全体の構成となりますが。 connect(...) より大きなグラフでブランチやマージを構築するために使用するのと同じ関数です。次に、以下を出力します。 graph.describe() 構成されたトポロジーをダンプする — グラフが意図したとおりに接続されているかをすばやく確認し、構築を開始する前に検証します。

tutorials/013_build_a_custom_data_graph/build_a_custom_data_graph.cpp
graph.connect("image", "out");

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

サンプルを構築してプッシュ

build() (ここでは初期サンプルは不要です)記述を実際に実行可能なものに変換します。 Run次に、決定的なテンソルを1つ構築します。 Sample — 既知の情報を格納した8×8×3のRGB画像 stream_id, frame_idおよび pts_ns — そして push(...) それを image 名前でエンドポイントを指定します。サンプルに含まれるメタデータは、後で確認する内容です。

push(...) は bool 型の値を返します。失敗した場合は run.last_error() を表示します。サンプルは make_sample() で作成します。

tutorials/013_build_a_custom_data_graph/build_a_custom_data_graph.cpp
simaai::neat::Run run = graph.build();
if (!run.push("image", make_sample())) {
throw std::runtime_error("push failed: " + run.last_error());
}

出力を取得し、メタデータを検証します。

pull("out", ...) 指定された出力エンドポイントから結果を取得し、タイムアウト時間を過ぎると、 close() 実行時。入力と出力の間に変換処理がないため、正しいパイプラインは同じ論理的なサンプルを返します。したがって、読み出し時には stream_id, frame_idおよび pts_ns そして、私たちが送信した値を確認することで、ランタイムがサンプルごとのメタデータをトラバーサルを通じて保持していることがわかります。この保証こそが、後続の処理段階でフレームの識別子とタイムスタンプを信頼できるようにするものです。

tutorials/013_build_a_custom_data_graph/build_a_custom_data_graph.cpp
auto out = run.pull("out", /*timeout_ms=*/2000);
run.close();

実行

実行すると、グラフの説明の後に、往復処理されたメタデータが表示されるはずです。PythonC++(事前にビルドされたもの)のコマンドをから実行してください。Neat root をインストールします(ディレクトリには、次のものが含まれます)。 share/ そして lib/); ソースコードからビルドするためのコマンドをリポジトリのルートディレクトリ**から実行します。この章ではモデルアーカイブは必要ありません。

C++ (prebuilt):

./lib/sima-neat/tutorials/tutorial_013_build_a_custom_data_graph

C++ (build from source):

./build.sh --target tutorial_013_build_a_custom_data_graph
./build/tutorials-standalone/tutorial_013_build_a_custom_data_graph

期待される出力(以下に続く) graph.describe() ダンプ:

stream=graph frame=42 pts_ns=123456789
[OK] 013_build_a_custom_data_graph

(Pythonのビルドでは、stream_id=graph frame_id=42 pts_ns=123456789 が出力されます。)この章のC++ソースコードをカスタムの CMakeLists.txt を使って独自のプロジェクトに組み込むには(追加のフォルダーは不要です)、ランディングページのチュートリアルの実行方法を参照してください。

完全なソース

完全なソースプログラムを表示
tutorials/013_build_a_custom_data_graph/build_a_custom_data_graph.cpp
// Compose a minimal public Neat Graph: named Input -> named Output.
//
// Usage:
// tutorial_013_build_a_custom_data_graph

#include "neat.h"

#include <cstdint>
#include <iostream>
#include <stdexcept>
#include <utility>
#include <vector>

namespace {

std::vector<int64_t> contiguous_strides_bytes(const std::vector<int64_t>& shape,
int64_t elem_bytes) {
std::vector<int64_t> strides(shape.size(), 0);
int64_t stride = elem_bytes;
for (int i = static_cast<int>(shape.size()) - 1; i >= 0; --i) {
strides[static_cast<size_t>(i)] = stride;
stride *= shape[static_cast<size_t>(i)];
}
return strides;
}

simaai::neat::Sample make_sample() {
const int w = 8;
const int h = 8;
const int c = 3;
const std::size_t bytes = static_cast<std::size_t>(w) * h * c;

simaai::neat::Tensor t;
t.device = {simaai::neat::DeviceType::CPU, 0};
t.dtype = simaai::neat::TensorDType::UInt8;
t.layout = simaai::neat::TensorLayout::HWC;
t.shape = {h, w, c};
t.semantic.image = simaai::neat::ImageSpec{simaai::neat::ImageSpec::PixelFormat::RGB, ""};
t.storage = simaai::neat::make_cpu_owned_storage(bytes);
t.strides_bytes = contiguous_strides_bytes(t.shape, 1);
t.read_only = false;
{
auto map = t.map(simaai::neat::MapMode::Write);
auto* p = static_cast<std::uint8_t*>(map.data);
for (std::size_t i = 0; i < bytes; ++i)
p[i] = static_cast<std::uint8_t>(i % 255);
}
t.read_only = true;

simaai::neat::Sample s;
s.kind = simaai::neat::SampleKind::Tensor;
s.tensor = std::move(t);
s.stream_id = "graph";
s.frame_id = 42;
s.pts_ns = 123456789;
return s;
}

} // namespace

int main() {
try {
// CORE LOGIC
// `Graph` is the public composition type. Input("image") declares the name
// used by Run::push("image", ...). Output("out") declares the name used by
// Run::pull("out", ...).
simaai::neat::Graph graph;
graph.add(simaai::neat::nodes::Input("image"));
graph.add(simaai::neat::nodes::Output("out"));
graph.connect("image", "out");

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

simaai::neat::Run run = graph.build();
if (!run.push("image", make_sample())) {
throw std::runtime_error("push failed: " + run.last_error());
}
auto out = run.pull("out", /*timeout_ms=*/2000);
run.close();

if (!out.has_value())
throw std::runtime_error("graph produced no output");
std::cout << "stream=" << out->stream_id << " frame=" << out->frame_id
<< " pts_ns=" << out->pts_ns << "\n";
std::cout << "[OK] 013_build_a_custom_data_graph\n";
return 0;
} catch (const std::exception& e) {
std::cerr << "[FAIL] " << e.what() << "\n";
return 1;
}
}

ソース