建立一個可供生產環境使用的管線
| 欄位 | 值 |
|---|---|
| 類別 | 圖形與管線 |
| 難度 | 進階 |
| 預估閱讀時間 | 20-25 minutes |
| 標籤 | production, reliability, deployment |
這是總結章節。到目前為止,我們每次只處理一個概念;在這裡,它們將結合在一起,形成一個單一的藍圖,您可以將其應用到實際的部署程式碼中。這個範本的整體目的是明確說明預設設定中未明確說明的三個事項:模型的輸入範圍(以便在建置時失敗,而不是在執行過程中失敗)、階段命名(以便在多個模型共享一個程序時,診斷資訊仍然易於閱讀),以及佇列策略(以便在負載下,行為是可以觀察的,而不是神秘的)。
其結構如下:設定執行選項,設定並載入模型,建置一個執行器,然後使用有界非同步迴圈來驅動它。到最後,您將擁有一個 Runner,它將執行一個具有生產預設值的非同步管線,以及一個用於計算成功輸出的推送/拉取迴圈——這就是您希望在同一應用程式中的多個模型中標準化的執行階段框架。
操作指南
設定執行選項
這些是生產執行階段的預設值。queue_depth = 8 提供一個小的有界緩衝區;overflow_policy = Block 使生產者等待,而不是靜默地丟棄幀(當您關心資料遺失時,這是更安全的選擇);output_memory = Owned 確保傳回的張量在拉取後仍然存在。明確設定這些值(而不是依賴預設值)可以使負載下的行為更具可預測性。
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。
C++: Model::Options 還明確說明了模型預期的預處理——InputKind::Image、RGB 顏色轉換以及使用 has_explicit_stats = true 的 ImageNet 正規化——因為 C++ 路徑會提前宣告預處理,而不是依賴檔案的預設值。
Python: ModelOptions 在 mopt.preprocess.* 下設定圖像預處理、輸入範圍、ImageNet 正規化和字尾。
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_input 和 include_output 都為 true——並使用相同的 _prod 後綴,以便路由的元素與模型的命名匹配。然後,我們調用 model.build(sample, route_options, run_options):這是一個單次調用路徑,它將 Model 直接連接到一個可運行的 Runner,並將路由和運行選項傳遞到底層的管線中。這個示例樣本允許建置鎖定已協商的形狀。
C++: 該樣本是一個使用 Tensor::from_cv_mat(rgb, ..., TensorMemory::EV74) 建置的 TensorList,它將輸入放置在適合設備的記憶體中。
Python: 該樣本是一個列表 ,包含一個來自 Tensor.from_numpy(...) 的 Tensor。
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() 會乾淨地關閉運行器。這種推送布林值/帶超時的拉取/明確關閉的模式是可靠的非同步框架;替換成您實際的輸入和輸出處理,結構保持不變。
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(無需額外的資料夾),請參閱登陸頁面上的 如何執行教學。
完整原始碼
顯示完整原始碼程式
// 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;
}
}