PCIe를 통해 첫 번째 모델 실행
| 필드 | 값 |
|---|---|
| 범주 | PCIe 코프로세싱 |
| 난이도 | 초급 |
| 예상 소요 시간 | 15 minutes |
| 레이블 | PCIe, inference, tensor, image, detection |
동일한 YOLOv8s 아카이브와 640x480 거리 장면을 사용하여 세 개의 독립적인 프로그램을 실행합니다. 각 프로그램은 하나의 모드를 시연하고, 큐 0을 동기적으로 사용하며, 하나의 모델을 닫습니다. 이렇게 하면 각 예제를 복사하여 사용할 수 있을 만큼 짧게 유지할 수 있습니다.
둘러보기
모델에 사용할 수 있는 텐서 실행
호스트는 이미지를 모델에서 보고한 [640, 640, 3] 입력에 맞게 조정하고, BGR을 RGB로 변환하고, 픽셀을 [0, 1]로 조정합니다. Model.run()은 해당 FP32 텐서를 보내고 카드 측 이미지 전처리를 수행하지 않으며 6개의 원시 YOLO 출력 경로를 모두 출력합니다.
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-to-RGB 변환 및 정규화를 수행합니다. 프로그램은 6개의 원시 출력 경로 이름과 모양을 출력하므로 텐서 모드와 비교할 수 있습니다.
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)가 포함됩니다. 예제는 원본 이미지 좌표에서 처음 10개의 레코드를 구문 분석하고 출력합니다.
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바이트 레코드는 출력에 사용할 수 있도록 하나의 감지로 변환됩니다.
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 호스트 패키지를 설치하고 튜토리얼 설정에 설명된 대로 튜토리얼 번들을 다운로드합니다. 추출된 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 프로그램은 텐서 모드와 이미지 모드에 대해 동일한 6개의 원시 출력 계약을 출력한 다음 디코딩된 사람, 자동차 또는 기타 보이는 개체를 출력합니다.
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()에서 보고하는 dtype, 모양, 레이아웃, 색상 순서 및 숫자 범위를 생성하는 경우 텐서 모드를 사용합니다. 애플리케이션에서 자연스럽게 디코딩된 픽셀을 소유하고 카드가 반복 가능한 모델 전처리를 적용하도록 하려는 경우 이미지 모드를 사용합니다. 애플리케이션에서 원시 기능 맵이 아닌 감지가 필요한 경우 박스 디코드를 활성화합니다.
모든 모드는 동일한 pcie::Model/pyneatpcie.Model 라이프사이클을 사용합니다. ModelOptions 및 제출된 페이로드만 변경됩니다. push() 및 pull()을 사용하여 제출과 완료를 겹치도록 PCIe 추론을 비동기적으로 실행합니다.로 계속 진행합니다.
전체 소스
전체 소스 프로그램 표시
Tensor Mode
// 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
// 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
// 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;
}
}