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

Запустіть свою першу модель через PCIe

ПолеЗначення
КатегоріяСпівпроцесинг PCIe
СкладністьПочатковий
Орієнтовний час читання15 minutes
МіткиPCIe, inference, tensor, image, detection

Запустіть три незалежні програми з одним і тим же архівом YOLOv8s і зображенням вулиці розміром 640x480. Кожна програма демонструє один режим, використовує чергу 0 синхронно та закриває одну модель. Це дозволяє кожному прикладу бути достатньо коротким, щоб його можна було скопіювати окремо.

Покроковий огляд

Запустіть тензор, готовий до використання моделлю

Хост змінює розмір зображення відповідно до вхідних даних [640, 640, 3], зазначених моделлю, конвертує BGR в RGB і масштабує пікселі до [0, 1]. Model.run() надсилає цей тензор FP32 без попередньої обробки зображення на карті та виводить усі шість необроблених вихідних каналів YOLO.

pcie_host/tutorials/024_run_your_first_model_over_pcie/run_tensor_mode.cpp
pcie::ConnectionOptions connection;
connection.card_id = card_id;
pcie::Model model(kModelPath, {}, connection);
const auto info = model.info();
if (info.inputs.size() != 1) {
throw std::runtime_error("YOLOv8s must expose one input tensor");
}
const pcie::Tensor input = make_yolo_tensor(image, info.inputs[0]);
model.build(kBuildTimeoutMs);
const auto outputs = model.run(input, kRunTimeoutMs);
if (outputs.empty()) {
throw std::runtime_error("tensor mode returned no outputs");
}
std::cout << "Tensor mode raw outputs:\n";
for (const auto& output : outputs) {
std::cout << " " << output.route.name << " " << dtype_name(output.dtype) << " "
<< shape_string(output.shape) << '\n';
}
model.close();

Перенесіть попередню обробку на карту

Встановіть preprocess.kind на Image, ідентифікуйте вхідні пікселі як BGR і виберіть попередньо встановлений профіль COCO_YOLO. Тепер хост надсилає декодовані пікселі, а карта виконує зміну розміру, конвертацію BGR в RGB і нормалізацію. Програма виводить імена та розміри шести необроблених вихідних каналів, щоб ви могли порівняти їх із режимом тензора.

pcie_host/tutorials/024_run_your_first_model_over_pcie/run_image_mode.cpp
pcie::ConnectionOptions connection;
connection.card_id = card_id;
pcie::ModelOptions options;
options.preprocess.kind = pcie::InputKind::Image;
options.preprocess.color_convert.input_format = pcie::ColorFormat::BGR;
options.preprocess.color_convert.output_format = pcie::ColorFormat::RGB;
options.preprocess.resize.enable = pcie::AutoFlag::On;
options.preprocess.resize.mode = pcie::ResizeMode::Letterbox;
options.preprocess.normalize.preset = pcie::NormalizePreset::COCO_YOLO;
pcie::Model model(kModelPath, options, connection);
model.build(kBuildTimeoutMs);
const auto outputs = model.run(image, kRunTimeoutMs);
if (outputs.empty()) {
throw std::runtime_error("image mode returned no outputs");
}
std::cout << "Image mode raw outputs:\n";
for (const auto& output : outputs) {
std::cout << " " << output.route.name << " " << dtype_name(output.dtype) << " "
<< shape_string(output.shape) << '\n';
}
model.close();

Декодуйте виявлення на карті

Додайте BoxDecodeType.YoloV8, порогове значення для оцінки, порогове значення NMS і ліміт виводу. Повернений тензор BBOX починається з кількості виявлень, за яким слідують записи фіксованого розміру, що містять (x, y, width, height, score, class_id). Приклад аналізує та виводить перші десять записів у координатах вихідного зображення.

