From 054cfb1edf3c7342ae8ddc8019ef2d412d1815db Mon Sep 17 00:00:00 2001 From: devangpratap <115096812+devangpratap@users.noreply.github.com> Date: Thu, 9 Jul 2026 11:22:45 +0530 Subject: [PATCH] fix: resolve bandwidth for RTX Ada Generation laptop workstation GPUs The RTX 500/1000/2000/3000/3500/4000/5000 Ada Generation Laptop GPUs all resolved memory bandwidth to None ("BW: N/A"), even though dbgpu already ships their specs. _normalize_detected_name turned the driver name "RTX 2000 Ada Generation Laptop GPU" into "RTX 2000 Ada Generation Mobile" (marker last), while dbgpu names the card "RTX 2000 Mobile Ada Generation" (marker mid-string), so the two never matched. Relocate the Mobile marker to a trailing position in the normalizer. The dbgpu index uses the same normalizer, so both orderings now converge and the cards resolve from dbgpu's own data. The relocation is idempotent for cards that already carry a trailing marker, so consumer laptops, the curated RTX A3000 Laptop entry, and all desktop cards are unchanged. --- src/whichllm/hardware/gpu_db.py | 7 +++++++ tests/test_gpu_db.py | 31 +++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/src/whichllm/hardware/gpu_db.py b/src/whichllm/hardware/gpu_db.py index e679615..f50a482 100644 --- a/src/whichllm/hardware/gpu_db.py +++ b/src/whichllm/hardware/gpu_db.py @@ -40,6 +40,7 @@ _TRAILING_GRAPHICS_RE = re.compile(r"\bgraphics\s*$", re.IGNORECASE) _WHITESPACE_RE = re.compile(r"\s+") _MOBILE_MARKER_RE = re.compile(r"\b(?:laptop|mobile|max-?q)\b", re.IGNORECASE) +_MOBILE_WORD_RE = re.compile(r"\bmobile\b", re.IGNORECASE) # Driver names write VRAM bins without a space ("RTX A2000 12GB"); dbgpu # writes "RTX A2000 12 GB" (#98). _VRAM_NOSPACE_RE = re.compile(r"\b(\d+)GB\b", re.IGNORECASE) @@ -57,6 +58,12 @@ def _normalize_detected_name(name: str) -> str: text = _LAPTOP_GPU_RE.sub("Mobile", text) text = _TRAILING_GRAPHICS_RE.sub("", text) text = _VRAM_NOSPACE_RE.sub(r"\1 GB", text) + # Canonicalize the Mobile marker to a trailing position. dbgpu names the + # workstation Ada laptops "RTX 2000 Mobile Ada Generation" (marker in the + # middle) while drivers emit "RTX 2000 Ada Generation Laptop GPU" (marker + # last). Moving it to the end on both sides makes the two orderings match. + if _MOBILE_WORD_RE.search(text): + text = f"{_MOBILE_WORD_RE.sub('', text)} Mobile" return _WHITESPACE_RE.sub(" ", text).strip() diff --git a/tests/test_gpu_db.py b/tests/test_gpu_db.py index 1063091..c15b6aa 100644 --- a/tests/test_gpu_db.py +++ b/tests/test_gpu_db.py @@ -106,6 +106,37 @@ def test_resolve_a2000_12gb_vram_bin_from_dbgpu(): assert resolve_detected_bandwidth("NVIDIA RTX A2000 12GB", 12 * _GiB) == 288.0 +def test_resolve_ada_workstation_laptop_family_from_dbgpu(): + # The whole RTX Ada Generation Laptop workstation line reported BW: N/A: + # dbgpu names them "RTX 2000 Mobile Ada Generation" (marker mid-string) + # while drivers emit "RTX 2000 Ada Generation Laptop GPU" (marker last). + # The normalizer must reconcile the two orderings so dbgpu's value is used. + expected = { + "NVIDIA RTX 500 Ada Generation Laptop GPU": 128.0, + "NVIDIA RTX 1000 Ada Generation Laptop GPU": 192.0, + "NVIDIA RTX 2000 Ada Generation Laptop GPU": 256.0, + "NVIDIA RTX 3000 Ada Generation Laptop GPU": 256.0, + "NVIDIA RTX 3500 Ada Generation Laptop GPU": 432.0, + "NVIDIA RTX 4000 Ada Generation Laptop GPU": 432.0, + "NVIDIA RTX 5000 Ada Generation Laptop GPU": 576.0, + } + for name, bw in expected.items(): + assert resolve_detected_bandwidth(name) == bw, name + + +def test_normalize_relocates_mid_string_mobile_marker(): + # Consumer laptops already carry a trailing marker and must be unchanged. + assert ( + _normalize_detected_name("NVIDIA GeForce RTX 4060 Laptop GPU") + == "GeForce RTX 4060 Mobile" + ) + # dbgpu's mid-string marker is moved to the tail to match driver ordering. + assert ( + _normalize_detected_name("RTX 2000 Mobile Ada Generation") + == "RTX 2000 Ada Generation Mobile" + ) + + def test_static_bandwidth_compound_lspci_name(): # Ported from amd.py (#68): compound lspci names resolve via segment # splitting, first matching segment wins, "RX " prefix retried for bare