여러 모델 실행
| 필드 | 값 |
|---|---|
| 범주 | PCIe 코프로세싱 |
| 난이도 | 초급 |
| 예상 소요 시간 | 15 minutes |
| 레이블 | PCIe, queues, concurrency, classification, detection |
두 모델은 의도적으로 서로 다른 이미지를 사용합니다. ResNet-50은 선명한 래브라도어 개의 사진을 분류하는 반면, YOLOv8s는 붐비는 거리의 장면에서 사람과 자동차를 감지합니다.
둘러보기
모델별 이미지를 로드합니다.
각 큐에 모델 아카이브와 패키징된 에셋을 모두 검증하고, 큐에 추가하기 전에 디코딩합니다. 이미지를 분리하면 각 결과가 의미를 가지게 되고, 분류 포트레이트를 객체 감지 작업에 사용하는 것을 방지할 수 있습니다.
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::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::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 레코드를 반환하고 감지 클래스, 신뢰도 및 원본 이미지 좌표를 출력합니다. 두 모델 중 하나를 닫으면 해당 모델에 할당된 큐만 해제됩니다.
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 모델 워크플로 그리고 문제 해결 안내서.
전체 소스
전체 소스 프로그램 표시
// 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;
}
}