1つのサンプルで複数の入力を送信する
| 項目 | 値 |
|---|---|
| カテゴリ | モデルと推論 |
| 難易度 | 中級 |
| 推定所要時間 | 15 minutes |
| ラベル | multi-input, samples, sync |
多くの実際のアプリケーションでは、1回の推論処理で複数の入力を使用します。 Neat これをバンドルサンプルとして表現します。これは単一のものです。 Sample 誰の fields リストには複数の名前付きテンソルペイロードが含まれており、それぞれにアドレスが割り当てられています。 port_nameランタイムは、指定されたフィールドをまとめて1つの論理的なイベントとして保持します。 left そして right (または画像とメタデータ) がパイプライン全体を通して整合性を保ちます。
この章では、テンソルを入力として受け取り、テンソルを出力するグラフを構築し、2つの名前付きの浮動小数点テンソルをまとめて、パイプラインを通して処理し、名 前付きフィールドを読み出します。最終的には、複数のフィールドを持つサンプルを構築し、両方のフィールドがポート名を含めて変更されずに処理されたことを確認します。
ウォークスルー
テンソル入力の設定
このグラフはデコードされた画像ではなく、生のテンソルを消費するため、入力の形式はテンソルペイロードとして定義されます。FP32、 width/height/depth)というよりは、ピクセル形式です。これにより、入力ノードはテンソルバッファーを直接受け入れるようになります。
in.payload_type = PayloadType::Tensor を設定します。
simaai::neat::InputOptions in;
in.payload_type = simaai::neat::PayloadType::Tensor;
in.format = "FP32";
in.width = w;
in.height = h;
in.depth = c;
グラフと初期実行を構築する
当社は同じ最小限の Input -> Output 第004章のトポロジーから build() それを Run. build() 交渉によって決定された形状を固定するには、代表的なサンプルが必要です。そのため、実際のフィールドで使用するのと同じ形状の単一のシードテンソル(すべてゼロ)を渡します。シードは形状の交渉のためだけに使用され、実際のデータは次に処理されます。
// Graph accepting fp32 tensors as input.
simaai::neat::Graph graph;
graph.add(simaai::neat::nodes::Input(in));
graph.add(simaai::neat::nodes::Output());
auto run = graph.build(simaai::neat::TensorList{seed});
バンドルを構築します
それでは、複数の入力をまとめてイベントを作成します。各入力には、次のように名前が付けられます。 make_tensor_sample(port_name, tensor)そして、それらの名前付きフィールドは、モデルがポートを通じて扱う対象となります。 left ~で満たされています 1.0 そして right ~とともに 2.0 そのため、移動中にそれらを区別することができます。
make_bundle_sample({...}) 指定されたフィールドをまとめて処理します。 Sample 誰の kind は BundlePython: 名前付きのサンプルの一覧が、直接渡されます。 push(...); pyneat が、バンドルをまとめる処理を行います。
// make_bundle_sample packs multiple named tensors into one Sample.
simaai::neat::Sample bundle = simaai::neat::make_bundle_sample({
simaai::neat::make_tensor_sample("left", make_fp32_tensor(w, h, c, 1.0f)),
simaai::neat::make_tensor_sample("right", make_fp32_tensor(w, h, c, 2.0f)),
});
バンドルをプッシュし、その内容を読み出します。
最後に、バンドルを送信して結果を確認します。出力自体もバンドルです。 Sampleなので、私たちは読みました。 out.fields 単一のテンソルとして扱うのではなく、 out.fields.size() ~すべきである 2、そして各フィールドは次のような情報を含みます。 port_name およびテンソルペイロード。
run.run(Sample{bundle}, timeout_ms) 1つを返します Sample論理的な結果には複数のフィールドがあるため、返された Sample それ自体が Bundle — そこで確認します out.kind == SampleKind::Bundle そして繰り返す out.fieldsではありません front() (これは「バンドル内の最初のフィールド」という意味になります)。
auto outs = run.run(simaai::neat::Sample{bundle}, /*timeout_ms=*/1000);
実行
「Python」と「C++(事前にビルドされたもの)」のコマンドを、以下の場所から実行します。Neat root をインストールします(ディレクトリには、次のものが含まれます)。 share/ そして lib/); ソースコードからビルドするためのコマンドをリポジトリのルートディレクトリから実行します。この章ではモデルアーカイブは必要ありません。
C++ (prebuilt):
./lib/sima-neat/tutorials/tutorial_010_feed_multi_input_model \
--width 64 --height 48
C++ (build from source):
./build.sh --target tutorial_010_feed_multi_input_model
./build/tutorials-standalone/tutorial_010_feed_multi_input_model \
--width 64 --height 48
期待される出力(C++):
bundle_fields=2
field=left has_tensor=yes
field=right has_tensor=yes
[OK] 010_feed_multi_input_model
(Pythonのビルドでは、port=left has_tensor=True の行と同じフィールド数が表示されます。)この章のC++ソースコードをカスタム の CMakeLists.txt を使って独自のプロジェクトに組み込むには(追加のフォルダーは不要です)、ランディングページのチュートリアルの実行方法を参照してください。
実践
この2つのフィールドを使用したデモを超えて、バンドルパターンをどのように適用するか。
名前付けとルーティング
-
port_name配線契約とは、複数の入力を持つモデルが各フィールドをどのように処理するかを定めたものです。モデルで宣言された入力ポートと名前を一致させてください。 -
出力バンドルはフィールド構造を保持するため、位置ではなく名前で結果と入力を対応させることができます。
出力バンドルの確認
- 常に分岐処理を行います。
kindまず、複数のフィールドを持つ結果はSampleKind.Bundleそして、それを単一のテンソルとして読み込もうとすると、正しく処理できません。 - 各フィールドにおけるテンソルの存在を確認してください。
field.tensor is not None/field.tensor.has_value()ペイロードにアクセスする前に — フィールドにはテンソルではなく、メタデータが含まれている場合があります。
完全なソース
完全なソースプログラムを表示
// Build a multi-port bundle Sample and push it through a tensor-in/tensor-out Graph.
//
// Usage:
// tutorial_010_feed_multi_input_model [--width 64] [--height 48]
#include "neat.h"
#include <cstddef>
#include <iostream>
#include <stdexcept>
#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;
}
int parse_int_arg(int argc, char** argv, const std::string& key, int def) {
std::string value;
if (!get_arg(argc, argv, key, value))
return def;
return std::stoi(value);
}
simaai::neat::Tensor make_fp32_tensor(int w, int h, int c, float fill) {
const std::size_t bytes = static_cast<std::size_t>(w) * h * c * sizeof(float);
auto storage = simaai::neat::make_cpu_owned_storage(bytes);
auto map = storage->map(simaai::neat::MapMode::Write);
auto* p = static_cast<float*>(map.data);
const std::size_t n = static_cast<std::size_t>(w) * h * c;
for (std::size_t i = 0; i < n; ++i)
p[i] = fill;
simaai::neat::Tensor t;
t.storage = storage;
t.dtype = simaai::neat::TensorDType::Float32;
t.layout = simaai::neat::TensorLayout::HWC;
t.shape = {h, w, c};
t.device = {simaai::neat::DeviceType::CPU, 0};
t.read_only = true;
return t;
}
} // namespace
int main(int argc, char** argv) {
try {
const int w = parse_int_arg(argc, argv, "--width", 64);
const int h = parse_int_arg(argc, argv, "--height", 48);
const int c = 3;
simaai::neat::InputOptions in;
in.payload_type = simaai::neat::PayloadType::Tensor;
in.format = "FP32";
in.width = w;
in.height = h;
in.depth = c;
simaai::neat::Tensor seed = make_fp32_tensor(w, h, c, 0.0f);
// CORE LOGIC
// Graph accepting fp32 tensors as input.
simaai::neat::Graph graph;
graph.add(simaai::neat::nodes::Input(in));
graph.add(simaai::neat::nodes::Output());
auto run = graph.build(simaai::neat::TensorList{seed});
// make_bundle_sample packs multiple named tensors into one Sample.
simaai::neat::Sample bundle = simaai::neat::make_bundle_sample({
simaai::neat::make_tensor_sample("left", make_fp32_tensor(w, h, c, 1.0f)),
simaai::neat::make_tensor_sample("right", make_fp32_tensor(w, h, c, 2.0f)),
});
auto outs = run.run(simaai::neat::Sample{bundle}, /*timeout_ms=*/1000);
if (outs.empty())
throw std::runtime_error("bundle output missing");
// `Run::run(Sample)` returns one Sample. When the logical result has multiple fields,
// that Sample is itself a Bundle; `front()` would mean "first field inside the bundle",
// not "first output sample".
const simaai::neat::Sample& out = outs;
if (out.kind != simaai::neat::SampleKind::Bundle)
throw std::runtime_error("expected bundle output");
if (out.fields.size() != 2U)
throw std::runtime_error("expected two bundle fields");
std::cout << "bundle_fields=" << out.fields.size() << "\n";
for (std::size_t i = 0; i < out.fields.size(); ++i) {
const auto& field = out.fields[i];
const bool has_tensor = field.tensor.has_value() || !field.tensors.empty();
const std::string label =
!field.port_name.empty()
? field.port_name
: (!field.stream_label.empty() ? field.stream_label : ("field_" + std::to_string(i)));
std::cout << " field=" << label << " has_tensor=" << (has_tensor ? "yes" : "no") << "\n";
}
std::cout << "[OK] 010_feed_multi_input_model\n";
return 0;
} catch (const std::exception& e) {
std::cerr << "[FAIL] " << e.what() << "\n";
return 1;
}
}