Надсилання кількох вхідних даних в одному зразку
| Поле | Значення |
|---|---|
| Категорія | Моделі та інференс |
| Складність | Середній |
| Орієнтовний час читання | 15 minutes |
| Мітки | multi-input, samples, sync |
У багатьох реальних застосунках обробляється більше одного вхідного значення під час одного етапу висновку. Neat представляє це як пакетний зразок: один Sample, список fields якого містить кілька іменованих тензорних даних, до кожного з яких можна отримати доступ за допомогою port_name. Середовище виконання зберігає іменовані поля разом як одну логічну подію, тому left і right (або зображення та метадані) залишаються узгодженими протягом усього конвеєра.
У цьому розділі створюється граф, який приймає тензор на вході та видає тензор на виході, об’єднуються два іменовані тензори з плаваючою комою, пакет даних передається через граф, а потім іменовані поля зчитуються. Наприкінці ви створите багатопольний зразок і підтвердите, що обидва поля успішно пройшли повний цикл, і їхні імена портів залишилися незмінними.
Покроковий огляд
Налаштуйте тензорний вхід
Цей граф обробляє необроблені тензори, а не декодовані зображення, тому вхідний контракт визначається як тензорний пакет даних (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 дорівнює Bundle.
// 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) повертає один Sample. Оскільки логічний результат містить кілька полів, повернутий Sample сам по собі є Bundle — тому ми перевіряємо out.kind == SampleKind::Bundle і перебираємо out.fields, а не front() (що означало б «перше поле всередині пакета»).
auto outs = run.run(simaai::neat::Sample{bundle}, /*timeout_ms=*/1000);
Запуск
Запустіть команди Python і C++ (попередньо скомпільовані) з кореневої директорії встановлення Neat (директорії, яка містить 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 (додаткова тека не потрібна), див. розділ Як запускати навчальні матеріали на головній сторінці.
На практиці
Як застосувати шаблон «пакет» за межами цієї демонстрації з двома полями.
Найменування та маршрутизація
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;
}
}