본문으로 건너뛰기

여러 모델 실행

필드
범주PCIe 코프로세싱
난이도초급
예상 소요 시간15 minutes
레이블PCIe, queues, concurrency, classification, detection

두 모델은 의도적으로 서로 다른 이미지를 사용합니다. ResNet-50은 선명한 래브라도어 개의 사진을 분류하는 반면, YOLOv8s는 붐비는 거리의 장면에서 사람과 자동차를 감지합니다.

둘러보기

모델별 이미지를 로드합니다.

각 큐에 모델 아카이브와 패키징된 에셋을 모두 검증하고, 큐에 추가하기 전에 디코딩합니다. 이미지를 분리하면 각 결과가 의미를 가지게 되고, 분류 포트레이트를 객체 감지 작업에 사용하는 것을 방지할 수 있습니다.

pcie_host/tutorials/026_run_multiple_models/run_multiple_models.cpp
const Args args = parse_args(argc, argv);
for (const auto* model : {kResnetModelPath, kYoloModelPath}) {
if (!std::filesystem::is_regular_file(model)) {
throw std::runtime_error(std::string("model does not exist: ") + model);
}
}
const cv::Mat labrador = cv::imread(kResnetImagePath, cv::IMREAD_COLOR);
const cv::Mat street = cv::imread(kYoloImagePath, cv::IMREAD_COLOR);
if (labrador.empty() || street.empty()) {
throw std::runtime_error("OpenCV could not decode one of the input images");
}

각 큐에 하나의 모델을 할당합니다.

두 개의 일반적인 Model 객체. ResNet-50을 사용하여 ImageNet 이미지 전처리 작업을 큐 0에서 수행하고, YOLOv8s를 사용하여 COCO 이미지 전처리 작업과 박스 디코딩 작업을 큐 1에서 수행하도록 구성합니다. 빌드 오류가 발생하면 오류가 발생한 큐와 모델을 식별합니다. 두 번째 빌드가 실패하면 이미 빌드된 모델이 닫힙니다.

pcie_host/tutorials/026_run_multiple_models/run_multiple_models.cpp
pcie::Model resnet(kResnetModelPath, classification_options(),
connection_for(args, kResnetQueue));
pcie::Model yolo(kYoloModelPath, detection_options(), connection_for(args, kYoloQueue));
try {
resnet.build(kBuildTimeoutMs);
} catch (const std::exception& error) {
throw std::runtime_error("queue " + std::to_string(kResnetQueue) +
" failed to build ResNet-50: " + error.what());
}
try {
yolo.build(kBuildTimeoutMs);
} catch (const std::exception& error) {
resnet.close();
throw std::runtime_error("queue " + std::to_string(kYoloQueue) +
" failed to build YOLOv8s: " + error.what());
}

두 큐를 동시에 실행합니다.

각 모델에 대해 별도의 호스트 스레드에서 차단 이미지 추론을 하나씩 시작합니다. 각 호출은 여전히 간단한 동기식 모델을 사용합니다. run 행동은 동일하지만, 통화가 겹치는 이유는 서로 다른 물리적 대기열을 대상으로 하기 때문입니다.

pcie_host/tutorials/026_run_multiple_models/run_multiple_models.cpp
pcie::TensorList classification;
pcie::TensorList detections;
try {
auto classification_future =
std::async(std::launch::async, [&] { return resnet.run(labrador, kRunTimeoutMs); });
auto detection_future =
std::async(std::launch::async, [&] { return yolo.run(street, kRunTimeoutMs); });
classification = classification_future.get();
detections = detection_future.get();
} catch (...) {
yolo.close();
resnet.close();
throw;
}

각 결과를 독립적으로 해석하십시오.

큐 0은 하나의 FP32 분류 텐서를 반환하고 가장 높은 점수를 받은 ImageNet 클래스를 출력합니다. 큐 1은 디코딩된 BBOX 레코드를 반환하고 감지 클래스, 신뢰도 및 원본 이미지 좌표를 출력합니다. 두 모델 중 하나를 닫으면 해당 모델에 할당된 큐만 해제됩니다.

pcie_host/tutorials/026_run_multiple_models/run_multiple_models.cpp
const int top1 = top_class(classification);
const auto boxes = parse_boxes(detections);
std::cout << "queue=" << kResnetQueue
<< " model=resnet_50 output_shape=" << shape_string(classification[0].shape)
<< " top1=" << top1;
if (top1 == 208) {
std::cout << " (Labrador retriever)";
}
std::cout << '\n';
std::cout << "queue=" << kYoloQueue << " model=yolo_v8s detections=" << boxes.size() << '\n';
for (std::size_t index = 0; index < std::min<std::size_t>(boxes.size(), 5); ++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("YOLOv8s returned no street-scene detections");
}

실행

PCIe 호스트 패키지를 설치하고, 설명서에 나와 있는 대로 튜토리얼 번들을 다운로드하세요. 튜토리얼 설정추출된 PCIe 추가 루트에서 두 모델을 모두 다운로드합니다.

sima-cli modelzoo get resnet_50
sima-cli modelzoo get yolo_v8s

이 프로그램은 정확한 경로를 필요로 합니다. resnet_50_mpk.tar.gz 그리고 yolo_v8s_mpk.tar.gz 이 디렉터리 안에 있습니다. 만약 Model Zoo 다른 이름이나 위치를 사용한 경우, 다운로드한 파일을 해당 위치에 복사하고 파일이 올바른지 확인합니다.

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

Python 실행:

source ~/pyneatpcie/bin/activate
python3 share/sima-pcie-host/tutorials/026_run_multiple_models/run_multiple_models.py

미리 빌드된 C++ 튜토리얼을 실행합니다.

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