pcie_host/tutorials/024_run_your_first_model_over_pcie/run_image_boxdecode.cpp
pcie::ConnectionOptions connection;
connection.card_id = card_id;
pcie::ModelOptions options;
options.preprocess.kind = pcie::InputKind::Image;
options.preprocess.color_convert.input_format = pcie::ColorFormat::BGR;
options.preprocess.color_convert.output_format = pcie::ColorFormat::RGB;
options.preprocess.resize.enable = pcie::AutoFlag::On;
options.preprocess.resize.mode = pcie::ResizeMode::Letterbox;
options.preprocess.normalize.preset = pcie::NormalizePreset::COCO_YOLO;
options.decode_type = pcie::BoxDecodeType::YoloV8;
options.score_threshold = 0.25F;
options.nms_iou_threshold = 0.45F;
options.top_k = 100;
pcie::Model model(kModelPath, options, connection);
model.build(kBuildTimeoutMs);
const auto boxes = parse_boxes(model.run(image, kRunTimeoutMs));
std::cout << "Image + boxdecode detections=" << boxes.size() << '\n';
for (std::size_t index = 0; index < std::min<std::size_t>(boxes.size(), 10); ++index) {
const auto& box = boxes[index];
std::cout << " " << class_name(box.class_id) << " score=" << std::fixed
<< std::setprecision(3) << box.score << " box=(" << box.x << ", " << box.y << ", "
<< box.width << ", " << box.height << ")\n";
}
if (boxes.empty()) {
throw std::runtime_error("no detections passed the score threshold");
}
model.close();

Проаналізуйте тензор BBOX

Перевірте, чи декодування обмежувальних рамок повернуло один заповнений тензор, прочитайте його початкову кількість і відхиліть кількість, яка перевищує корисне навантаження. Кожен наступний 24-байтовий запис потім перетворюється на одне виявлення для виведення.

pcie_host/tutorials/024_run_your_first_model_over_pcie/run_image_boxdecode.cpp
std::vector<Box> parse_boxes(const pcie::TensorList& outputs) {
if (outputs.size() != 1 || outputs[0].data == nullptr || outputs[0].byte_offset < 0) {
throw std::runtime_error("boxdecode must return one populated BBOX tensor");
}
const auto& tensor = outputs[0];
const auto offset = static_cast<std::size_t>(tensor.byte_offset);
if (offset > tensor.size_bytes || tensor.size_bytes - offset < sizeof(std::uint32_t)) {
throw std::runtime_error("BBOX tensor is too small");
}
const auto* bytes = static_cast<const std::uint8_t*>(tensor.data) + offset;
const std::size_t available = tensor.size_bytes - offset;
const std::uint32_t count = read_value<std::uint32_t>(bytes);
constexpr std::size_t record_size = 24;
if (count > (available - 4) / record_size) {
throw std::runtime_error("BBOX detection count exceeds its payload");
}
std::vector<Box> boxes;
boxes.reserve(count);
for (std::uint32_t index = 0; index < count; ++index) {
const auto* record = bytes + 4 + index * record_size;
boxes.push_back({read_value<std::int32_t>(record), read_value<std::int32_t>(record + 4),
read_value<std::int32_t>(record + 8), read_value<std::int32_t>(record + 12),
read_value<float>(record + 16), read_value<std::int32_t>(record + 20)});
}
return boxes;
}

Запуск

Встановіть пакет PCIe host і завантажте пакет для навчального посібника, як описано в Налаштування навчального посібника.. Запустіть наступні команди з кореневої папки розпакованих додаткових матеріалів PCIe:

sima-cli modelzoo get yolo_v8s

Програми вимагають наявності yolo_v8s_mpk.tar.gz у цьому каталозі. Вихідні імена та розташування Model Zoo можуть відрізнятися. Якщо команда не створила саме цей шлях, скопіюйте завантажений архів на місце та перевірте його:

cp /absolute/path/to/downloaded-yolov8s-archive.tar.gz yolo_v8s_mpk.tar.gz
test -f yolo_v8s_mpk.tar.gz

C++ (prebuilt):

./lib/sima-pcie-host/tutorials/tutorial_024_run_tensor_mode
./lib/sima-pcie-host/tutorials/tutorial_024_run_image_mode
./lib/sima-pcie-host/tutorials/tutorial_024_run_image_boxdecode

C++ (build from source):

./build.sh --target tutorial_024_run_tensor_mode
./build.sh --target tutorial_024_run_image_mode
./build.sh --target tutorial_024_run_image_boxdecode

