跳至主要内容

在 PCIe 上執行您的第一個模型

欄位
類別PCIe 協同處理
難度初級
預估閱讀時間15 minutes
標籤PCIe, inference, tensor, image, detection

使用相同的 YOLOv8s 檔案和 640x480 街景執行三個獨立的程式。每個程式都演示一種模式,同步使用佇列 0,並關閉一個模型。這可確保每個範例都足夠簡短,可以單獨複製。

操作指南

執行模型準備好的張量

主機會將圖像調整大小,使其符合模型報告的 [640, 640, 3] 輸入,將 BGR 轉換為 RGB,並將像素縮放到 [0, 1]Model.run() 會傳送該 FP32 張量,而無需卡片端圖像預處理,並列印所有六個原始 YOLO 輸出路由。

pcie_host/tutorials/024_run_your_first_model_over_pcie/run_tensor_mode.cpp
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 到 RGB 轉換和正規化。該程式會列印六個原始輸出路由名稱和形狀,以便您可以將其與張量模式進行比較。

pcie_host/tutorials/024_run_your_first_model_over_pcie/run_image_mode.cpp
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)。該範例會解析並列印源圖像座標中的前十條記錄。

pcie_host/tutorials/024_run_your_first_model_over_pcie/run_image_boxdecode.cpp
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 位元組記錄都會轉換為一個檢測,以便進行列印。

pcie_host/tutorials/024_run_your_first_model_over_pcie/run_image_boxdecode.cpp
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 程式會列印張量模式和圖像模式的相同六個原始輸出合約,然後是已解碼的人、汽車或其他可見物件:

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 和提交的有效負載會發生變化。繼續使用 非同步執行 PCIe 推論,以使用 push()pull() 重疊提交和完成。

完整原始碼

顯示完整原始碼程式

Tensor Mode

pcie_host/tutorials/024_run_your_first_model_over_pcie/run_tensor_mode.cpp
// 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

pcie_host/tutorials/024_run_your_first_model_over_pcie/run_image_mode.cpp
// 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

pcie_host/tutorials/024_run_your_first_model_over_pcie/run_image_boxdecode.cpp
// 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;
}
}

來源