mirror of
https://github.com/ilya-zlobintsev/LACT.git
synced 2025-02-25 18:55:26 -06:00
feat: use VRAM offset ratio on Ada, add config migration system to erase old
memory clock settings
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
use crate::server::gpu_controller::fan_control::FanCurve;
|
use crate::server::gpu_controller::{fan_control::FanCurve, VENDOR_NVIDIA};
|
||||||
use amdgpu_sysfs::gpu_handle::{PerformanceLevel, PowerLevelKind};
|
use amdgpu_sysfs::gpu_handle::{PerformanceLevel, PowerLevelKind};
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
use indexmap::IndexMap;
|
use indexmap::IndexMap;
|
||||||
@@ -18,7 +18,7 @@ use std::{
|
|||||||
time::{Duration, Instant},
|
time::{Duration, Instant},
|
||||||
};
|
};
|
||||||
use tokio::{sync::mpsc, time};
|
use tokio::{sync::mpsc, time};
|
||||||
use tracing::{debug, error};
|
use tracing::{debug, error, info};
|
||||||
|
|
||||||
const FILE_NAME: &str = "config.yaml";
|
const FILE_NAME: &str = "config.yaml";
|
||||||
const DEFAULT_ADMIN_GROUPS: [&str; 2] = ["wheel", "sudo"];
|
const DEFAULT_ADMIN_GROUPS: [&str; 2] = ["wheel", "sudo"];
|
||||||
@@ -29,6 +29,8 @@ const SELF_CONFIG_EDIT_PERIOD_MILLIS: u64 = 1000;
|
|||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
|
#[serde(default)]
|
||||||
|
pub version: u64,
|
||||||
pub daemon: Daemon,
|
pub daemon: Daemon,
|
||||||
#[serde(default = "default_apply_settings_timer")]
|
#[serde(default = "default_apply_settings_timer")]
|
||||||
pub apply_settings_timer: u64,
|
pub apply_settings_timer: u64,
|
||||||
@@ -51,6 +53,7 @@ impl Default for Config {
|
|||||||
profiles: IndexMap::new(),
|
profiles: IndexMap::new(),
|
||||||
current_profile: None,
|
current_profile: None,
|
||||||
auto_switch_profiles: false,
|
auto_switch_profiles: false,
|
||||||
|
version: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -206,6 +209,36 @@ impl Config {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn migrate_versions(&mut self) {
|
||||||
|
loop {
|
||||||
|
let next_version = self.version + 1;
|
||||||
|
match next_version {
|
||||||
|
0 => unreachable!(),
|
||||||
|
// Reset VRAM settings on Nvidia after new offset ratio logic
|
||||||
|
1 => {
|
||||||
|
for (id, gpu) in &mut self.gpus {
|
||||||
|
if id.starts_with(VENDOR_NVIDIA) {
|
||||||
|
gpu.clocks_configuration.max_memory_clock = None;
|
||||||
|
gpu.clocks_configuration.min_memory_clock = None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for profile in &mut self.profiles.values_mut() {
|
||||||
|
for (id, gpu) in &mut profile.gpus {
|
||||||
|
if id.starts_with(VENDOR_NVIDIA) {
|
||||||
|
gpu.clocks_configuration.max_memory_clock = None;
|
||||||
|
gpu.clocks_configuration.min_memory_clock = None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => break,
|
||||||
|
}
|
||||||
|
info!("migrated config version {} to {next_version}", self.version);
|
||||||
|
self.version = next_version;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Gets the GPU configs according to the current profile. Returns an error if the current profile could not be found.
|
/// Gets the GPU configs according to the current profile. Returns an error if the current profile could not be found.
|
||||||
pub fn gpus(&self) -> anyhow::Result<&IndexMap<String, Gpu>> {
|
pub fn gpus(&self) -> anyhow::Result<&IndexMap<String, Gpu>> {
|
||||||
match &self.current_profile {
|
match &self.current_profile {
|
||||||
@@ -364,6 +397,7 @@ fn default_apply_settings_timer() -> u64 {
|
|||||||
mod tests {
|
mod tests {
|
||||||
use super::{ClocksConfiguration, Config, Daemon, FanControlSettings, Gpu};
|
use super::{ClocksConfiguration, Config, Daemon, FanControlSettings, Gpu};
|
||||||
use crate::server::gpu_controller::fan_control::FanCurve;
|
use crate::server::gpu_controller::fan_control::FanCurve;
|
||||||
|
use indexmap::IndexMap;
|
||||||
use insta::assert_yaml_snapshot;
|
use insta::assert_yaml_snapshot;
|
||||||
use lact_schema::{FanControlMode, PmfwOptions};
|
use lact_schema::{FanControlMode, PmfwOptions};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
@@ -431,4 +465,70 @@ mod tests {
|
|||||||
gpu.clocks_configuration.voltage_offset = Some(10);
|
gpu.clocks_configuration.voltage_offset = Some(10);
|
||||||
assert!(gpu.is_core_clocks_used());
|
assert!(gpu.is_core_clocks_used());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn migrate_versions() {
|
||||||
|
let mut config = Config {
|
||||||
|
version: 0,
|
||||||
|
daemon: Daemon::default(),
|
||||||
|
apply_settings_timer: 5,
|
||||||
|
gpus: IndexMap::from([
|
||||||
|
(
|
||||||
|
"10DE:2704-1462:5110-0000:09:00.0".to_owned(),
|
||||||
|
Gpu {
|
||||||
|
clocks_configuration: ClocksConfiguration {
|
||||||
|
max_core_clock: Some(3000),
|
||||||
|
max_memory_clock: Some(10_000),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"1002:687F-1043:0555-0000:0b:00.0".to_owned(),
|
||||||
|
Gpu {
|
||||||
|
clocks_configuration: ClocksConfiguration {
|
||||||
|
max_core_clock: Some(1500),
|
||||||
|
max_memory_clock: Some(920),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
profiles: IndexMap::new(),
|
||||||
|
current_profile: None,
|
||||||
|
auto_switch_profiles: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
config.migrate_versions();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
config
|
||||||
|
.gpus
|
||||||
|
.get("10DE:2704-1462:5110-0000:09:00.0")
|
||||||
|
.unwrap()
|
||||||
|
.clocks_configuration
|
||||||
|
.max_core_clock,
|
||||||
|
Some(3000)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
config
|
||||||
|
.gpus
|
||||||
|
.get("10DE:2704-1462:5110-0000:09:00.0")
|
||||||
|
.unwrap()
|
||||||
|
.clocks_configuration
|
||||||
|
.max_memory_clock,
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
config
|
||||||
|
.gpus
|
||||||
|
.get("1002:687F-1043:0555-0000:0b:00.0")
|
||||||
|
.unwrap()
|
||||||
|
.clocks_configuration
|
||||||
|
.max_memory_clock,
|
||||||
|
Some(920),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ use anyhow::Context;
|
|||||||
use config::Config;
|
use config::Config;
|
||||||
use futures::future::select_all;
|
use futures::future::select_all;
|
||||||
use server::{handle_stream, handler::Handler, Server};
|
use server::{handle_stream, handler::Handler, Server};
|
||||||
|
use std::cell::Cell;
|
||||||
|
use std::time::Instant;
|
||||||
use std::{os::unix::net::UnixStream as StdUnixStream, time::Duration};
|
use std::{os::unix::net::UnixStream as StdUnixStream, time::Duration};
|
||||||
use tokio::net::UnixStream;
|
use tokio::net::UnixStream;
|
||||||
use tokio::{
|
use tokio::{
|
||||||
@@ -46,7 +48,7 @@ pub fn run() -> anyhow::Result<()> {
|
|||||||
.build()
|
.build()
|
||||||
.expect("Could not initialize tokio runtime");
|
.expect("Could not initialize tokio runtime");
|
||||||
rt.block_on(async {
|
rt.block_on(async {
|
||||||
let config = Config::load_or_create()?;
|
let mut config = Config::load_or_create()?;
|
||||||
|
|
||||||
let env_filter = EnvFilter::builder()
|
let env_filter = EnvFilter::builder()
|
||||||
.with_default_directive(LevelFilter::INFO.into())
|
.with_default_directive(LevelFilter::INFO.into())
|
||||||
@@ -54,6 +56,12 @@ pub fn run() -> anyhow::Result<()> {
|
|||||||
.context("Invalid log level")?;
|
.context("Invalid log level")?;
|
||||||
tracing_subscriber::fmt().with_env_filter(env_filter).init();
|
tracing_subscriber::fmt().with_env_filter(env_filter).init();
|
||||||
|
|
||||||
|
let original_version = config.version;
|
||||||
|
config.migrate_versions();
|
||||||
|
if config.version != original_version {
|
||||||
|
config.save(&Cell::new(Instant::now()))?;
|
||||||
|
}
|
||||||
|
|
||||||
ensure_sufficient_uptime().await;
|
ensure_sufficient_uptime().await;
|
||||||
|
|
||||||
LocalSet::new()
|
LocalSet::new()
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use super::{fan_control::FanCurve, FanControlHandle, GpuController};
|
use super::{fan_control::FanCurve, FanControlHandle, GpuController, VENDOR_AMD};
|
||||||
use crate::{
|
use crate::{
|
||||||
config::{self, ClocksConfiguration, FanControlSettings},
|
config::{self, ClocksConfiguration, FanControlSettings},
|
||||||
server::vulkan::get_vulkan_info,
|
server::vulkan::get_vulkan_info,
|
||||||
@@ -47,7 +47,6 @@ use {
|
|||||||
|
|
||||||
const GPU_CLOCKDOWN_TIMEOUT_SECS: u64 = 3;
|
const GPU_CLOCKDOWN_TIMEOUT_SECS: u64 = 3;
|
||||||
const MAX_PSTATE_READ_ATTEMPTS: u32 = 5;
|
const MAX_PSTATE_READ_ATTEMPTS: u32 = 5;
|
||||||
const VENDOR_AMD: &str = "1002";
|
|
||||||
const STEAM_DECK_IDS: [&str; 2] = ["163F", "1435"];
|
const STEAM_DECK_IDS: [&str; 2] = ["163F", "1435"];
|
||||||
|
|
||||||
pub struct AmdGpuController {
|
pub struct AmdGpuController {
|
||||||
|
|||||||
@@ -3,6 +3,9 @@ mod amd;
|
|||||||
pub mod fan_control;
|
pub mod fan_control;
|
||||||
mod nvidia;
|
mod nvidia;
|
||||||
|
|
||||||
|
pub const VENDOR_AMD: &str = "1002";
|
||||||
|
pub const VENDOR_NVIDIA: &str = "10DE";
|
||||||
|
|
||||||
pub use amd::AmdGpuController;
|
pub use amd::AmdGpuController;
|
||||||
pub use nvidia::NvidiaGpuController;
|
pub use nvidia::NvidiaGpuController;
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,8 @@ use lact_schema::{
|
|||||||
};
|
};
|
||||||
use nvml_wrapper::{
|
use nvml_wrapper::{
|
||||||
bitmasks::device::ThrottleReasons,
|
bitmasks::device::ThrottleReasons,
|
||||||
enum_wrappers::device::{Clock, TemperatureSensor, TemperatureThreshold},
|
enum_wrappers::device::{Brand, Clock, TemperatureSensor, TemperatureThreshold},
|
||||||
|
enums::device::DeviceArchitecture,
|
||||||
Device, Nvml,
|
Device, Nvml,
|
||||||
};
|
};
|
||||||
use std::{
|
use std::{
|
||||||
@@ -248,6 +249,20 @@ impl NvidiaGpuController {
|
|||||||
|
|
||||||
Ok(power_states)
|
Ok(power_states)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// See https://github.com/ilya-zlobintsev/LACT/issues/418
|
||||||
|
fn vram_offset_ratio(&self) -> i32 {
|
||||||
|
let device = self.device();
|
||||||
|
if let (Ok(brand), Ok(architecture)) = (device.brand(), device.architecture()) {
|
||||||
|
let ratio = match (brand, architecture) {
|
||||||
|
(Brand::GeForce, DeviceArchitecture::Ada) => 2,
|
||||||
|
// TODO: check others
|
||||||
|
_ => 1,
|
||||||
|
};
|
||||||
|
return ratio;
|
||||||
|
}
|
||||||
|
1
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GpuController for NvidiaGpuController {
|
impl GpuController for NvidiaGpuController {
|
||||||
@@ -495,6 +510,7 @@ impl GpuController for NvidiaGpuController {
|
|||||||
gpc = Some(NvidiaClockInfo {
|
gpc = Some(NvidiaClockInfo {
|
||||||
max: max as i32,
|
max: max as i32,
|
||||||
offset,
|
offset,
|
||||||
|
offset_ratio: 1,
|
||||||
offset_range,
|
offset_range,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -511,6 +527,7 @@ impl GpuController for NvidiaGpuController {
|
|||||||
mem = Some(NvidiaClockInfo {
|
mem = Some(NvidiaClockInfo {
|
||||||
max: max as i32,
|
max: max as i32,
|
||||||
offset,
|
offset,
|
||||||
|
offset_ratio: self.vram_offset_ratio(),
|
||||||
offset_range,
|
offset_range,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -600,7 +617,7 @@ impl GpuController for NvidiaGpuController {
|
|||||||
let default_max_clock = device
|
let default_max_clock = device
|
||||||
.max_clock_info(Clock::Memory)
|
.max_clock_info(Clock::Memory)
|
||||||
.context("Could not read max memory clock")?;
|
.context("Could not read max memory clock")?;
|
||||||
let offset = max_mem_clock - default_max_clock as i32;
|
let offset = (max_mem_clock - default_max_clock as i32) * self.vram_offset_ratio();
|
||||||
debug!("Using mem clock offset {offset} (default max clock: {default_max_clock})");
|
debug!("Using mem clock offset {offset} (default max clock: {default_max_clock})");
|
||||||
|
|
||||||
device
|
device
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
---
|
---
|
||||||
source: lact-daemon/src/config.rs
|
source: lact-daemon/src/config.rs
|
||||||
expression: deserialized_config
|
expression: deserialized_config
|
||||||
snapshot_kind: text
|
|
||||||
---
|
---
|
||||||
|
version: 0
|
||||||
daemon:
|
daemon:
|
||||||
log_level: info
|
log_level: info
|
||||||
admin_groups:
|
admin_groups:
|
||||||
|
|||||||
@@ -412,7 +412,8 @@ fn set_nvidia_clock_offset(clock_info: &NvidiaClockInfo, adjustment_row: &Adjust
|
|||||||
let oc_adjustment = &adjustment_row.imp().adjustment;
|
let oc_adjustment = &adjustment_row.imp().adjustment;
|
||||||
oc_adjustment.set_lower((clock_info.max + clock_info.offset_range.0) as f64);
|
oc_adjustment.set_lower((clock_info.max + clock_info.offset_range.0) as f64);
|
||||||
oc_adjustment.set_upper((clock_info.max + clock_info.offset_range.1) as f64);
|
oc_adjustment.set_upper((clock_info.max + clock_info.offset_range.1) as f64);
|
||||||
oc_adjustment.set_value((clock_info.max + clock_info.offset) as f64);
|
oc_adjustment
|
||||||
|
.set_value((clock_info.max + (clock_info.offset / clock_info.offset_ratio)) as f64);
|
||||||
|
|
||||||
adjustment_row.set_visible(true);
|
adjustment_row.set_visible(true);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -159,6 +159,7 @@ pub struct NvidiaClocksTable {
|
|||||||
pub struct NvidiaClockInfo {
|
pub struct NvidiaClockInfo {
|
||||||
pub max: i32,
|
pub max: i32,
|
||||||
pub offset: i32,
|
pub offset: i32,
|
||||||
|
pub offset_ratio: i32,
|
||||||
pub offset_range: (i32, i32),
|
pub offset_range: (i32, i32),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user