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

Асинхронний запуск 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;
}
}

Джерело