./build/tutorials-standalone/tutorial_024_run_tensor_mode
./build/tutorials-standalone/tutorial_024_run_image_mode
./build/tutorials-standalone/tutorial_024_run_image_boxdecode

Відповідні програми C++ і Python виводять однакові шість необроблених вихідних каналів для режиму тензора та режиму зображення, за якими слідують декодовані люди, автомобілі або інші видимі об’єкти:

Tensor mode raw outputs:
bbox_0 FP32 [80, 80, 64]
...
[OK] 024_run_tensor_mode
Image mode raw outputs:
bbox_0 FP32 [80, 80, 64]
...
[OK] 024_run_image_mode
Image + boxdecode detections=...
person score=... box=(...)
[OK] 024_run_image_boxdecode

За замовчуванням використовується карта 0 і черга 0. Передавайте --card N лише під час використання іншої карти; її адреса керування визначається автоматично.

На практиці

Використовуйте режим тензора, коли ваш застосунок вже створює тензори з точно таким типом даних, формою, розміщенням, порядком кольорів і числовим діапазоном, як зазначено в model.info(). Використовуйте режим зображення, коли застосунок природним чином володіє декодованими пікселями, і ви хочете, щоб карта застосовувала повторювану попередню обробку моделі. Увімкніть декодування обмежувальних рамок, коли застосунку потрібні виявлення, а не необроблені карти ознак.

У кожному режимі використовується один і той же життєвий цикл pcie::Model/pyneatpcie.Model. Змінюються лише ModelOptions і передане корисне навантаження. Продовжуйте з Запустіть асинхронний процес виведення даних PCIe., щоб перекрити надсилання та завершення за допомогою push() і pull().

Повний початковий код

Показати повні програми

Tensor Mode

pcie_host/tutorials/024_run_your_first_model_over_pcie/run_tensor_mode.cpp
// Run YOLOv8s tensor-mode inference over PCIe.
//
// Usage:
// tutorial_024_run_tensor_mode [--card 0]

#include <simaai/neat/pcie/Model.h>

#include <opencv2/imgcodecs.hpp>
#include <opencv2/imgproc.hpp>

#include <algorithm>
#include <cmath>
#include <cstdint>
#include <cstdlib>
#include <filesystem>
#include <iostream>
#include <stdexcept>
#include <string>
#include <utility>
#include <vector>

namespace pcie = simaai::neat::pcie;

namespace {

constexpr int kBuildTimeoutMs = 180000;
constexpr int kRunTimeoutMs = 30000;
constexpr char kModelPath[] = "yolo_v8s_mpk.tar.gz";
constexpr char kImagePath[] = "share/sima-pcie-host/tutorials/assets/street-scene.png";

int parse_card(const int argc, char** argv) {
int card_id = 0;
for (int index = 1; index < argc; ++index) {
const std::string arg = argv[index];
if (arg == "--card" && index + 1 < argc) {
card_id = std::stoi(argv[++index]);
} else if (arg == "-h" || arg == "--help") {
std::cout << "Usage: " << argv[0] << " [--card 0]\n";
std::exit(0);
} else {
throw std::runtime_error("unknown or incomplete argument: " + arg);
}
}
return card_id;
}

std::string shape_string(const std::vector<std::int64_t>& shape) {
std::string text = "[";
for (std::size_t index = 0; index < shape.size(); ++index) {
text += (index == 0 ? "" : ", ") + std::to_string(shape[index]);
}
return text + "]";
}

const char* dtype_name(const pcie::TensorDType dtype) {
switch (dtype) {
case pcie::TensorDType::UInt8:
return "UINT8";
case pcie::TensorDType::Int8:
return "INT8";
case pcie::TensorDType::UInt16:
return "UINT16";
case pcie::TensorDType::Int16:
return "INT16";
case pcie::TensorDType::Int32:
return "INT32";
case pcie::TensorDType::BFloat16:
return "BF16";
case pcie::TensorDType::Float32:
return "FP32";
case pcie::TensorDType::Float64:
return "FP64";
}
return "UNKNOWN";
}

pcie::Tensor make_yolo_tensor(const cv::Mat& bgr, const pcie::TensorInfo& input) {
if (input.shape.size() != 3 || input.shape[2] != 3) {
throw std::runtime_error("expected a three-channel HWC YOLO input");
}
const int target_height = static_cast<int>(input.shape[0]);
const int target_width = static_cast<int>(input.shape[1]);
const double scale = std::min(static_cast<double>(target_width) / bgr.cols,
static_cast<double>(target_height) / bgr.rows);
const int resized_width = std::max(1, static_cast<int>(std::round(bgr.cols * scale)));
const int resized_height = std::max(1, static_cast<int>(std::round(bgr.rows * scale)));

cv::Mat resized;
cv::resize(bgr, resized, cv::Size(resized_width, resized_height));
cv::Mat letterboxed(target_height, target_width, CV_8UC3, cv::Scalar(114, 114, 114));
const int left = (target_width - resized_width) / 2;
const int top = (target_height - resized_height) / 2;
resized.copyTo(letterboxed(cv::Rect(left, top, resized_width, resized_height)));

cv::Mat rgb;
cv::cvtColor(letterboxed, rgb, cv::COLOR_BGR2RGB);
cv::Mat normalized;
rgb.convertTo(normalized, CV_32FC3, 1.0 / 255.0);
if (!normalized.isContinuous()) {
normalized = normalized.clone();
}
const auto* begin = normalized.ptr<float>();
std::vector<float> values(begin, begin + normalized.total() * normalized.channels());
return pcie::Tensor::from_vector(std::move(values), input.shape, input.name);
}

} // namespace

int main(int argc, char** argv) {
try {
const int card_id = parse_card(argc, argv);
if (!std::filesystem::is_regular_file(kModelPath)) {
throw std::runtime_error(std::string("model does not exist: ") + kModelPath);
}
const cv::Mat image = cv::imread(kImagePath, cv::IMREAD_COLOR);
if (image.empty()) {
throw std::runtime_error(std::string("OpenCV could not decode: ") + kImagePath);
}

// CORE LOGIC
pcie::ConnectionOptions connection;
connection.card_id = card_id;
pcie::Model model(kModelPath, {}, connection);
const auto info = model.info();
if (info.inputs.size() != 1) {
throw std::runtime_error("YOLOv8s must expose one input tensor");
}
const pcie::Tensor input = make_yolo_tensor(image, info.inputs[0]);
model.build(kBuildTimeoutMs);
const auto outputs = model.run(input, kRunTimeoutMs);
if (outputs.empty()) {
throw std::runtime_error("tensor mode returned no outputs");
}
std::cout << "Tensor mode raw outputs:\n";
for (const auto& output : outputs) {
std::cout << " " << output.route.name << " " << dtype_name(output.dtype) << " "
<< shape_string(output.shape) << '\n';
}
model.close();

std::cout << "[OK] 024_run_tensor_mode\n";
return 0;
} catch (const std::exception& error) {
std::cerr << "[FAIL] " << error.what() << '\n';
return 1;
}
}

Image Mode

pcie_host/tutorials/024_run_your_first_model_over_pcie/run_image_mode.cpp
// Run YOLOv8s image-mode inference over PCIe.
//
// Usage:
// tutorial_024_run_image_mode [--card 0]

#include <simaai/neat/pcie/Model.h>

#include <opencv2/imgcodecs.hpp>

#include <cstdint>
#include <cstdlib>
#include <filesystem>
#include <iostream>
#include <stdexcept>
#include <string>
#include <vector>

namespace pcie = simaai::neat::pcie;

