ember: the AMD helper's gmax range is an offset from stock (PC 1's 9070 XT: -500 to 1000), not MHz: the clock knob stays closed on an offset range and the power ladder is bounded by plimit_range (-30 to 10) on a percent scale

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
igneum-josh 2026-10-05 23:43:43 +01:00
parent 5d7ced9d4f
commit 054e0416d8

View file

@ -1891,13 +1891,13 @@ impl Engine {
None if allowed || current > 0 => crate::detect::run_timeout(std::process::Command::new(&smi).args(["-i", &device, "-pl", &current.to_string()]), None, Duration::from_secs(20)).map(|out| out.contains("All done")),
None => Some(false),
};
shared.send(Cmd::TuneProbe(idx, Ok(TuneProbe { clock_max_mhz: clock_max, clock_min_mhz: 0, driver, direct: direct.unwrap_or(false), amd_ordinal: -1 })));
shared.send(Cmd::TuneProbe(idx, Ok(TuneProbe { clock_max_mhz: clock_max, clock_min_mhz: 0, driver, direct: direct.unwrap_or(false), amd_ordinal: -1, ..Default::default() })));
});
}
"amd" => {
let Some(exe) = self.bins.telemetry.clone() else {
self.sweep_pending = None;
self.shared.send(Cmd::TuneProbe(idx, Ok(TuneProbe { clock_max_mhz: 0, clock_min_mhz: 0, driver: c.driver.clone(), direct: false, amd_ordinal: -1 })));
self.shared.send(Cmd::TuneProbe(idx, Ok(TuneProbe { clock_max_mhz: 0, clock_min_mhz: 0, driver: c.driver.clone(), direct: false, amd_ordinal: -1, ..Default::default() })));
return;
};
let ordinal = c.amd_ordinal;
@ -1906,14 +1906,18 @@ impl Engine {
let out = crate::detect::run_timeout(std::process::Command::new(&exe).arg("--tune"), None, Duration::from_secs(20)).unwrap_or_default();
let t = out.lines().filter_map(parse_amd_tune).find(|t| ordinal < 0 || t.ordinal as i64 == ordinal);
shared.send(Cmd::TuneProbe(idx, Ok(match t {
Some(t) if t.ok => TuneProbe { clock_max_mhz: t.gmax_max as u32, clock_min_mhz: t.gmax_min as u32, driver, direct: true, amd_ordinal: t.ordinal as i64 },
Some(t) => TuneProbe { clock_max_mhz: 0, clock_min_mhz: 0, driver, direct: false, amd_ordinal: t.ordinal as i64 },
None => TuneProbe { clock_max_mhz: 0, clock_min_mhz: 0, driver, direct: false, amd_ordinal: -1 },
// PC 1's 9070 XT (ember-tune-pc1-1, 22:30 UTC): `gmax 0 gmax_range -500 1000`, an OFFSET from
// the stock clock, not MHz; a range with a negative floor is an offset range and the clock
// knob stays closed until the stock clock is known (the power limit is the AMD lever), and
// `plimit_range -30 10` bounds the power ladder (the percent scale rides power_* below)
Some(t) if t.ok => TuneProbe { clock_max_mhz: if t.gmax_min >= 0.0 && t.gmax_max > 0.0 { t.gmax_max as u32 } else { 0 }, clock_min_mhz: if t.gmax_min > 0.0 { t.gmax_min as u32 } else { 0 }, driver, direct: true, amd_ordinal: t.ordinal as i64, plimit_min: t.plimit_min, plimit_max: t.plimit_max },
Some(t) => TuneProbe { clock_max_mhz: 0, clock_min_mhz: 0, driver, direct: false, amd_ordinal: t.ordinal as i64, ..Default::default() },
None => TuneProbe { clock_max_mhz: 0, clock_min_mhz: 0, driver, direct: false, amd_ordinal: -1, ..Default::default() },
})));
});
}
_ => {
self.shared.send(Cmd::TuneProbe(idx, Ok(TuneProbe { clock_max_mhz: 0, clock_min_mhz: 0, driver: c.driver.clone(), direct: false, amd_ordinal: -1 })));
self.shared.send(Cmd::TuneProbe(idx, Ok(TuneProbe { clock_max_mhz: 0, clock_min_mhz: 0, driver: c.driver.clone(), direct: false, amd_ordinal: -1, ..Default::default() })));
}
}
}
@ -1939,7 +1943,13 @@ impl Engine {
// NVIDIA control: this process is elevated (direct), or Power control is on so the one-prompt helper may run.
// The --sweep job alone never counts: it must not raise a prompt on a PC with nobody there (5 October 2026).
let power_control = probe.direct || self.shared.settings.lock().unwrap().power_control;
let limits = crate::ember::Limits { power_default_w: c.power_default_w, power_min_w: c.power_min_w, power_max_w: c.power_max_w, clock_max_mhz: probe.clock_max_mhz, clock_min_mhz: probe.clock_min_mhz };
// AMD's power limit is a percent offset from the default (ADLX): the plan's watts scale becomes a percent
// scale (default 100, floor 100 + plimit_min, ceiling 100 + plimit_max), tune_apply sends pct - 100
let limits = if c.vendor == "amd" && probe.direct && probe.plimit_max >= probe.plimit_min && probe.plimit_min > -100.0 {
crate::ember::Limits { power_default_w: 100.0, power_min_w: 100.0 + probe.plimit_min, power_max_w: 100.0 + probe.plimit_max, clock_max_mhz: probe.clock_max_mhz, clock_min_mhz: probe.clock_min_mhz }
} else {
crate::ember::Limits { power_default_w: c.power_default_w, power_min_w: c.power_min_w, power_max_w: c.power_max_w, clock_max_mhz: probe.clock_max_mhz, clock_min_mhz: probe.clock_min_mhz }
};
let control = match c.vendor.as_str() {
"nvidia" => crate::ember::control_reason("nvidia", &limits, &c.device, power_control, false),
"amd" => crate::ember::control_reason("amd", &limits, &c.device, power_control, probe.direct && probe.amd_ordinal >= 0),
@ -2113,7 +2123,8 @@ impl Engine {
return;
}
let n = c.amd_ordinal.to_string();
let offset = step.point.power_pct as i64 - 100;
// the step's limit on the AMD scale is a percent (the probe's Limits); the offset is that minus 100
let offset = if c.power_default_w <= 0.0 { step.watts.round() as i64 - 100 } else { step.point.power_pct as i64 - 100 };
let unlocked = clock == 0 && offset == 0;
let gmax = if clock > 0 { clock } else { c.clock_max_mhz };
std::thread::spawn(move || {
@ -3465,6 +3476,9 @@ pub struct TuneProbe {
/// NVIDIA: this process sets limits itself (elevated); AMD: the helper answered its `--tune` line with ok
pub direct: bool,
pub amd_ordinal: i64,
/// AMD: the power offset range in percent from the `tune` line (PC 1's 9070 XT: -30 to 10)
pub plimit_min: f64,
pub plimit_max: f64,
}
/// One `tune` line of igneum-gpu-telemetry --tune: