Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
128 changes: 126 additions & 2 deletions onnxruntime/core/platform/linux/device_discovery.cc
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// Licensed under the MIT License.

#include "core/platform/device_discovery.h"
#include "core/platform/linux/npu_device_discovery.h"
#include "core/platform/linux/pci_device_discovery.h"

#include <filesystem>
Expand Down Expand Up @@ -123,7 +124,7 @@

std::error_code error_code;
auto pci_bus_id_path = std::filesystem::canonical(sysfs_path / "device", error_code); // resolves symlink to PCI bus id, e.g. 0000:65:00.0
ORT_RETURN_IF_ERROR(ErrorCodeToStatus(error_code, sysfs_path / "device", "Getting PCI bus id from DRM device by resolving symlink"));
ORT_RETURN_IF_ERROR(ErrorCodeToStatus(error_code, sysfs_path / "device", "Getting PCI bus id from sysfs device by resolving symlink"));

auto pci_bus_id_filename = pci_bus_id_path.filename();
if (std::regex_match(pci_bus_id_filename.string(), pci_bus_id_regex)) {
Expand Down Expand Up @@ -269,6 +270,7 @@
namespace {

constexpr const char* kSysfsPciDevicesPath = "/sys/bus/pci/devices";
constexpr const char* kSysfsAccelPath = "/sys/class/accel";

Status GetGpuDevices(std::vector<OrtHardwareDevice>& gpu_devices_out) {
std::vector<GpuSysfsPathInfo> gpu_sysfs_path_infos{};
Expand Down Expand Up @@ -315,6 +317,119 @@

} // namespace

namespace npu_device_discovery {

Status DetectNpuSysfsPaths(const fs::path& sysfs_accel_path,
std::vector<NpuSysfsPathInfo>& npu_sysfs_paths_out) {
std::error_code error_code{};

const bool sysfs_accel_path_exists = fs::exists(sysfs_accel_path, error_code);
ORT_RETURN_IF_ERROR(ErrorCodeToStatus(error_code, sysfs_accel_path, "Checking existence of accel sysfs path"));

if (!sysfs_accel_path_exists) {
npu_sysfs_paths_out = {};
return Status::OK();
}

const auto detect_accel_path = [](const fs::path& sysfs_path, size_t& accel_idx) -> bool {
const auto filename = sysfs_path.filename();
const auto filename_str = std::string_view{filename.native()};

// Look for a filename matching "accelN". N is a number.
constexpr std::string_view prefix = "accel";
if (filename_str.find(prefix) != 0) {
return false;
}

size_t parsed_accel_idx{};
if (!TryParseStringWithClassicLocale<size_t>(filename_str.substr(prefix.size()), parsed_accel_idx)) {
return false;
}

accel_idx = parsed_accel_idx;
return true;
};

std::vector<NpuSysfsPathInfo> npu_sysfs_paths{};

auto dir_iterator = fs::directory_iterator{sysfs_accel_path, error_code};
ORT_RETURN_IF_ERROR(ErrorCodeToStatus(error_code, sysfs_accel_path, "Iterating over accel sysfs devices"));

for (const auto& dir_item : dir_iterator) {
const auto& dir_item_path = dir_item.path();

if (size_t accel_idx{}; detect_accel_path(dir_item_path, accel_idx)) {
NpuSysfsPathInfo path_info{};
path_info.accel_idx = accel_idx;
path_info.path = dir_item_path;
npu_sysfs_paths.emplace_back(std::move(path_info));
}
}

npu_sysfs_paths_out = std::move(npu_sysfs_paths);
return Status::OK();
}

Status GetNpuDeviceFromSysfs(const NpuSysfsPathInfo& path_info,
OrtHardwareDevice& npu_device_out) {
OrtHardwareDevice npu_device{};

const auto& sysfs_path = path_info.path;

uint16_t vendor_id{};
const auto vendor_id_path = sysfs_path / "device" / "vendor";
ORT_RETURN_IF_ERROR(ReadValueFromFile(vendor_id_path, vendor_id));
npu_device.vendor_id = vendor_id;

uint16_t device_id{};
const auto device_id_path = sysfs_path / "device" / "device";
ORT_RETURN_IF_ERROR(ReadValueFromFile(device_id_path, device_id));
npu_device.device_id = device_id;

npu_device.metadata.Add("accel_idx", MakeString(path_info.accel_idx));

std::optional<std::string> pci_bus_id;
ORT_RETURN_IF_ERROR(GetPciBusId(sysfs_path, pci_bus_id));
if (pci_bus_id) {
npu_device.metadata.Add("pci_bus_id", std::move(*pci_bus_id));
}

npu_device.type = OrtHardwareDeviceType_NPU;

npu_device_out = std::move(npu_device);

return Status::OK();
}

} // namespace npu_device_discovery

namespace {

Status GetNpuDevices(std::vector<OrtHardwareDevice>& npu_devices_out) {
std::vector<npu_device_discovery::NpuSysfsPathInfo> npu_sysfs_path_infos{};
ORT_RETURN_IF_ERROR(npu_device_discovery::DetectNpuSysfsPaths(kSysfsAccelPath, npu_sysfs_path_infos));

std::vector<OrtHardwareDevice> npu_devices{};
npu_devices.reserve(npu_sysfs_path_infos.size());

for (const auto& npu_sysfs_path_info : npu_sysfs_path_infos) {
OrtHardwareDevice npu_device{};
if (auto status = npu_device_discovery::GetNpuDeviceFromSysfs(npu_sysfs_path_info, npu_device); !status.IsOK()) {
LOGS_DEFAULT(WARNING) << MakeString("Failed to detect devices under ",
npu_sysfs_path_info.path,
": ",
status.ErrorMessage());
continue;
}
npu_devices.emplace_back(std::move(npu_device));
}

npu_devices_out = std::move(npu_devices);

Check warning on line 427 in onnxruntime/core/platform/linux/device_discovery.cc

View workflow job for this annotation

GitHub Actions / Optional Lint C++

[cpplint] reported by reviewdog 🐶 Add #include <utility> for move [build/include_what_you_use] [4] Raw Output: onnxruntime/core/platform/linux/device_discovery.cc:427: Add #include <utility> for move [build/include_what_you_use] [4]

return Status::OK();
}
} // namespace

std::unordered_set<OrtHardwareDevice> DeviceDiscovery::DiscoverDevicesForPlatform() {
std::unordered_set<OrtHardwareDevice> devices;

Expand All @@ -334,7 +449,16 @@
}

// get NPU devices
// TODO figure out how to discover these
{
std::vector<OrtHardwareDevice> npu_devices{};

Check warning on line 453 in onnxruntime/core/platform/linux/device_discovery.cc

View workflow job for this annotation

GitHub Actions / Optional Lint C++

[cpplint] reported by reviewdog 🐶 Add #include <vector> for vector<> [build/include_what_you_use] [4] Raw Output: onnxruntime/core/platform/linux/device_discovery.cc:453: Add #include <vector> for vector<> [build/include_what_you_use] [4]
Status npu_device_discovery_status = GetNpuDevices(npu_devices);
if (npu_device_discovery_status.IsOK()) {
devices.insert(std::make_move_iterator(npu_devices.begin()),
std::make_move_iterator(npu_devices.end()));
} else {
LOGS_DEFAULT(WARNING) << "NPU device discovery failed: " << npu_device_discovery_status.ErrorMessage();
}
}

return devices;
}
Expand Down
32 changes: 32 additions & 0 deletions onnxruntime/core/platform/linux/npu_device_discovery.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

// This header exposes Linux NPU device discovery internals for testing.

#pragma once

#include <cstddef>
#include <filesystem>

Check warning on line 9 in onnxruntime/core/platform/linux/npu_device_discovery.h

View workflow job for this annotation

GitHub Actions / Optional Lint C++

[cpplint] reported by reviewdog 🐶 <filesystem> is an unapproved C++17 header. [build/c++17] [5] Raw Output: onnxruntime/core/platform/linux/npu_device_discovery.h:9: <filesystem> is an unapproved C++17 header. [build/c++17] [5]
#include <vector>

#include "core/common/status.h"
#include "core/session/abi_devices.h"

namespace onnxruntime {
namespace npu_device_discovery {

struct NpuSysfsPathInfo {
size_t accel_idx;
std::filesystem::path path;
};

// Scans the given sysfs accel directory for NPU accel devices.
Status DetectNpuSysfsPaths(const std::filesystem::path& sysfs_accel_path,
std::vector<NpuSysfsPathInfo>& npu_sysfs_paths_out);

// Reads vendor/device IDs and populates an OrtHardwareDevice from an accel sysfs path.
Status GetNpuDeviceFromSysfs(const NpuSysfsPathInfo& path_info,
OrtHardwareDevice& npu_device_out);

} // namespace npu_device_discovery
} // namespace onnxruntime
103 changes: 103 additions & 0 deletions onnxruntime/test/platform/linux/npu_device_discovery_test.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

#include "core/platform/linux/npu_device_discovery.h"

#include <algorithm>
#include <filesystem>
#include <fstream>
#include <string>
#include <vector>

#include "gtest/gtest.h"
#include "test/util/include/asserts.h"

namespace fs = std::filesystem;

namespace onnxruntime::test {
namespace {

void WriteFile(const fs::path& path, const std::string& value) {
std::ofstream f(path);
f << value;
}

class NpuDeviceDiscoveryTest : public ::testing::Test {
protected:
void SetUp() override {
temp_dir_ = fs::temp_directory_path() / "ort_npu_discovery_test";
fs::remove_all(temp_dir_);
fs::create_directories(temp_dir_);
}

void TearDown() override {
fs::remove_all(temp_dir_);
}

fs::path temp_dir_;
};

} // namespace

TEST_F(NpuDeviceDiscoveryTest, ReturnsEmptyForNonexistentPath) {
std::vector<npu_device_discovery::NpuSysfsPathInfo> npu_paths;
ASSERT_STATUS_OK(npu_device_discovery::DetectNpuSysfsPaths(temp_dir_ / "nonexistent", npu_paths));
EXPECT_TRUE(npu_paths.empty());
}

TEST_F(NpuDeviceDiscoveryTest, DetectsAccelDevices) {
fs::create_directories(temp_dir_ / "accel0");
fs::create_directories(temp_dir_ / "accel12");
fs::create_directories(temp_dir_ / "renderD128");
fs::create_directories(temp_dir_ / "accelabc");

std::vector<npu_device_discovery::NpuSysfsPathInfo> npu_paths;
ASSERT_STATUS_OK(npu_device_discovery::DetectNpuSysfsPaths(temp_dir_, npu_paths));

ASSERT_EQ(npu_paths.size(), 2u);

std::vector<size_t> accel_indices;
accel_indices.reserve(npu_paths.size());
for (const auto& npu_path : npu_paths) {
accel_indices.push_back(npu_path.accel_idx);
}

std::sort(accel_indices.begin(), accel_indices.end());

EXPECT_EQ(accel_indices[0], 0u);
EXPECT_EQ(accel_indices[1], 12u);
}

TEST_F(NpuDeviceDiscoveryTest, GetNpuDeviceFromSysfsReadsVendorDeviceAndMetadata) {
const auto pci_device_dir = temp_dir_ / "pci_devices" / "0000:65:00.0";
const auto accel_dir = temp_dir_ / "class_accel" / "accel0";

fs::create_directories(pci_device_dir);
fs::create_directories(accel_dir);

WriteFile(pci_device_dir / "vendor", "0x1022");
WriteFile(pci_device_dir / "device", "0x1502");

std::error_code error_code{};
fs::create_directory_symlink(pci_device_dir, accel_dir / "device", error_code);
ASSERT_FALSE(error_code) << error_code.message();

npu_device_discovery::NpuSysfsPathInfo path_info{};
path_info.accel_idx = 0;
path_info.path = accel_dir;

OrtHardwareDevice npu_device{};
ASSERT_STATUS_OK(npu_device_discovery::GetNpuDeviceFromSysfs(path_info, npu_device));

EXPECT_EQ(npu_device.type, OrtHardwareDeviceType_NPU);
EXPECT_EQ(npu_device.vendor_id, 0x1022u);
EXPECT_EQ(npu_device.device_id, 0x1502u);

const auto& entries = npu_device.metadata.Entries();
EXPECT_NE(entries.find("accel_idx"), entries.end());
EXPECT_EQ(entries.at("accel_idx"), "0");
EXPECT_NE(entries.find("pci_bus_id"), entries.end());
EXPECT_EQ(entries.at("pci_bus_id"), "0000:65:00.0");
}

} // namespace onnxruntime::test
Loading