Skip to content

Add detailed GPU hardware detection reporting and consolidate logic - #2782

Draft
olliewalsh wants to merge 3 commits into
containers:mainfrom
olliewalsh:hw_info
Draft

Add detailed GPU hardware detection reporting and consolidate logic#2782
olliewalsh wants to merge 3 commits into
containers:mainfrom
olliewalsh:hw_info

Conversation

@olliewalsh

@olliewalsh olliewalsh commented Jun 4, 2026

Copy link
Copy Markdown
Collaborator

Add multi-device hardware detection for ramalama info, reporting all GPU devices/interfaces with memory details

Unify GPU detection behind hw_detect.py as the single source of truth, remove detection logic in common.py
Extract acceleration selection code into focused accel.py module (check_*, get_accel, CDI validation, accel_image)

Use the Vulkan C API directly via ctypes instead of parsing vulkaninfo text output
Look up AMD GPU product names from the system PCI IDs database when the KFD driver reports a generic name

The ultimate goal is to use this data to help find suitable models/quants for the available hardware.

Signed-off-by: Oliver Walsh <owalsh@redhat.com>
Consolidate duplicated GPU detection logic so hw_detect.py is the
single source of truth for hardware detection, and extract all
acceleration selection code (check_*, get_accel, CDI validation,
accel_image) from common.py into a focused accel.py module.

- hw_detect.py: extend DeviceInfo with index/uuid fields, add
  per-detector cached functions, use Vulkan API directly via ctypes
  instead of parsing vulkaninfo output, add ARM guard to AmdRocmDetector,
  add Intel iGPU device IDs
- accel.py: new module with GPU selection, env var setup, CDI validation,
  and container image selection (moved from common.py)
- common.py: shed ~250 lines of acceleration code, now general utilities
- Update 14 import sites across the codebase
- Split acceleration tests into test_accel.py

Signed-off-by: Oliver Walsh <owalsh@redhat.com>
@olliewalsh
olliewalsh temporarily deployed to macos-installer June 4, 2026 10:52 — with GitHub Actions Inactive
@coderabbitai

coderabbitai Bot commented Jun 4, 2026

Copy link
Copy Markdown

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 0053cacf-f163-4578-b9b7-e3cd176ef34c

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request refactors hardware detection and accelerator configuration by moving accelerator logic from ramalama/common.py to ramalama/accel.py and hardware detection to ramalama/hw_detect.py. It also enhances the CLI to display detailed accelerator information. The review feedback highlights several robustness improvements and potential crash fixes, including wrapping get_podman_machine_cdi_config in a try...except block, validating JSON structures in MthreadsDetector to prevent AttributeError crashes, stripping whitespace from CUDA_VISIBLE_DEVICES, specifying explicit file encodings, handling UnicodeDecodeError when reading /proc/cpuinfo, and using globals() for cleaner dynamic function lookups.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread ramalama/accel.py
Comment thread ramalama/hw_detect.py
Comment on lines +418 to +423
try:
import json

data = json.loads(result.stdout)
except (ValueError, KeyError):
return []

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

If data is not a dictionary (e.g., if mthreads-gmi returned a JSON array or a JSON string/number), data.get will raise an AttributeError outside the try...except block and crash the program. Validate that data is a dictionary before proceeding.

Suggested change
try:
import json
data = json.loads(result.stdout)
except (ValueError, KeyError):
return []
try:
import json
data = json.loads(result.stdout)
if not isinstance(data, dict):
return []
except (ValueError, KeyError):
return []

Comment thread ramalama/accel.py
Comment on lines +129 to +134
cuda_visible_devices = os.environ.get("CUDA_VISIBLE_DEVICES", "")
visible_devices = cuda_visible_devices.split(',') if cuda_visible_devices else []
for device in visible_devices:
if device not in indices and not any(uuid.startswith(device) for uuid in uuids):
perror(f"{device} not found")
return None

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

If CUDA_VISIBLE_DEVICES contains spaces (e.g., "0, 1"), splitting by "," will result in leading/trailing whitespace in the device identifiers (e.g., " 1"). This will cause the device lookup to fail. Strip whitespace from each device identifier to make the environment variable parsing more robust.