namespace {

constexpr int kBuildTimeoutMs = 180000;
constexpr int kRunTimeoutMs = 30000;
constexpr char kModelPath[] = "yolo_v8s_mpk.tar.gz";
constexpr char kImagePath[] = "share/sima-pcie-host/tutorials/assets/street-scene.png";

int parse_card(const int argc, char** argv) {
int card_id = 0;
for (int index = 1; index < argc; ++index) {
const std::string arg = argv[index];
if (arg == "--card" && index + 1 < argc) {
card_id = std::stoi(argv[++index]);
} else if (arg == "-h" || arg == "--help") {
std::cout << "Usage: " << argv[0] << " [--card 0]\n";
std::exit(0);
} else {
throw std::runtime_error("unknown or incomplete argument: " + arg);
}
}
return card_id;
}

std::string shape_string(const std::vector<std::int64_t>& shape) {
std::string text = "[";
for (std::size_t index = 0; index < shape.size(); ++index) {
text += (index == 0 ? "" : ", ") + std::to_string(shape[index]);
}
return text + "]";
}

const char* dtype_name(const pcie::TensorDType dtype) {
switch (dtype) {
case pcie::TensorDType::UInt8:
return "UINT8";
case pcie::TensorDType::Int8:
return "INT8";
case pcie::TensorDType::UInt16:
return "UINT16";
case pcie::TensorDType::Int16:
return "INT16";
case pcie::TensorDType::Int32:
return "INT32";
case pcie::TensorDType::BFloat16:
return "BF16";
case pcie::TensorDType::Float32:
return "FP32";
case pcie::TensorDType::Float64:
return "FP64";
}
return "UNKNOWN";
}

} // namespace

int main(int argc, char** argv) {
try {
const int card_id = parse_card(argc, argv);
if (!std::filesystem::is_regular_file(kModelPath)) {
throw std::runtime_error(std::string("model does not exist: ") + kModelPath);
}
const cv::Mat image = cv::imread(kImagePath, cv::IMREAD_COLOR);
if (image.empty()) {
throw std::runtime_error(std::string("OpenCV could not decode: ") + kImagePath);
}

// CORE LOGIC
pcie::ConnectionOptions connection;
connection.card_id = card_id;
pcie::ModelOptions options;
options.preprocess.kind = pcie::InputKind::Image;
options.preprocess.color_convert.input_format = pcie::ColorFormat::BGR;
options.preprocess.color_convert.output_format = pcie::ColorFormat::RGB;
options.preprocess.resize.enable = pcie::AutoFlag::On;
options.preprocess.resize.mode = pcie::ResizeMode::Letterbox;
options.preprocess.normalize.preset = pcie::NormalizePreset::COCO_YOLO;
pcie::Model model(kModelPath, options, connection);
model.build(kBuildTimeoutMs);
const auto outputs = model.run(image, kRunTimeoutMs);
if (outputs.empty()) {
throw std::runtime_error("image mode returned no outputs");
}
std::cout << "Image mode raw outputs:\n";
for (const auto& output : outputs) {
std::cout << " " << output.route.name << " " << dtype_name(output.dtype) << " "
<< shape_string(output.shape) << '\n';
}
model.close();

std::cout << "[OK] 024_run_image_mode\n";
return 0;
} catch (const std::exception& error) {
std::cerr << "[FAIL] " << error.what() << '\n';
return 1;
}
}

Image Boxdecode

pcie_host/tutorials/024_run_your_first_model_over_pcie/run_image_boxdecode.cpp
// Run YOLOv8s image inference with card-side box decode over PCIe.
//
// Usage:
// tutorial_024_run_image_boxdecode [--card 0]

#include <simaai/neat/pcie/Model.h>

#include <opencv2/imgcodecs.hpp>

#include <algorithm>
#include <cstdint>
#include <cstring>
#include <cstdlib>
#include <filesystem>
#include <iomanip>
#include <iostream>
#include <stdexcept>
#include <string>
#include <vector>

namespace pcie = simaai::neat::pcie;

namespace {

constexpr int kBuildTimeoutMs = 180000;
constexpr int kRunTimeoutMs = 30000;
constexpr char kModelPath[] = "yolo_v8s_mpk.tar.gz";
constexpr char kImagePath[] = "share/sima-pcie-host/tutorials/assets/street-scene.png";

int parse_card(const int argc, char** argv) {
int card_id = 0;
for (int index = 1; index < argc; ++index) {
const std::string arg = argv[index];
if (arg == "--card" && index + 1 < argc) {
card_id = std::stoi(argv[++index]);
} else if (arg == "-h" || arg == "--help") {
std::cout << "Usage: " << argv[0] << " [--card 0]\n";
std::exit(0);
} else {
throw std::runtime_error("unknown or incomplete argument: " + arg);
}
}
return card_id;
}

struct Box {
int x;
int y;
int width;
int height;
float score;
int class_id;
};

template <typename T> T read_value(const std::uint8_t* data) {
T value{};
std::memcpy(&value, data, sizeof(value));
return value;
}

std::vector<Box> parse_boxes(const pcie::TensorList& outputs) {
if (outputs.size() != 1 || outputs[0].data == nullptr || outputs[0].byte_offset < 0) {
throw std::runtime_error("boxdecode must return one populated BBOX tensor");
}
const auto& tensor = outputs[0];
const auto offset = static_cast<std::size_t>(tensor.byte_offset);
if (offset > tensor.size_bytes || tensor.size_bytes - offset < sizeof(std::uint32_t)) {
throw std::runtime_error("BBOX tensor is too small");
}
const auto* bytes = static_cast<const std::uint8_t*>(tensor.data) + offset;
const std::size_t available = tensor.size_bytes - offset;
const std::uint32_t count = read_value<std::uint32_t>(bytes);
constexpr std::size_t record_size = 24;
if (count > (available - 4) / record_size) {
throw std::runtime_error("BBOX detection count exceeds its payload");
}
std::vector<Box> boxes;
boxes.reserve(count);
for (std::uint32_t index = 0; index < count; ++index) {
const auto* record = bytes + 4 + index * record_size;
boxes.push_back({read_value<std::int32_t>(record), read_value<std::int32_t>(record + 4),
read_value<std::int32_t>(record + 8), read_value<std::int32_t>(record + 12),
read_value<float>(record + 16), read_value<std::int32_t>(record + 20)});
}
return boxes;
}

std::string class_name(const int class_id) {
switch (class_id) {
case 0:
return "person";
case 1:
return "bicycle";
case 2:
return "car";
case 3:
return "motorcycle";
case 5:
return "bus";
case 7:
return "truck";
default:
return "class_" + std::to_string(class_id);
}
}

} // namespace

int main(int argc, char** argv) {
try {
const int card_id = parse_card(argc, argv);
if (!std::filesystem::is_regular_file(kModelPath)) {
throw std::runtime_error(std::string("model does not exist: ") + kModelPath);
}
const cv::Mat image = cv::imread(kImagePath, cv::IMREAD_COLOR);
if (image.empty()) {
throw std::runtime_error(std::string("OpenCV could not decode: ") + kImagePath);
}

// CORE LOGIC
pcie::ConnectionOptions connection;
connection.card_id = card_id;
pcie::ModelOptions options;
options.preprocess.kind = pcie::InputKind::Image;
options.preprocess.color_convert.input_format = pcie::ColorFormat::BGR;
options.preprocess.color_convert.output_format = pcie::ColorFormat::RGB;
options.preprocess.resize.enable = pcie::AutoFlag::On;
options.preprocess.resize.mode = pcie::ResizeMode::Letterbox;
options.preprocess.normalize.preset = pcie::NormalizePreset::COCO_YOLO;
options.decode_type = pcie::BoxDecodeType::YoloV8;
options.score_threshold = 0.25F;
options.nms_iou_threshold = 0.45F;
options.top_k = 100;
pcie::Model model(kModelPath, options, connection);
model.build(kBuildTimeoutMs);
const auto boxes = parse_boxes(model.run(image, kRunTimeoutMs));
std::cout << "Image + boxdecode detections=" << boxes.size() << '\n';
for (std::size_t index = 0; index < std::min<std::size_t>(boxes.size(), 10); ++index) {
const auto& box = boxes[index];
std::cout << " " << class_name(box.class_id) << " score=" << std::fixed
<< std::setprecision(3) << box.score << " box=(" << box.x << ", " << box.y << ", "
<< box.width << ", " << box.height << ")\n";
}
if (boxes.empty()) {
throw std::runtime_error("no detections passed the score threshold");
}
model.close();

std::cout << "[OK] 024_run_image_boxdecode\n";
return 0;
} catch (const std::exception& error) {
std::cerr << "[FAIL] " << error.what() << '\n';
return 1;
}
}

Джерело