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 name is NVIDIA GPU [10de:XXXX] (???? for a device ID it could not read), AMD GPU [1002:XXXX] or NVIDIA NVSwitch [10de:XXXX]. It is only for messages; nothing is decided by it.

  • compute is what "NVIDIA compute classification" in Rex::GPU::Detect gives wherever the device ID's generation row or the PCI class decides: 0 with the Kepler warning for Kepler or older at any class, 1 for class 0302, 1 for a generation row that is compute. Where the lspci path would fall back to the name rules -- a class 0300 device whose ID no generation row covers (3000 and up, bar Blackwell Ultra), or whose ID was unreadable -- there is no name to judge, and compute is undef ("undecided"), with a warning. The lspci path says 0 there. "gpu_setup" in Rex::GPU installs a driver only for a true compute, so an undecided GPU gets none, as an unknown one does with lspci; 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 without 0x (sysfs reads 0x10de), the form the lspci path gives and "install_driver" in Rex::GPU::NVIDIA requires; undef where sysfs could not be read. A device without subsystem IDs reads 0000, which is kept.

  • Subsystem IDs and vGPU. subsystem_vendor_id and subsystem_id come from sysfs subsystem_vendor / subsystem_device, for every NVIDIA GPU, so no PCI slot has to be matched. vgpu and vgpu_type are decided from them by the same vGPU table as with lspci (see "NVIDIA vGPU guests" in Rex::GPU::Detect).

  • NVSwitch. Only an NVIDIA device of PCI class 0680 whose device ID is in "nvswitch_device_ids" in Rex::GPU::Detect counts; with no name there is no acceptance by name. Another NVIDIA 0680 device is logged and skipped. As with lspci, nvswitch is [] 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 class 0300 and 0302 devices 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.