Suggested change
cuda_visible_devices = os.environ.get("CUDA_VISIBLE_DEVICES", "")
visible_devices = cuda_visible_devices.split(',') if cuda_visible_devices else []
for device in visible_devices:
if device not in indices and not any(uuid.startswith(device) for uuid in uuids):
perror(f"{device} not found")
return None
cuda_visible_devices = os.environ.get("CUDA_VISIBLE_DEVICES", "")
visible_devices = [d.strip() for d in cuda_visible_devices.split(',')] if cuda_visible_devices else []
for device in visible_devices:
if device not in indices and not any(uuid.startswith(device) for uuid in uuids):
perror(f"{device} not found")
return None

Comment thread ramalama/accel.py
Comment on lines +50 to +54
with open(file_path, "r") as stream:
if ext == ".json":
config = json.load(stream)
else:
config = yaml.safe_load(stream)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

When opening files, it is highly recommended to specify an explicit encoding (such as encoding="utf-8") to ensure consistent behavior across different platforms (especially Windows), rather than relying on the system default locale encoding.

Suggested change
with open(file_path, "r") as stream:
if ext == ".json":
config = json.load(stream)
else:
config = yaml.safe_load(stream)
with open(file_path, "r", encoding="utf-8") as stream:
if ext == ".json":
config = json.load(stream)
else:
config = yaml.safe_load(stream)

Comment thread ramalama/hw_detect.py
Comment on lines +125 to +129
try:
with open("/proc/cpuinfo", "r") as f:
for line in f:
if line.startswith("model name"):
return line.split(":", 1)[1].strip()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Reading /proc/cpuinfo can raise a UnicodeDecodeError if it contains non-ASCII characters on systems with non-UTF-8 default encodings. Since UnicodeDecodeError inherits from ValueError (not OSError), it will not be caught by except OSError. Use errors="replace" to prevent potential crashes.

            with open("/proc/cpuinfo", "r", errors="replace") as f:
                for line in f:
                    if line.startswith("model name") and ":" in line:
                        return line.split(":", 1)[1].strip()

Comment thread ramalama/hw_detect.py
Comment on lines +426 to +431
for gpu in data.get("GPU", []):
name = gpu.get("Product Name", "Moore Threads GPU")
total_str = gpu.get("FB Memory Usage", {}).get("Total", "")
total_bytes = self._parse_memory_string(total_str)
if total_bytes > 0:
devices.append(DeviceInfo(name=name or "Moore Threads GPU", memory_bytes=total_bytes))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

If gpu.get("FB Memory Usage") returns None or a non-dictionary value (e.g., if the field is null or malformed in the JSON output), calling .get("Total") on it will raise an AttributeError and crash the detector. Add a type check to ensure it is a dictionary before calling .get().

        fb_usage = gpu.get("FB Memory Usage")
        total_str = fb_usage.get("Total", "") if isinstance(fb_usage, dict) else ""
        total_bytes = self._parse_memory_string(total_str)
        if total_bytes > 0:
            devices.append(DeviceInfo(name=name or "Moore Threads GPU", memory_bytes=total_bytes))

Comment thread ramalama/hw_detect.py
Comment on lines +820 to +832
def detect_all_hardware() -> tuple[AcceleratorInfo, ...]:
import ramalama.hw_detect as _self

results: list[AcceleratorInfo] = []
for accel_type, fn_name in _DETECTOR_NAMES:
try:
fn: Callable[[], list[DeviceInfo]] = getattr(_self, fn_name)
devices = fn()
if devices:
total = sum(d.memory_bytes for d in devices)
results.append(AcceleratorInfo(accel_type=accel_type, devices=devices, total_memory_bytes=total))
except Exception as e:
logger.debug(f"Hardware detection failed for {accel_type}: {e}")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Instead of dynamically looking up detector functions by name using getattr on a self-imported module reference, you can reference the function objects directly from globals(). This is much cleaner, more idiomatic, and avoids unnecessary self-imports.

