NAME
Rex::GPU::Detect::Sysfs - GPU detection from sysfs, without lspci or pciutils
VERSION
version 0.004
SYNOPSIS
use Rex -feature => ['1.4'];
use Rex::GPU;
use Rex::GPU::NVIDIA;
# Rexfile-wide: gpu_detect, gpu_setup, detect() and Rex::Rancher's gpu => 1
set gpu_detect_class => 'Rex::GPU::Detect::Sysfs';
task 'setup', sub {
gpu_setup(containerd_config => 'rke2'); # detects from sysfs
};
task 'driver', sub {
my $gpus = gpu_detect(); # or gpu_detect(detect => 'Rex::GPU::Detect::Sysfs')
# Your call, not gpu_setup's: drop what is known not to be compute
# (Kepler and older), keep the undecided ones (compute undef)
my @gpus = grep { !defined $_->{compute} || $_->{compute} } @{ $gpus->{nvidia} };
return unless @gpus; # gpus => [] still installs a driver (GPU-agnostic)
install_driver(gpus => \@gpus,
( @{ $gpus->{nvswitch} } ? ( nvswitches => $gpus->{nvswitch} ) : () ));
};
DESCRIPTION
Experimental. A subclass of Rex::GPU::Detect that detects GPUs from the kernel's view of the PCI bus, /sys/bus/pci/devices, instead of lspci -nn. For callers that must not install a package just to detect (no pciutils), or whose hosts' pci.ids is too old to name the newest cards anyway -- vendor, device and class come from the hardware, not from a name database.
What it does not give: product names (sysfs has none, see "detect"), and therefore no compute decision from a name -- an NVIDIA display-class device whose ID the generation table does not cover is compute => undef, not 0 -- and no NVSwitch recognised by name alone.
What it shares with the lspci detection, as one source: the generation table (Rex::GPU::NVIDIA::Requirement, through "requirement_class" in Rex::GPU::Detect), the vGPU table ("vgpu_class" in Rex::GPU::Detect), the NVSwitch device IDs ("nvswitch_device_ids" in Rex::GPU::Detect) and the virtual display vendors ("virtual_display_vendor_ids" in Rex::GPU::Detect). A subclass overrides those, or any helper, as for Rex::GPU::Detect (see "CLASSES OF YOUR OWN" in Rex::GPU).
Like lspci, sysfs lists every PCI device whatever driver it is bound to: a GPU bound to vfio-pci for passthrough to a VM is reported like any other. In a container, /sys shows the host's PCI devices, so the result is the host's.
detect
my $gpus = Rex::GPU::Detect::Sysfs->detect;
Experimental. Detect GPU hardware on the remote host from /sys/bus/pci/devices: one read-only command reads class, vendor, device, subsystem_vendor and subsystem_device of every PCI device, and the rest is decided here. It never runs lspci and never installs anything (no pciutils), and it needs no SFTP. Returns the same hashref as "detect" in Rex::GPU::Detect -- nvidia, amd and nvswitch, always all three array refs -- with the differences below; everything not named there is as documented for "detect" in Rex::GPU::Detect.
{
nvidia => [
{
name => "NVIDIA GPU [10de:2204]", # sysfs has no names
vendor => "nvidia",
pci_class => "0300", # "0300" or "0302"
compute => 1, # 1, 0 or undef, see below
device_id => "2204", # four lowercase hex digits; undef if unreadable
subsystem_vendor_id => "1043", # likewise, no 0x
subsystem_id => "87b5", # likewise
vgpu => 0, # 1 for an NVIDIA vGPU guest device
# vgpu_type => "NVIDIA A10-2Q", # only when vgpu is 1
}
],
amd => [
{ name => "AMD GPU [1002:744c]", vendor => "amd",
pci_class => "0300", compute => 0 }
],
nvswitch => [
{ name => "NVIDIA NVSwitch [10de:22a3]", vendor => "nvidia",
pci_class => "0680", device_id => "22a3" }
],
}
Names. sysfs knows no product names, so
nameisNVIDIA GPU [10de:XXXX](????for a device ID it could not read),AMD GPU [1002:XXXX]orNVIDIA NVSwitch [10de:XXXX]. It is only for messages; nothing is decided by it.computeis what "NVIDIA compute classification" in Rex::GPU::Detect gives wherever the device ID's generation row or the PCI class decides:0with the Kepler warning for Kepler or older at any class,1for class0302,1for a generation row that is compute. Where thelspcipath would fall back to the name rules -- a class0300device whose ID no generation row covers (3000and up, bar Blackwell Ultra), or whose ID was unreadable -- there is no name to judge, andcomputeisundef("undecided"), with a warning. Thelspcipath says0there. "gpu_setup" in Rex::GPU installs a driver only for a truecompute, so an undecided GPU gets none, as an unknown one does withlspci; a caller that wants to try the driver for it passes it to "install_driver" in Rex::GPU::NVIDIA itself.IDs --
device_id,subsystem_vendor_id,subsystem_id-- are four lowercase hex digits without0x(sysfs reads0x10de), the form thelspcipath gives and "install_driver" in Rex::GPU::NVIDIA requires;undefwhere sysfs could not be read. A device without subsystem IDs reads0000, which is kept.Subsystem IDs and vGPU.
subsystem_vendor_idandsubsystem_idcome from sysfssubsystem_vendor/subsystem_device, for every NVIDIA GPU, so no PCI slot has to be matched.vgpuandvgpu_typeare decided from them by the same vGPU table as withlspci(see "NVIDIA vGPU guests" in Rex::GPU::Detect).NVSwitch. Only an NVIDIA device of PCI class
0680whose device ID is in "nvswitch_device_ids" in Rex::GPU::Detect counts; with no name there is no acceptance by name. Another NVIDIA0680device is logged and skipped. As withlspci,nvswitchis[]when no NVIDIA GPU was found.Virtual displays ("virtual_display_vendor_ids" in Rex::GPU::Detect) are skipped per device, never hiding a real card next to them, as with
lspci. Only class0300and0302devices are display devices here too.Order. Devices are reported in the order of their PCI address.
Dies -- before anything is installed or changed, and instead of reporting "no GPU" for a host it could not look at -- when /sys/bus/pci/devices is missing or unreadable, when the read exits non-zero, when a line of it is not six fields, and when the class or vendor of any PCI device is unreadable or malformed: such a device could be the GPU, so it is not skipped. A device removed while it is read can cause this; run again. A readable but empty /sys/bus/pci/devices (no PCI device at all) returns empty arrays.
Selection is explicit; Rex::GPU::Detect (lspci) stays the default, also where sysfs exists -- which is every Linux host, so "sysfs if present" would switch every caller, Rex::Rancher's gpu => 1 included, to other names and another compute for unknown IDs. Choose it any of these ways:
use Rex::GPU::Detect::Sysfs; # for the direct call only; the rest load it
my $gpus = Rex::GPU::Detect::Sysfs->detect;
my $gpus = gpu_detect(detect => 'Rex::GPU::Detect::Sysfs');
gpu_setup(detect => 'Rex::GPU::Detect::Sysfs', containerd_config => 'rke2');
set gpu_detect_class => 'Rex::GPU::Detect::Sysfs'; # Rexfile-wide, detect() too
SEE ALSO
Rex::GPU::Detect, Rex::GPU, "install_driver" in Rex::GPU::NVIDIA
SUPPORT
Issues
Please report bugs and feature requests on GitHub at https://github.com/Getty/rex-gpu/issues.
CONTRIBUTING
Contributions are welcome! Please fork the repository and submit a pull request.
AUTHOR
Torsten Raudssus <getty@cpan.org>
COPYRIGHT AND LICENSE
This software is copyright (c) 2026 by Torsten Raudssus <torsten@raudssus.de> https://raudssus.de/.
This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself.