Перейти до основного вмісту

Створіть конвеєр, готовий до використання у виробництві

ПолеЗначення
КатегоріяГрафи та конвеєри
СкладністьПросунутий
Орієнтовний час читання20-25 minutes
Міткиproduction, reliability, deployment

Це заключна глава. До цього моменту кожен аспект розглядався окремо; тут вони об’єднуються в єдиний шаблон, який можна використати для створення коду для реального розгортання. Головна мета шаблону — чітко визначити три речі, які за замовчуванням залишаються неявними: межі вхідних даних моделі (щоб порушення умов виявлялися на етапі збірки, а не в процесі виконання), іменування етапів (щоб діагностична інформація залишалася зрозумілою, коли кілька моделей використовують один і той самий процес) і політику черги (щоб поведінка під навантаженням була зрозумілою, а не загадковою).

Структура така: налаштуйте параметри виконання, налаштуйте та завантажте модель, створіть 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 і нормалізація ImageNet з has_explicit_stats = true — оскільки в 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_input і include_output, обидва значення встановлено в true, — і використовує той самий суфікс _prod, щоб елементи маршруту відповідали іменуванню моделі. Потім ми викликаємо model.build(sample, route_options, run_options): це єдиний виклик, який передає Model безпосередньо до об’єкта Runner, який можна запустити, передаючи як маршрут, так і параметри запуску в основний конвеєр. Представницький зразок дозволяє процесу збірки фіксувати узгоджені розміри.

Зразок є TensorList, створеним за допомогою Tensor::from_cv_mat(rgb, ..., TensorMemory::EV74), що розміщує вхідні дані в пам’яті, відповідній для пристрою.

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() чітко розбиває потік виконання. Ця схема «push-bool / pull-with-timeout / explicit-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). Запустіть команди Python і C++ (попередньо скомпільовані) з Neat встановити в кореневу директорію **(директорію, яка містить) share/ і lib/); виконайте команди збірки з вихідного коду з кореневої директорії репозиторію.

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

Джерело