PCIe による共同処理
PCIe による共同処理では、アプリケーションの制御と入力の準備をホストマシン上で行い、接続された Modalix PCIe カードでモデルを実行します。この例では、ホストが画像を読み込み、サイズを変更し、正規化し、準備されたテンソルをカードに送信して ResNet-50 の推論を実行し、返された分類結果を出力します。
始める前に
次のものが必要です。
- ホストマシンに Neat PCIe ホストパッケージ をインストールすること。
- カードに互換性のある Neat Library リリースをインストールすること。
- ホストからアクセス可能なカード管理インターフェース。カード 0 はデフォルトで
10.0.0.2を使用します。
モデルと画像の取得
ホストマシンに作業ディレクトリを作成し、Model Zoo から ResNet-50 をダウンロードします。
mkdir -p pcie-host-quickstart/assetscd pcie-host-quickstartsima-cli modelzoo get resnet_50ダウンロードしたアーカイブをこのディレクトリに、resnet_50.tar.gzとして配置します。
ラブラドール犬のサンプル画像をダウンロードし、assets/sample.pngとして保存します。
この画像は、Elfが撮影し、DjmirkoとFT2が修正した黄色のラブラドール犬。新品のように見える。jpgを基にしており、CC BY-SA 3.0のライセンスで提供されています。
作業ディレクトリには、以下のファイルが含まれているはずです。
pcie-host-quickstart/
├── assets/
│ └── sample.png
└── resnet_50.tar.gz
アプリケーションの作成
PythonまたはC++を選択し、作業ディレクトリにアプリケーションを作成します。
pcie_host.cpp を作成します。
pcie_host.cpp
#include <simaai/neat/pcie/Model.h>
#include <opencv2/imgcodecs.hpp>
#include <opencv2/imgproc.hpp>
#include <algorithm>
#include <cstdint>
#include <iostream>
#include <iterator>
#include <stdexcept>
#include <string>
#include <utility>
#include <vector>
namespace pcie = simaai::neat::pcie;
pcie::Tensor load_input(const std::string& path,
const std::string& route_name) {
cv::Mat bgr = cv::imread(path, cv::IMREAD_COLOR);
if (bgr.empty())
throw std::runtime_error("failed to read image: " + path);
cv::resize(bgr, bgr, cv::Size(224, 224), 0, 0, cv::INTER_AREA);
cv::Mat rgb;
cv::cvtColor(bgr, rgb, cv::COLOR_BGR2RGB);
if (!rgb.isContinuous())
rgb = rgb.clone();
constexpr float mean[] = {0.485F, 0.456F, 0.406F};
constexpr float stddev[] = {0.229F, 0.224F, 0.225F};
std::vector<float> input(rgb.total() * rgb.channels());
for (int row = 0; row < rgb.rows; ++row) {
const auto* pixels = rgb.ptr<std::uint8_t>(row);
for (int col = 0; col < rgb.cols; ++col) {
for (int channel = 0; channel < 3; ++channel) {
const std::size_t index =
(static_cast<std::size_t>(row) * rgb.cols + col) * 3 + channel;
const float value = pixels[col * 3 + channel] / 255.0F;
input[index] = (value - mean[channel]) / stddev[channel];
}
}
}
return pcie::Tensor::from_vector(
std::move(input), {rgb.rows, rgb.cols, rgb.channels()}, route_name);
}
int main() {
pcie::ConnectionOptions connection;
connection.card_host = "10.0.0.2";
connection.card_id = 0;
connection.queue = 0;
pcie::Model model("resnet_50.tar.gz", {}, connection);
const pcie::ModelInfo info = model.info();
if (info.inputs.empty())
throw std::runtime_error("model reports no inputs");
model.build(/*readiness_timeout_ms=*/180000);
pcie::TensorList outputs = model.run(
load_input("assets/sample.png", info.inputs[0].name),
/*timeout_ms=*/30000);
if (outputs.empty() || outputs[0].dtype != pcie::TensorDType::Float32 ||
outputs[0].data == nullptr || outputs[0].size_bytes == 0)
throw std::runtime_error("expected one FP32 classification output");
const auto* scores = static_cast<const float*>(outputs[0].data);
const std::size_t count = outputs[0].size_bytes / sizeof(float);
const auto best = std::max_element(scores, scores + count);
std::cout << "output: " << outputs[0].route.name << " [";
for (std::size_t index = 0; index < outputs[0].shape.size(); ++index) {
if (index != 0)
std::cout << ", ";
std::cout << outputs[0].shape[index];
}
std::cout << "]\n";
std::cout << "top1: " << std::distance(scores, best) << '\n';
model.close();
}
CMakeLists.txt を作成します。
CMakeLists.txt
cmake_minimum_required(VERSION 3.16)
project(pcie_host LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
find_package(SimaPCIeHost REQUIRED CONFIG)
find_package(OpenCV REQUIRED COMPONENTS core imgcodecs imgproc)
add_executable(pcie_host pcie_host.cpp)
target_include_directories(pcie_host PRIVATE ${OpenCV_INCLUDE_DIRS})
target_link_libraries(
pcie_host
PRIVATE SimaPCIeHost::sima_neat_pcie_host ${OpenCV_LIBS}
)