Надсилання кількох вхідних даних в одному зразку
| Поле | Значення |
|---|---|
| Категорія | Моделі та інференс |
| Складність | Середній |
| Орієнтовний час читання | 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;
}
}