또는 다시 구축합니다.

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

문서화된 모델과 에셋을 사용하면 두 버전 모두 다음과 유사한 결과를 출력합니다.

queue=0 model=resnet_50 output_shape=[1, 1000] top1=208 (Labrador retriever)
queue=1 model=yolo_v8s detections=...
person score=... box=(...)
[OK] 026_run_multiple_models

튜토리얼에서는 ResNet-50을 큐 0에, YOLOv8s를 큐 1에 할당하도록 의도적으로 설정합니다. 통과 --card N 다른 카드를 사용할 때만 해당됩니다.

실전 활용

큐 할당은 애플리케이션 리소스 결정입니다. 두 개의 활성 모델은 동일한 물리적 큐를 가질 수 없습니다. 작업을 시작하기 전에 모델을 구축하고, 오류 발생 시 특정 큐를 보고하며, 정상 및 오류 경로 모두에서 성공적으로 구축된 모든 모델을 닫습니다. 분리합니다. Model 인스턴스는 결과를 보관하고 오류를 격리하면서도 분석하기 쉽도록 유지합니다.

배포 진단을 위해 다음 단계를 진행합니다. PCIe 모델 워크플로 그리고 문제 해결 안내서.

전체 소스

전체 소스 프로그램 표시
pcie_host/tutorials/026_run_multiple_models/run_multiple_models.cpp
// Run ResNet-50 and YOLOv8s concurrently on two PCIe queues.
//
// Usage:
// tutorial_026_run_multiple_models

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

#include <opencv2/imgcodecs.hpp>

#include <algorithm>
#include <cstdint>
#include <cstring>
#include <cstdlib>
#include <filesystem>
#include <future>
#include <iomanip>
#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 int kResnetQueue = 0;
constexpr int kYoloQueue = 1;
constexpr char kResnetModelPath[] = "resnet_50_mpk.tar.gz";
constexpr char kYoloModelPath[] = "yolo_v8s_mpk.tar.gz";
constexpr char kResnetImagePath[] = "share/sima-pcie-host/tutorials/assets/labrador.jpg";
constexpr char kYoloImagePath[] = "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::ConnectionOptions connection_for(const Args& args, const int queue) {
pcie::ConnectionOptions connection;
connection.card_id = args.card_id;
connection.queue = queue;
return connection;
}

pcie::ModelOptions classification_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::Stretch;
options.preprocess.normalize.preset = pcie::NormalizePreset::ImageNet;
return options;
}

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::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 + "]";
}

int top_class(const pcie::TensorList& outputs) {
if (outputs.size() != 1 || outputs[0].dtype != pcie::TensorDType::Float32 ||
outputs[0].data == nullptr || outputs[0].byte_offset < 0) {
throw std::runtime_error("ResNet-50 must return one populated FP32 tensor");
}
const auto& output = outputs[0];
const auto offset = static_cast<std::size_t>(output.byte_offset);
if (offset > output.size_bytes || (output.size_bytes - offset) % sizeof(float) != 0) {
throw std::runtime_error("ResNet-50 returned an invalid output span");
}
const auto* scores =
reinterpret_cast<const float*>(static_cast<const std::uint8_t*>(output.data) + offset);
const std::size_t count = (output.size_bytes - offset) / sizeof(float);
return static_cast<int>(std::distance(scores, std::max_element(scores, scores + count)));
}

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("YOLOv8 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 < 4) {
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 auto 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 Args args = parse_args(argc, argv);
for (const auto* model : {kResnetModelPath, kYoloModelPath}) {
if (!std::filesystem::is_regular_file(model)) {
throw std::runtime_error(std::string("model does not exist: ") + model);
}
}
const cv::Mat labrador = cv::imread(kResnetImagePath, cv::IMREAD_COLOR);
const cv::Mat street = cv::imread(kYoloImagePath, cv::IMREAD_COLOR);
if (labrador.empty() || street.empty()) {
throw std::runtime_error("OpenCV could not decode one of the input images");
}

pcie::Model resnet(kResnetModelPath, classification_options(),
connection_for(args, kResnetQueue));
pcie::Model yolo(kYoloModelPath, detection_options(), connection_for(args, kYoloQueue));
try {
resnet.build(kBuildTimeoutMs);
} catch (const std::exception& error) {
throw std::runtime_error("queue " + std::to_string(kResnetQueue) +
" failed to build ResNet-50: " + error.what());
}
try {
yolo.build(kBuildTimeoutMs);
} catch (const std::exception& error) {
resnet.close();
throw std::runtime_error("queue " + std::to_string(kYoloQueue) +
" failed to build YOLOv8s: " + error.what());
}

// CORE LOGIC
pcie::TensorList classification;
pcie::TensorList detections;
try {
auto classification_future =
std::async(std::launch::async, [&] { return resnet.run(labrador, kRunTimeoutMs); });
auto detection_future =
std::async(std::launch::async, [&] { return yolo.run(street, kRunTimeoutMs); });
classification = classification_future.get();
detections = detection_future.get();
} catch (...) {
yolo.close();
resnet.close();
throw;
}

const int top1 = top_class(classification);
const auto boxes = parse_boxes(detections);
std::cout << "queue=" << kResnetQueue
<< " model=resnet_50 output_shape=" << shape_string(classification[0].shape)
<< " top1=" << top1;
if (top1 == 208) {
std::cout << " (Labrador retriever)";
}
std::cout << '\n';
std::cout << "queue=" << kYoloQueue << " model=yolo_v8s detections=" << boxes.size() << '\n';
for (std::size_t index = 0; index < std::min<std::size_t>(boxes.size(), 5); ++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("YOLOv8s returned no street-scene detections");
}

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

소스