Suggested change
def detect_all_hardware() -> tuple[AcceleratorInfo, ...]:
import ramalama.hw_detect as _self
results: list[AcceleratorInfo] = []
for accel_type, fn_name in _DETECTOR_NAMES:
try:
fn: Callable[[], list[DeviceInfo]] = getattr(_self, fn_name)
devices = fn()
if devices:
total = sum(d.memory_bytes for d in devices)
results.append(AcceleratorInfo(accel_type=accel_type, devices=devices, total_memory_bytes=total))
except Exception as e:
logger.debug(f"Hardware detection failed for {accel_type}: {e}")
@lru_cache(maxsize=1)
def detect_all_hardware() -> tuple[AcceleratorInfo, ...]:
results: list[AcceleratorInfo] = []
for accel_type, fn_name in _DETECTOR_NAMES:
try:
fn: Callable[[], list[DeviceInfo]] = globals()[fn_name]
devices = fn()
if devices:
total = sum(d.memory_bytes for d in devices)
results.append(AcceleratorInfo(accel_type=accel_type, devices=devices, total_memory_bytes=total))
except Exception as e:
logger.debug(f"Hardware detection failed for {accel_type}: {e}")

@olliewalsh

Copy link
Copy Markdown
Collaborator Author

Output on f44 RX9070:

    "Accelerators": {
        "cpu": {
            "devices": [
                {
                    "memory": 33556418560,
                    "memory_human": "31.25 GB",
                    "name": "AMD Ryzen 7 5800X 8-Core Processor"
                }
            ],
            "total_memory": 33556418560,
            "total_memory_human": "31.25 GB"
        },
        "hip": {
            "devices": [
                {
                    "memory": 17095983104,
                    "memory_human": "15.92 GB",
                    "name": "AMD Navi 48 [Radeon RX 9070/9070 XT/9070 GRE]"
                }
            ],
            "total_memory": 17095983104,
            "total_memory_human": "15.92 GB"
        },
        "vulkan": {
            "devices": [
                {
                    "memory": 17095983104,
                    "memory_human": "15.92 GB",
                    "name": "AMD Radeon RX 9070 XT (RADV GFX1201)"
                }
            ],
            "total_memory": 17095983104,
            "total_memory_human": "15.92 GB"
        }

@olliewalsh

Copy link
Copy Markdown
Collaborator Author

f43 AMD iGPU:

    "Accelerators": {
        "cpu": {
            "devices": [
                {
                    "memory": 33030242304,
                    "memory_human": "30.76 GB",
                    "name": "AMD Ryzen 5 5560U with Radeon Graphics"
                }
            ],
            "total_memory": 33030242304,
            "total_memory_human": "30.76 GB"
        },
        "hip": {
            "devices": [
                {
                    "memory": 25769803776,
                    "memory_human": "24.0 GB",
                    "name": "AMD Renoir"
                }
            ],
            "total_memory": 25769803776,
            "total_memory_human": "24.0 GB"
        },
        "vulkan": {
            "devices": [
                {
                    "memory": 26306674688,
                    "memory_human": "24.5 GB",
                    "name": "AMD Radeon Graphics (RADV RENOIR)"
                }
            ],
            "total_memory": 26306674688,
            "total_memory_human": "24.5 GB"
        }
    }

@olliewalsh

Copy link
Copy Markdown
Collaborator Author

f44 NVIDIA:

    "Accelerators": {
        "cpu": {
            "devices": [
                {
                    "memory": 101210492928,
                    "memory_human": "94.26 GB",
                    "name": "AMD Ryzen 9 5900X 12-Core Processor"
                }
            ],
            "total_memory": 101210492928,
            "total_memory_human": "94.26 GB"
        },
        "cuda": {
            "devices": [
                {
                    "memory": 25769803776,
                    "memory_human": "24.0 GB",
                    "name": "NVIDIA GeForce RTX 3090"
                },
                {
                    "memory": 25769803776,
                    "memory_human": "24.0 GB",
                    "name": "NVIDIA GeForce RTX 3090"
                }
            ],
            "total_memory": 51539607552,
            "total_memory_human": "48.0 GB"
        },
        "vulkan": {
            "devices": [
                {
                    "memory": 25769803776,
                    "memory_human": "24.0 GB",
                    "name": "NVIDIA GeForce RTX 3090"
                },
                {
                    "memory": 26027753472,
                    "memory_human": "24.24 GB",
                    "name": "NVIDIA GeForce RTX 3090"
                }
            ],
            "total_memory": 51797557248,
            "total_memory_human": "48.24 GB"
        }

@olliewalsh

Copy link
Copy Markdown
Collaborator Author

@bmahabirbu WDYT?

@olliewalsh

Copy link
Copy Markdown
Collaborator Author

W11 9070XT:

"Accelerators": {
        "cpu": {
            "devices": [
                {
                    "memory": 34277593088,
                    "memory_human": "31.92 GB",
                    "name": "AMD Ryzen 7 5800X 8-Core Processor"
                }
            ],
            "total_memory": 34277593088,
            "total_memory_human": "31.92 GB"
        },
        "vulkan": {
            "devices": [
                {
                    "memory": 17095983104,
                    "memory_human": "15.92 GB",
                    "name": "AMD Radeon RX 9070 XT"
                }
            ],
            "total_memory": 17095983104,
            "total_memory_human": "15.92 GB"
        }
    }

Signed-off-by: Oliver Walsh <owalsh@redhat.com>
@olliewalsh
olliewalsh temporarily deployed to macos-installer June 4, 2026 11:34 — with GitHub Actions Inactive
@olliewalsh

Copy link
Copy Markdown
Collaborator Author

Win11 NVIDIA:

"Accelerators": {
        "cpu": {
            "devices": [
                {
                    "memory": 130645536768,
                    "memory_human": "121.67 GB",
                    "name": "AMD Ryzen 9 5900X 12-Core Processor"
                }
            ],
            "total_memory": 130645536768,
            "total_memory_human": "121.67 GB"
        },
        "cuda": {
            "devices": [
                {
                    "memory": 25769803776,
                    "memory_human": "24.0 GB",
                    "name": "NVIDIA GeForce RTX 3090"
                },
                {
                    "memory": 25769803776,
                    "memory_human": "24.0 GB",
                    "name": "NVIDIA GeForce RTX 3090"
                }
            ],
            "total_memory": 51539607552,
            "total_memory_human": "48.0 GB"
        },
        "vulkan": {
            "devices": [
                {
                    "memory": 25503465472,
                    "memory_human": "23.75 GB",
                    "name": "NVIDIA GeForce RTX 3090"
                },
                {
                    "memory": 25732055040,
                    "memory_human": "23.96 GB",
                    "name": "NVIDIA GeForce RTX 3090"
                }
            ],
            "total_memory": 51235520512,
            "total_memory_human": "47.72 GB"
        }
    }

@bmahabirbu

Copy link
Copy Markdown
Collaborator

Gave it a test works great on linux too detectred by cpu and gpu 9800x3d and gpu 9070xt

Comment thread ramalama/accel.py

devices = detect_intel()
if devices:
os.environ["INTEL_VISIBLE_DEVICES"] = str(len(devices))

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

according to claude "check_intel sets INTEL_VISIBLE_DEVICES to the count (accel.py:196), not a device index. This was
the old behavior too, but it's inconsistent with every other check_* which sets an index or device
list.
"

@bmahabirbu

Copy link
Copy Markdown
Collaborator

Helped from claude for the review

No vulkan check in the get_accel() chain. If a system has a GPU only detectable via vulkan (no
vendor-specific tooling), get_accel() returns "none" even though detect_vulkan() would find it. Should
there be a check_vulkan that sets GGML_VK_VISIBLE_DEVICES?

@github-actions

github-actions Bot commented Jul 6, 2026

Copy link
Copy Markdown

A friendly reminder that this PR had no activity for 30 days.

@rhatdan

rhatdan commented Jul 6, 2026

Copy link
Copy Markdown
Member

@olliewalsh still working on this one?

@rhatdan rhatdan removed the stale-pr label Jul 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants