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

Асинхронний запуск PCIe-інференсу

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

У цьому навчальному посібнику повторно використовується конфігурація YOLOv8s (зображення плюс декодування обмежувальних рамок) і зображення міської сцени розміром 640x480 з навчального посібника 024. Він надсилає одне й те саме зображення кілька разів, щоб зберігання та декодування зображень не спотворювали вимірювання PCIe.

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

Налаштування однієї моделі виявлення​

Завантажте зображення один раз, налаштуйте попередню обробку COCO на стороні відеокарти та декодування обмежувальних рамок YOLOv8, а потім створіть одну Model у черзі 0. Відсутні файли та помилки запуску відеокарти зупиняють програму до початку вимірювання.

pcie_host/tutorials/025_run_pcie_inference_async/run_pcie_inference_async.cpp
const Args args = parse_args(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);
}
pcie::ConnectionOptions connection;
connection.card_id = args.card_id;
pcie::Model model(kModelPath, detection_options(), connection);
model.build(kBuildTimeoutMs);

Підготовка конвеєра​

Запустіть кілька повних виявлень, не вимірюючи час. Підготовка усуває ефекти запуску моделі та першого буфера з отриманих даних про навантаження.

pcie_host/tutorials/025_run_pcie_inference_async/run_pcie_inference_async.cpp
for (int index = 0; index < kWarmupFrames; ++index) {
(void)detection_count(model.run(image, kPullTimeoutMs));
}

Одночасна відправка та отримання​

Один потік надсилає зображення за допомогою push(), а інший отримує вихідні дані BBOX за допомогою pull() з обмеженим часом очікування. Невеликий FIFO, що належить застосунку, зберігає час початку кожної відправки. Будь-яке відхилення, перевищення часу очікування або неправильний результат закриває модель і активує інший потік.

Приклад покладається лише на звичайну поведінку керування потоком Model; у застосунку немає налаштування глибини черги.

pcie_host/tutorials/025_run_pcie_inference_async/run_pcie_inference_async.cpp
const BenchmarkResult result = measure(model, image, kMeasuredFrames);

Звіт про виконану роботу​

Зупиніть вимірювання часу лише після того, як обидва потоки завершать роботу, і всі прийняті зображення будуть отримані. Кількість кадрів в секунду використовує кількість завершених вихідних даних. Середня затримка вимірюється від кожної спроби відправки до надходження відповідного впорядкованого результату.

pcie_host/tutorials/025_run_pcie_inference_async/run_pcie_inference_async.cpp
std::cout << "completed=" << result.completed << '\n';
std::cout << std::fixed << std::setprecision(2) << "elapsed_seconds=" << result.elapsed_seconds
<< '\n'
<< "throughput_fps=" << result.completed / result.elapsed_seconds << '\n'
<< "average_latency_ms=" << result.average_latency_ms << '\n'
<< "total_detections=" << result.total_detections << '\n';

Запуск​

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

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

Запустіть Python:

source ~/pyneatpcie/bin/activate
python3 share/sima-pcie-host/tutorials/025_run_pcie_inference_async/run_pcie_inference_async.py

Запустіть попередньо зібраний навчальний посібник C++:

./lib/sima-pcie-host/tutorials/tutorial_025_run_pcie_inference_async

Або перезберіть його:

./build.sh --target tutorial_025_run_pcie_inference_async
./build/tutorials-standalone/tutorial_025_run_pcie_inference_async

Точний час залежить від хоста та відеокарти, але обидві програми використовують однакові межі вимірювання та виводять:

completed=1000
elapsed_seconds=...
throughput_fps=...
average_latency_ms=...
total_detections=...
[OK] 025_run_pcie_inference_async

Навчальний посібник завжди починає з п’яти кадрів, а потім вимірює 1000 завершених кадрів. Передавайте --card N лише під час використання іншої відеокарти.

На практиці​

Забезпечте збалансованість відправки та отримання. Якщо застосунок нескінченно надсилає дані, не отримуючи їх, нормальний механізм зворотного зв’язку зрештою сповільнить відправку. Виділений споживач також спрощує виявлення збоїв: обмежений час очікування ідентифікує результат, який не надходить, і закриття моделі звільняє чергу 0, навіть якщо виробник чекає.

Для репрезентативного тестування замініть повторюваний кадр фіксованим набором зображень і виключіть читання з диска з вимірюваного часового проміжку. Продовжуйте з Запустіть кілька моделей., щоб одночасно запустити дві різні моделі.

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

Показати повні програми
pcie_host/tutorials/025_run_pcie_inference_async/run_pcie_inference_async.cpp
// Measure completed YOLOv8s detections with asynchronous PCIe push/pull.
//
// Usage:
// tutorial_025_run_pcie_inference_async

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

#include <opencv2/imgcodecs.hpp>

