From bd687203e2e3687a64c84ce735a9843a824b9798 Mon Sep 17 00:00:00 2001 From: Roman Makarov Date: Mon, 17 Aug 2026 09:49:12 +0200 Subject: [PATCH] feat: fmt gpu name in gpu-picker (#1157) * feat: fmt gpu name in gpu-picker * move implementation to daemon * move to schema * add Radeon/Geforce prefix back * remove from info_elements --- README.md | 4 +- lact-cli/src/lib.rs | 10 +++- lact-cli/src/subcommands.rs | 9 ++-- lact-daemon/src/server/metrics.rs | 12 ++++- .../src/app/components/gpu_stats_section.rs | 17 +++--- lact-schema/src/lib.rs | 29 +++++++++- lact-schema/src/tests.rs | 54 ++++++++++++++++++- 7 files changed, 113 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index 1fcb356c..b02f7c10 100644 --- a/README.md +++ b/README.md @@ -265,7 +265,7 @@ There is also a cli available. Example output: ``` - 10DE:2704-1462:5110-0000:09:00.0 (AD103 [GeForce RTX 4080]) + 0: 10DE:2704-1462:5110-0000:09:00.0 (GeForce RTX 4080) [Dedicated] ``` - Getting GPU information: @@ -277,7 +277,7 @@ There is also a cli available. $ lact cli info GPU 10DE:2704-1462:5110-0000:09:00.0: ===================================== - GPU Model: NVIDIA GeForce RTX 4080 (0x10DE:0x2704) + GPU Model: GeForce RTX 4080 (0x10DE:0x2704) Card Manufacturer: Micro-Star International Co., Ltd. [MSI] (0x1462) Card Model: Unknown (0x5110) Driver Used: nvidia 570.124.04 diff --git a/lact-cli/src/lib.rs b/lact-cli/src/lib.rs index 1072a2e5..f2ee5e58 100644 --- a/lact-cli/src/lib.rs +++ b/lact-cli/src/lib.rs @@ -8,6 +8,7 @@ use anyhow::{Context, Result, bail}; use lact_client::DaemonClient; use lact_schema::{ args::cli::{CliArgs, CliCommand, ProfileAutoSwitchCommand, ProfileCommand}, + clean_gpu_name, config::GpuConfig, request::ConfirmCommand, }; @@ -100,7 +101,11 @@ impl CliContext<'_> { if entries.len() > 1 { eprintln!( "GPU id not specified, selecting {}", - first_entry.name.as_deref().unwrap_or("") + first_entry + .name + .as_deref() + .map(clean_gpu_name) + .unwrap_or("") ); } Ok(first_entry.id.clone()) @@ -141,6 +146,7 @@ impl CliContext<'_> { .await? .into_iter() .find(|entry| entry.id == gpu_id) - .and_then(|entry| entry.name)) + .and_then(|entry| entry.name) + .map(|name| clean_gpu_name(&name).to_owned())) } } diff --git a/lact-cli/src/subcommands.rs b/lact-cli/src/subcommands.rs index 156b242a..9bb9a2d9 100644 --- a/lact-cli/src/subcommands.rs +++ b/lact-cli/src/subcommands.rs @@ -11,13 +11,10 @@ const PROFILE_DEFAULT: &str = "Default"; pub async fn list_gpus(ctx: CliContext<'_>) -> Result<()> { let entries = ctx.client.list_devices().await?; for (i, entry) in entries.into_iter().enumerate() { - let id = entry.id; - let device_type = entry.device_type; - - if let Some(name) = entry.name { - println!("{i}: {id} ({name}) [{device_type}]"); + if entry.name.is_some() { + println!("{i}: {} ({entry}) [{}]", entry.id, entry.device_type); } else { - println!("{i}: {id} [{device_type}]"); + println!("{i}: {} [{}]", entry.id, entry.device_type); } } Ok(()) diff --git a/lact-daemon/src/server/metrics.rs b/lact-daemon/src/server/metrics.rs index 1c0867aa..fe7e2ddb 100644 --- a/lact-daemon/src/server/metrics.rs +++ b/lact-daemon/src/server/metrics.rs @@ -6,7 +6,7 @@ use crate::{ }; use indexmap::IndexMap; use jiff::Zoned; -use lact_schema::DeviceStats; +use lact_schema::{DeviceStats, clean_gpu_name}; use schema::{ Attribute, Gauge, GaugeDataPoint, Metric, MetricsPayload, Resource, ResourceMetric, Scope, ScopeMetric, Value, @@ -335,7 +335,15 @@ async fn get_stats(handler: &Handler) -> anyhow::Result { self.vram_clock_ratio = info.vram_clock_ratio(); if let Some(pci_info) = &info.pci_info { - self.gpu_model = info - .drm_info - .as_ref() - .and_then(|drm| drm.device_name.as_deref()) - .or(pci_info.device_pci_info.model.as_deref()) - .unwrap_or("Unknown") - .to_owned(); + self.gpu_model = clean_gpu_name( + info.drm_info + .as_ref() + .and_then(|drm| drm.device_name.as_deref()) + .or(pci_info.device_pci_info.model.as_deref()) + .unwrap_or("Unknown"), + ) + .to_owned(); } } GpuStatsSectionMsg::Stats(stats) => { diff --git a/lact-schema/src/lib.rs b/lact-schema/src/lib.rs index 4f542f9d..ce90b660 100644 --- a/lact-schema/src/lib.rs +++ b/lact-schema/src/lib.rs @@ -67,6 +67,33 @@ pub fn bytes_to_mib(bytes: u64) -> f64 { bytes as f64 / 1024.0 / 1024.0 } +const GPU_VENDOR_PREFIXES: &[&str] = &["AMD ", "NVIDIA ", "Intel "]; + +pub fn clean_gpu_name(name: &str) -> &str { + let mut short = name.trim(); + + if let Some(marketing_name) = GPU_VENDOR_PREFIXES + .iter() + .find_map(|&prefix| short.strip_prefix(prefix)) + { + short = marketing_name + .split_once('[') + .map_or(marketing_name, |(model, _)| model.trim_end()); + } else if let Some((_, bracketed)) = short.split_once('[') + && let Some((model, _)) = bracketed.split_once(']') + { + // PCI device names carry the marketing name in brackets, e.g. "DG2 [Arc A770]" + short = model.trim(); + } + + short = GPU_VENDOR_PREFIXES + .iter() + .find_map(|&prefix| short.strip_prefix(prefix)) + .unwrap_or(short); + + short +} + #[derive(Serialize, Deserialize, Debug)] pub struct Pong; @@ -121,7 +148,7 @@ pub struct DeviceListEntry { impl Display for DeviceListEntry { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match &self.name { - Some(name) => Display::fmt(name, f), + Some(name) => Display::fmt(clean_gpu_name(name), f), None => Display::fmt(&self.id, f), } } diff --git a/lact-schema/src/tests.rs b/lact-schema/src/tests.rs index 56c8a06e..ae5ccf6a 100644 --- a/lact-schema/src/tests.rs +++ b/lact-schema/src/tests.rs @@ -1,4 +1,4 @@ -use crate::{FanControlMode, FanOptions, PmfwOptions, Pong, Request, Response}; +use crate::{FanControlMode, FanOptions, PmfwOptions, Pong, Request, Response, clean_gpu_name}; use anyhow::anyhow; use serde_json::json; use std::collections::BTreeMap; @@ -86,3 +86,55 @@ fn set_fan_clocks() { }); assert_eq!(expected_request, request); } + +#[test] +fn clean_gpu_name_removes_vendor_prefixes() { + assert_eq!(clean_gpu_name("AMD Radeon RX 9070 XT"), "Radeon RX 9070 XT"); + assert_eq!( + clean_gpu_name("NVIDIA GeForce RTX 5090"), + "GeForce RTX 5090" + ); + assert_eq!(clean_gpu_name("NVIDIA GeForce MX450"), "GeForce MX450"); + assert_eq!( + clean_gpu_name("NVIDIA GeForce RTX 5090 [Founders Edition]"), + "GeForce RTX 5090" + ); + assert_eq!( + clean_gpu_name("NVIDIA GeForce RTX 4070 Super"), + "GeForce RTX 4070 Super" + ); + assert_eq!(clean_gpu_name("Intel Arc A380"), "Arc A380"); +} + +#[test] +fn clean_gpu_name_unwraps_pci_names() { + assert_eq!( + clean_gpu_name("Pitcairn XT [Radeon HD 7870 GHz Edition]"), + "Radeon HD 7870 GHz Edition" + ); + assert_eq!(clean_gpu_name("DG2 [Arc A380]"), "Arc A380"); + assert_eq!(clean_gpu_name("GK107M [GeForce 710A]"), "GeForce 710A"); + assert_eq!(clean_gpu_name("GK107M [GeForce 820M]"), "GeForce 820M"); + assert_eq!(clean_gpu_name("TU117M [GeForce MX450]"), "GeForce MX450"); + assert_eq!( + clean_gpu_name("TigerLake-LP GT2 [Iris Xe Graphics]"), + "Iris Xe Graphics" + ); +} + +#[test] +fn clean_gpu_name_keeps_consumer_brands_and_unrecognized_names() { + assert_eq!( + clean_gpu_name("AMD Radeon 780M Graphics"), + "Radeon 780M Graphics" + ); + assert_eq!(clean_gpu_name("AMD Radeon VII"), "Radeon VII"); + assert_eq!(clean_gpu_name("Phoenix1"), "Phoenix1"); +} + +#[test] +fn clean_gpu_name_keeps_professional_product_brands() { + assert_eq!(clean_gpu_name("GK107GL [Quadro K600]"), "Quadro K600"); + assert_eq!(clean_gpu_name("GK110GL [Tesla K20]"), "Tesla K20"); + assert_eq!(clean_gpu_name("NVIDIA Quadro RTX 6000"), "Quadro RTX 6000"); +}