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

本番環境で利用可能なパイプラインを構築する

項目
カテゴリグラフとパイプライン
難易度上級
推定所要時間20-25 minutes
ラベルproduction, reliability, deployment

これは集大成の章です。これまでの内容は、それぞれ独立した概念でしたが、ここではそれらが統合され、実際のデプロイメントコードに適用できる単一の設計図となります。このテンプレートの主な目的は、デフォルト設定では暗黙的になってしまう3つの要素を明示的にすることです。それは、モデルの入力範囲(これにより、契約違反は実行中にではなく、ビルド時に検出される)、ステージの名前(これにより、複数のモデルが同じプロセスを共有する場合でも、診断結果が読みやすくなる)、およびキューポリシー(これにより、負荷時の動作が予測可能になり、不可解な状態になるのを防ぐ)です。

全体の流れは次のとおりです。実行オプションを設定し、モデルを構成およびロードし、ランナーを構築し、次に、制限された非同期ループで実行します。最終的に、あなたは、同じアプリケーション内の複数のモデルで標準化できる、プロダクションのデフォルト設定と、正常な出力をカウントするプッシュ/プルループを備えた非同期パイプラインを実行するRunnerを持つことになります。これはランタイムのスケルトンです。

ウォークスルー

実行オプションを設定する

これらは、プロダクションランタイムのデフォルト設定です。queue_depth = 8は、小さな制限されたバッファーを提供します。overflow_policy = Blockは、プロデューサーがフレームをサイレントに破棄するのではなく、待機するようにします(損失が問題になる場合は、安全な選択肢です)。output_memory = Ownedは、返されたテンソルがプルの後も存続することを保証します。これらの設定を明示的に行う(デフォルトに依存するのではなく)ことで、負荷時の動作が予測可能になります。

tutorials/017_build_production_pipeline/build_production_pipeline.cpp
simaai::neat::RunOptions run_opt;
run_opt.queue_depth = 8;
run_opt.overflow_policy = simaai::neat::OverflowPolicy::Block;
run_opt.output_memory = simaai::neat::OutputMemory::Owned;

モデルを構成およびロードする

ここでは、モデルの入力契約を明示的にします。preprocess.input_max_width/height/depthをフレームの寸法に設定すると、入力が一致しない場合、明確な契約違反エラーが発生し、ビルド時に失敗します。これにより、後で混乱を招くランタイムエラーが発生するのを防ぎます。name_suffix = "_prod"は、このモデルのステージにタグを付けて、マルチモデルアプリケーション全体で診断結果を特定できるようにします。次に、アーカイブパスとこれらのオプションからModelを構築します。

Model::Optionsは、モデルが期待する前処理(InputKind::Image、RGBカラー変換、およびhas_explicit_stats = trueによるImageNet正規化)も明示的に指定します。これは、C++パスが前処理を事前に宣言し、アーカイブのデフォルトに依存しないためです。

tutorials/017_build_production_pipeline/build_production_pipeline.cpp
simaai::neat::Model::Options model_opt;
model_opt.preprocess.kind = simaai::neat::InputKind::Image;
model_opt.preprocess.enable = simaai::neat::AutoFlag::On;
model_opt.preprocess.color_convert.input_format = simaai::neat::PreprocessColorFormat::RGB;
model_opt.preprocess.input_max_width = rgb.cols;
model_opt.preprocess.input_max_height = rgb.rows;
model_opt.preprocess.input_max_depth = rgb.channels();
model_opt.preprocess.normalize.enable = simaai::neat::AutoFlag::On;
model_opt.preprocess.normalize.mean = {0.485f, 0.456f, 0.406f};
model_opt.preprocess.normalize.stddev = {0.229f, 0.224f, 0.225f};
model_opt.preprocess.normalize.has_explicit_stats = true;
model_opt.name_suffix = "_prod";

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

ランナーを構築する

ModelRouteOptions (C++ Model::RouteOptions) は、ルートに含める境界を決定します。ここでは、include_inputinclude_output の両方が true に設定されており、ルートの要素がモデルの命名規則と一致するように、同じ _prod サフィックスが使用されます。次に、model.build(sample, route_options, run_options) を呼び出します。これは、Model を直接実行可能な Runner に接続し、ルートと実行オプションを基盤となるパイプラインに転送する、単一の呼び出しパスです。この代表的なサンプルにより、ビルドプロセスでネゴシエートされた形状を固定できます。

サンプルは、Tensor::from_cv_mat(rgb, ..., TensorMemory::EV74) を使用して構築された TensorList であり、入力データをデバイスに適したメモリに配置します。

tutorials/017_build_production_pipeline/build_production_pipeline.cpp
simaai::neat::Model::RouteOptions sess_opt;
sess_opt.include_input = true;
sess_opt.include_output = true;
sess_opt.name_suffix = "_prod";

auto runner = model.build(
simaai::neat::TensorList{simaai::neat::Tensor::from_cv_mat(
rgb, simaai::neat::ImageSpec::PixelFormat::RGB, simaai::neat::TensorMemory::EV74)},
sess_opt, run_opt);

プロダクションループの実行

これは、実際のサービスが実行するループです。各イテレーションで、入力を push(...) し、ブール値をチェックします。これにより、拒否されたプッシュ(Block、一時的な状態)が誤ってカウントされるのではなく、適切に処理されます。次に、有限のタイムアウトで pull(...) を実行し、成功した出力をカウントします。ループの最後に、close() を呼び出して、ランナーをクリーンにシャットダウンします。このプッシュ-ブール / タイムアウト付きプル / 明示的なクローズのパターンは、信頼性の高い非同期の基本構造です。実際の入力と出力処理に置き換えても、構造は変わりません。

tutorials/017_build_production_pipeline/build_production_pipeline.cpp
int ok = 0;
for (int i = 0; i < iters; ++i) {
if (!runner.push(simaai::neat::TensorList{simaai::neat::Tensor::from_cv_mat(
rgb, simaai::neat::ImageSpec::PixelFormat::RGB, simaai::neat::TensorMemory::EV74)}))
continue;
auto out = runner.pull(/*timeout_ms=*/2000);
if (!out.empty())
++ok;
}
runner.close();
if (ok <= 0)
throw std::runtime_error("runner produced no outputs");

実行

この章では、モデルアーカイブ(resnet_50)が必要です。Neat インストールルートshare/lib/ を含むディレクトリ)から、Python および C++(事前にビルドされたもの) のコマンドを実行します。ソースコードからビルドする コマンドは、リポジトリのルートから実行します。

C++ (prebuilt):

./lib/sima-neat/tutorials/tutorial_017_build_production_pipeline \
--model /tmp/resnet_50.tar.gz --iters 4

C++ (build from source):

./build.sh --target tutorial_017_build_production_pipeline
./build/tutorials-standalone/tutorial_017_build_production_pipeline \
--model /tmp/resnet_50.tar.gz --iters 4

期待される出力:

outputs=4
[OK] 017_build_production_pipeline

(Python ビルドは、iters=4 ok=4 を出力します。)

この章の C++ ソースコードを、カスタムの CMakeLists.txt を使用して独自のプロジェクトに統合する方法(追加のフォルダーは不要)については、ランディングページにある チュートリアルの実行方法 を参照してください。

完全なソース

完全なソースプログラムを表示
tutorials/017_build_production_pipeline/build_production_pipeline.cpp
// Production blueprint: wrap a Model in a Runner with production-grade RunOptions.
//
// Usage:
// tutorial_017_build_production_pipeline --model /path/to/resnet_50.tar.gz [--iters 4]

#include "neat.h"

#include <opencv2/core.hpp>

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

} // namespace

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

cv::Mat rgb(224, 224, CV_8UC3, cv::Scalar(16, 96, 196));
if (!rgb.isContinuous())
rgb = rgb.clone();

// CORE LOGIC
// Production defaults: bounded queue, blocking overflow, owned output memory.
// Model::build returns a Runner that owns the async pipeline; measure the
// workload explicitly when you need performance data.
simaai::neat::RunOptions run_opt;
run_opt.queue_depth = 8;
run_opt.overflow_policy = simaai::neat::OverflowPolicy::Block;
run_opt.output_memory = simaai::neat::OutputMemory::Owned;

simaai::neat::Model::Options model_opt;
model_opt.preprocess.kind = simaai::neat::InputKind::Image;
model_opt.preprocess.enable = simaai::neat::AutoFlag::On;
model_opt.preprocess.color_convert.input_format = simaai::neat::PreprocessColorFormat::RGB;
model_opt.preprocess.input_max_width = rgb.cols;
model_opt.preprocess.input_max_height = rgb.rows;
model_opt.preprocess.input_max_depth = rgb.channels();
model_opt.preprocess.normalize.enable = simaai::neat::AutoFlag::On;
model_opt.preprocess.normalize.mean = {0.485f, 0.456f, 0.406f};
model_opt.preprocess.normalize.stddev = {0.229f, 0.224f, 0.225f};
model_opt.preprocess.normalize.has_explicit_stats = true;
model_opt.name_suffix = "_prod";

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

simaai::neat::Model::RouteOptions sess_opt;
sess_opt.include_input = true;
sess_opt.include_output = true;
sess_opt.name_suffix = "_prod";

auto runner = model.build(
simaai::neat::TensorList{simaai::neat::Tensor::from_cv_mat(
rgb, simaai::neat::ImageSpec::PixelFormat::RGB, simaai::neat::TensorMemory::EV74)},
sess_opt, run_opt);

int ok = 0;
for (int i = 0; i < iters; ++i) {
if (!runner.push(simaai::neat::TensorList{simaai::neat::Tensor::from_cv_mat(
rgb, simaai::neat::ImageSpec::PixelFormat::RGB, simaai::neat::TensorMemory::EV74)}))
continue;
auto out = runner.pull(/*timeout_ms=*/2000);
if (!out.empty())
++ok;
}
runner.close();
if (ok <= 0)
throw std::runtime_error("runner produced no outputs");

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

ソース