#include <atomic>
#include <chrono>
#include <cstdint>
#include <cstring>
#include <cstdlib>
#include <deque>
#include <exception>
#include <filesystem>
#include <iomanip>
#include <iostream>
#include <mutex>
#include <numeric>
#include <stdexcept>
#include <string>
#include <thread>
#include <vector>

namespace pcie = simaai::neat::pcie;

namespace {

using Clock = std::chrono::steady_clock;
constexpr int kBuildTimeoutMs = 180000;
constexpr int kPullTimeoutMs = 30000;
constexpr int kWarmupFrames = 5;
constexpr int kMeasuredFrames = 1000;
constexpr char kModelPath[] = "yolo_v8s_mpk.tar.gz";
constexpr char kImagePath[] = "share/sima-pcie-host/tutorials/assets/street-scene.png";

struct Args {
int card_id = 0;
};

std::string require_value(int argc, char** argv, int& index, const char* option) {
if (index + 1 >= argc) {
throw std::runtime_error(std::string("missing value for ") + option);
}
return argv[++index];
}

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

pcie::ModelOptions detection_options() {
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;
return options;
}

std::uint32_t detection_count(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");
}
std::uint32_t count = 0;
std::memcpy(&count, static_cast<const std::uint8_t*>(tensor.data) + offset, sizeof(count));
constexpr std::size_t record_size = 24;
if (count > (tensor.size_bytes - offset - 4) / record_size) {
throw std::runtime_error("BBOX detection count exceeds its payload");
}
return count;
}

struct BenchmarkResult {
std::size_t completed = 0;
double elapsed_seconds = 0.0;
double average_latency_ms = 0.0;
std::uint64_t total_detections = 0;
};

BenchmarkResult measure(pcie::Model& model, const cv::Mat& image, const int frame_count) {
std::deque<Clock::time_point> submitted;
std::mutex submitted_mutex;
std::mutex failure_mutex;
std::exception_ptr first_failure;
std::atomic<bool> cancelled = false;
std::vector<double> latency_ms;
latency_ms.reserve(static_cast<std::size_t>(frame_count));
std::uint64_t total_detections = 0;

auto fail = [&](std::exception_ptr failure) {
{
std::lock_guard<std::mutex> lock(failure_mutex);
if (!first_failure) {
first_failure = std::move(failure);
}
}
cancelled = true;
model.close();
};

const auto benchmark_start = Clock::now();
std::thread producer([&] {
try {
for (int index = 0; index < frame_count && !cancelled; ++index) {
const auto started = Clock::now();
{
std::lock_guard<std::mutex> lock(submitted_mutex);
submitted.push_back(started);
}
if (!model.push(image)) {
throw std::runtime_error("push rejected frame " + std::to_string(index));
}
}
} catch (...) {
fail(std::current_exception());
}
});

std::thread consumer([&] {
try {
for (int index = 0; index < frame_count && !cancelled; ++index) {
auto outputs = model.pull(kPullTimeoutMs);
if (!outputs) {
throw std::runtime_error("pull timed out for frame " + std::to_string(index));
}
Clock::time_point started;
{
std::lock_guard<std::mutex> lock(submitted_mutex);
if (submitted.empty()) {
throw std::runtime_error("completion arrived without a submission record");
}
started = submitted.front();
submitted.pop_front();
}
total_detections += detection_count(*outputs);
latency_ms.push_back(
std::chrono::duration<double, std::milli>(Clock::now() - started).count());
}
} catch (...) {
fail(std::current_exception());
}
});

producer.join();
consumer.join();
const auto benchmark_end = Clock::now();
if (first_failure) {
std::rethrow_exception(first_failure);
}
if (latency_ms.size() != static_cast<std::size_t>(frame_count)) {
throw std::runtime_error("not every submitted frame completed");
}

BenchmarkResult result;
result.completed = latency_ms.size();
result.elapsed_seconds = std::chrono::duration<double>(benchmark_end - benchmark_start).count();
result.average_latency_ms =
std::accumulate(latency_ms.begin(), latency_ms.end(), 0.0) / latency_ms.size();
result.total_detections = total_detections;
return result;
}

} // namespace

int main(int argc, char** argv) {
try {
const Args args = parse_args(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);
}
pcie::ConnectionOptions connection;
connection.card_id = args.card_id;
pcie::Model model(kModelPath, detection_options(), connection);
model.build(kBuildTimeoutMs);

for (int index = 0; index < kWarmupFrames; ++index) {
(void)detection_count(model.run(image, kPullTimeoutMs));
}

// CORE LOGIC
const BenchmarkResult result = measure(model, image, kMeasuredFrames);

std::cout << "completed=" << result.completed << '\n';
std::cout << std::fixed << std::setprecision(2) << "elapsed_seconds=" << result.elapsed_seconds
<< '\n'
<< "throughput_fps=" << result.completed / result.elapsed_seconds << '\n'
<< "average_latency_ms=" << result.average_latency_ms << '\n'
<< "total_detections=" << result.total_detections << '\n';

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

Джерело​