diff --git a/app/igneum-app/src/ember.rs b/app/igneum-app/src/ember.rs index 9e9bf54c..c1cb4db8 100644 --- a/app/igneum-app/src/ember.rs +++ b/app/igneum-app/src/ember.rs @@ -20,9 +20,11 @@ use std::time::{Duration, Instant}; /// The power steps, percent of the card's default limit, highest first (the same ladder as src/sweep.rs). pub const POWER_STEPS_PCT: [u32; 6] = [100, 90, 80, 70, 60, 50]; /// The clock steps, percent of the card's maximum core clock, highest first; 100 = unlocked (the card's own boost). -pub const CLOCK_STEPS_PCT: [u32; 5] = [100, 90, 80, 70, 60]; +/// 6 October 2026, run 6: the 5090's best MH/W sat on the 60% floor (1,854 MHz: 0.563 MH/W, the rate within 0.15%), +/// so the ladder and the floor go to 45% of the maximum; the 1% rate tolerance is the guard below that +pub const CLOCK_STEPS_PCT: [u32; 7] = [100, 90, 80, 70, 60, 50, 45]; /// A card's clock floor when the vendor reports none: this share of its maximum core clock. -pub const CLOCK_FLOOR_PCT: u32 = 60; +pub const CLOCK_FLOOR_PCT: u32 = 45; /// A point may lose this much rate against the fastest point and still win on MH per watt (the manifest can change it). pub const RATE_TOLERANCE_PCT: f64 = 1.0; /// A step whose hottest GPU reading reaches this is marked hot and cannot win (the engine aborts at 90). @@ -601,7 +603,7 @@ pub fn prior_of(tuning: Option<&serde_json::Value>, key: &str, min_samples: u32) /// The fleet record: one `TUNE {json}` line. `machine` is a hash of the install id (never the id, never an address). #[allow(clippy::too_many_arguments)] -pub fn record_json(ts: f64, machine_hash: &str, app: &str, os: &str, card: &str, vendor: &str, driver: &str, class: &str, plan: PlanKind, rows: &[Row], chosen: Option<&Row>, before: Option<&Row>) -> serde_json::Value { +pub fn record_json(ts: f64, machine_hash: &str, app: &str, os: &str, card: &str, vendor: &str, driver: &str, class: &str, plan: PlanKind, rows: &[Row], chosen: Option<&Row>, before: Option<&Row>, floor: bool) -> serde_json::Value { serde_json::json!({ "ts": ts.round(), "machine": machine_hash, @@ -620,6 +622,8 @@ pub fn record_json(ts: f64, machine_hash: &str, app: &str, os: &str, card: &str, "eff": chosen.map(|r| r4(r.eff)).unwrap_or(0.0), "mhs": chosen.map(|r| r2(r.mhs)).unwrap_or(0.0), "watts": chosen.map(|r| r1(r.watts)).unwrap_or(0.0), + "floor": floor, + "note": if floor { "floor, not optimum" } else { "" }, }) } @@ -928,7 +932,7 @@ mod tests { #[test] fn the_full_plan_is_the_power_ladder_then_the_clock_ladder_at_the_chosen_power() { let plan = Plan::full(&l5090(), Point { clock_mhz: 0, power_pct: 80, mem_mhz: 0 }, 1.0); - assert_eq!(plan.len(), 5 + 4, "five power steps (60% and 50% clamp to 400 W; one kept) and four clock steps"); + assert_eq!(plan.len(), 5 + 6, "five power steps (60% and 50% clamp to 400 W; one kept) and six clock steps (90% down to 45%)"); let first = plan.next(&[]).unwrap(); assert_eq!((first.point, first.watts, first.kind), (Point { clock_mhz: 0, power_pct: 100, mem_mhz: 0 }, 575.0, Kind::Power)); // the power ladder: 575, 518, 460, 403, 400 @@ -952,8 +956,14 @@ mod tests { rows.push(row_at(s.point, 220.0, 123.5)); rows.push(row_at(plan.next(&rows).unwrap().point, 200.0, 118.0)); let s = plan.next(&rows).unwrap(); - assert_eq!(s.point.clock_mhz, 1854, "60% of 3,090 is the floor"); + assert_eq!(s.point.clock_mhz, 1854, "60% of 3,090"); rows.push(row_at(s.point, 180.0, 100.0)); + let s = plan.next(&rows).unwrap(); + assert_eq!(s.point.clock_mhz, 1545, "50%"); + rows.push(row_at(s.point, 170.0, 90.0)); + let s = plan.next(&rows).unwrap(); + assert_eq!(s.point.clock_mhz, 1390, "45% of 3,090 is the floor (6 October 2026)"); + rows.push(row_at(s.point, 160.0, 80.0)); assert_eq!(plan.next(&rows), None); // the choice: 2,472 MHz keeps 99.6% of the top rate at 220 W = 0.561 MH/W; 2,163 MHz (118 MH/s) is outside the 1% tolerance let best = choose(&rows, 1.0).unwrap(); @@ -961,7 +971,7 @@ mod tests { // a wider tolerance lets the 2,163 MHz step (0.590 MH/W, 4.8% slower) win assert_eq!(choose(&rows, 5.0).unwrap().point.clock_mhz, 2163); // no power limits, clocks only; no clocks, power only; nothing, empty - assert_eq!(Plan::full(&Limits { clock_max_mhz: 2000, ..Default::default() }, Point::default(), 1.0).len(), 4); + assert_eq!(Plan::full(&Limits { clock_max_mhz: 2000, ..Default::default() }, Point::default(), 1.0).len(), 6); assert_eq!(Plan::full(&Limits { power_default_w: 300.0, ..Default::default() }, Point::default(), 1.0).len(), 6); assert!(Plan::full(&Limits::default(), Point::default(), 1.0).is_empty()); } @@ -969,11 +979,11 @@ mod tests { #[test] fn limits_never_exceed_the_vendor_or_undercut_the_floor() { let l = l5090(); - assert_eq!(l.clock_floor(), 1854); - assert_eq!(l.clamp_clock(1000), 1854); + assert_eq!(l.clock_floor(), 1390); + assert_eq!(l.clamp_clock(1000), 1390); assert_eq!(l.clamp_clock(5000), 3090); assert_eq!(l.clamp_clock(0), 0, "unlocked stays unlocked"); - assert_eq!(Limits { clock_max_mhz: 3000, clock_min_mhz: 2100, ..Default::default() }.clamp_clock(1500), 2100, "the vendor's floor wins over the 60% rule"); + assert_eq!(Limits { clock_max_mhz: 3000, clock_min_mhz: 2100, ..Default::default() }.clamp_clock(1500), 2100, "the vendor's floor wins over the 45% rule"); assert_eq!(l.watts_for(100), 575.0); assert_eq!(l.watts_for(50), 400.0); assert_eq!(Limits { power_default_w: 300.0, power_max_w: 250.0, ..Default::default() }.watts_for(100), 250.0); @@ -1144,7 +1154,8 @@ mod tests { #[test] fn the_record_and_the_prior_round_trip_through_the_manifest_shape() { let chosen = row_at(Point { clock_mhz: 2472, power_pct: 100, mem_mhz: 0 }, 220.0, 123.5); - let rec = record_json(1_791_230_000.0, "8f3a2c1d", "0.3.10", "windows", "NVIDIA GeForce RTX 5090", "nvidia", "581.57", "l128w16", PlanKind::Full, &[chosen.clone()], Some(&chosen), None); + let rec = record_json(1_791_230_000.0, "8f3a2c1d", "0.3.10", "windows", "NVIDIA GeForce RTX 5090", "nvidia", "581.57", "l128w16", PlanKind::Full, &[chosen.clone()], Some(&chosen), None, false); + assert_eq!(rec["floor"], false); assert_eq!(rec["card"], "NVIDIA_GeForce_RTX_5090"); assert_eq!(rec["key"], "NVIDIA_GeForce_RTX_5090|581|l128w16"); assert_eq!(rec["driver_major"], "581"); diff --git a/app/igneum-app/src/engine.rs b/app/igneum-app/src/engine.rs index 6a8e28be..0d391cba 100644 --- a/app/igneum-app/src/engine.rs +++ b/app/igneum-app/src/engine.rs @@ -1845,7 +1845,7 @@ impl Engine { let dir = self.sweep_dir(); let _ = std::fs::create_dir_all(&dir); let script = dir.join("register-power-task.ps1"); - let installed: Vec = [std::env::var_os("LOCALAPPDATA").map(|l| PathBuf::from(l).join("Programs").join("Igneum Miner").join("igneum-app.exe")), std::env::var_os("ProgramFiles").map(|p| PathBuf::from(p).join("Igneum Miner").join("igneum-app.exe"))].into_iter().flatten().collect(); + let installed: Vec = crate::powertask::install_candidates(); match std::env::current_exe().map(|exe| std::fs::write(&script, [b"\xEF\xBB\xBF".as_slice(), crate::powertask::register_script(&crate::powertask::task_exe(&exe, &installed)).as_bytes()].concat())) { Ok(Ok(())) => format!("{} & \"{}\" -NoProfile -ExecutionPolicy Bypass -File \"{}\"", cmds.join(" & "), crate::platform::tool("powershell").display(), script.display()), _ => cmds.join(" & "), @@ -2351,7 +2351,7 @@ impl Engine { let dir = self.sweep_dir(); let _ = std::fs::create_dir_all(&dir); let script = dir.join("register-power-task.ps1"); - let installed: Vec = [std::env::var_os("LOCALAPPDATA").map(|l| PathBuf::from(l).join("Programs").join("Igneum Miner").join("igneum-app.exe")), std::env::var_os("ProgramFiles").map(|p| PathBuf::from(p).join("Igneum Miner").join("igneum-app.exe"))].into_iter().flatten().collect(); + let installed: Vec = crate::powertask::install_candidates(); if let Ok(exe) = std::env::current_exe() { let target = crate::powertask::task_exe(&exe, &installed); if std::fs::write(&script, [b"\xEF\xBB\xBF".as_slice(), crate::powertask::register_script(&target).as_bytes()].concat()).is_ok() { @@ -2785,9 +2785,12 @@ impl Engine { }; let _ = key; self.shared.save_settings(); - self.sweep_say(&format!("TUNE chosen card={} clock={} cap={} mem={} limit={:.0} watts={:.1} mhs={:.2} eff={:.4} plan={}{}", run.label, row.point.clock_mhz, row.point.power_pct, row.point.mem_mhz, row.limit, row.watts, row.mhs, row.eff, kind.name(), if pinned { " pinned=1" } else { "" })); + // 6 October 2026, run 6: the 5090's chosen clock sat on the ladder's floor (1,854 MHz = the old 60%), so a + // chosen point at the floor says so: it is the lowest step measured, not the optimum (Ember 2's climb walks on) + let floor = row.point.clock_mhz > 0 && row.point.clock_mhz <= run.plan.limits.clock_floor(); + self.sweep_say(&format!("TUNE chosen card={} clock={} cap={} mem={} limit={:.0} watts={:.1} mhs={:.2} eff={:.4} plan={}{}{}", run.label, row.point.clock_mhz, row.point.power_pct, row.point.mem_mhz, row.limit, row.watts, row.mhs, row.eff, kind.name(), if pinned { " pinned=1" } else { "" }, if floor { " floor=1 note=floor,_not_optimum" } else { "" })); let before = run.rows.first().filter(|_| kind == crate::ember::PlanKind::Full).cloned(); - let record = crate::ember::record_json(crate::platform::unix_now_f(), &crate::config::fingerprint8(&self.shared.runtime.machine_id), VERSION, crate::manifest::platform_name(), &name, &vendor, &driver, &class, kind, &run.rows, Some(&row), before.as_ref()); + let record = crate::ember::record_json(crate::platform::unix_now_f(), &crate::config::fingerprint8(&self.shared.runtime.machine_id), VERSION, crate::manifest::platform_name(), &name, &vendor, &driver, &class, kind, &run.rows, Some(&row), before.as_ref(), floor); self.sweep_say(&format!("TUNE {record}")); let line = crate::ember::tuned_line(row.mhs, row.watts, row.eff); match kind { diff --git a/app/igneum-app/src/powertask.rs b/app/igneum-app/src/powertask.rs index 0cb86b08..f674b31f 100644 --- a/app/igneum-app/src/powertask.rs +++ b/app/igneum-app/src/powertask.rs @@ -48,6 +48,9 @@ pub enum HelperCmd { MemReset, Quit, Remove, + /// re-point the task at the installed exe (no path argument: the helper finds the install folder itself, so a + /// writer of cmd.txt can never choose what runs elevated); 6 October 2026, run 6 registered a scratch copy + Reregister, } /// Parses one line: ` []` (the 0.3.9 form ` ` reads as a power limit; `quit` and @@ -71,6 +74,7 @@ pub fn parse_line(line: &str) -> Option<(u64, HelperCmd)> { [_, "lmc", m] if digits(m) => Some((seq, HelperCmd::MemClock(m.parse().ok()?))), [_, "rmc"] => Some((seq, HelperCmd::MemReset)), [_, "dev", d] if digits(d) => Some((seq, HelperCmd::Dev(d.to_string()))), + [_, "reregister"] => Some((seq, HelperCmd::Reregister)), _ => None, } } @@ -91,16 +95,37 @@ pub fn smi_args(dev: &str, c: &HelperCmd) -> Option> { /// kit a job fetched: `\app\jobs\\igneum-app-ember.exe`), in which case the installed app's own /// exe beside it is the durable target (6 October 2026: the first registration on PC 1 came from such a kit). pub fn task_exe(running: &Path, install_candidates: &[PathBuf]) -> PathBuf { - // either separator: the path comes from Windows, the tests run on the Mac too - let under_jobs = running.to_string_lossy().split(['\\', '/']).any(|c| c.eq_ignore_ascii_case("jobs")); - if under_jobs { - if let Some(p) = install_candidates.iter().find(|p| p.is_file()) { - return p.clone(); - } + // the running exe stands only when it is itself an install candidate (either separator, any case: the path + // comes from Windows, the tests run on the Mac too); a kit under jobs\ or a measurement run's scratch copy + // (run 6, 6 October 2026: igneum-tune-\bin\igneum-app.exe) never becomes the task's action when an + // installed exe exists + let norm = |p: &Path| p.to_string_lossy().replace('/', "\\").to_ascii_lowercase(); + let me = norm(running); + if install_candidates.iter().any(|p| norm(p) == me) { + return running.to_path_buf(); + } + if let Some(p) = install_candidates.iter().find(|p| p.is_file()) { + return p.clone(); } running.to_path_buf() } +/// Where the installed exe lives: the per-user install, then the old administrator install. +pub fn install_candidates() -> Vec { + [ + std::env::var_os("LOCALAPPDATA").map(|l| PathBuf::from(l).join("Programs").join("Igneum Miner").join("igneum-app.exe")), + std::env::var_os("ProgramFiles").map(|p| PathBuf::from(p).join("Igneum Miner").join("igneum-app.exe")), + ] + .into_iter() + .flatten() + .collect() +} + +/// The PowerShell that prints the task's action (what `reregister` is proven by). +pub fn readback_command() -> String { + format!("$t = Get-ScheduledTask -TaskName '{TASK_NAME}' -ErrorAction SilentlyContinue; if ($t) {{ Write-Output ($t.Actions[0].Execute + ' ' + $t.Actions[0].Arguments) }}; exit 0") +} + /// The PowerShell that registers the task (run inside the ONE elevated step, with the caps). `exe` is this /// executable's path in the install folder. Principal: the signed-in user, interactive logon, highest run level; no /// trigger; may start on battery; one hour limit per run; multiple starts are ignored while one runs. @@ -195,6 +220,26 @@ pub fn run_helper(dir: &Path) -> i32 { return if ok { 0 } else { 1 }; } _ if seq <= last_seq => continue, + HelperCmd::Reregister => { + last_seq = seq; + idle = Instant::now(); + let Some(target) = install_candidates().into_iter().find(|p| p.is_file()) else { + log(&format!("{seq} reregister: no installed exe found")); + continue; + }; + let script = dir.join("register-power-task.ps1"); + let ok = std::fs::write(&script, [b"\xEF\xBB\xBF".as_slice(), register_script(&target).as_bytes()].concat()).is_ok() && { + let mut p = std::process::Command::new(crate::platform::tool("powershell")); + p.args(["-NoProfile", "-ExecutionPolicy", "Bypass", "-File", &script.display().to_string()]); + crate::platform::quiet(&mut p); + p.status().map(|s| s.success()).unwrap_or(false) + }; + let mut q = std::process::Command::new(crate::platform::tool("powershell")); + q.args(["-NoProfile", "-ExecutionPolicy", "Bypass", "-Command", &readback_command()]); + crate::platform::quiet(&mut q); + let now = q.output().map(|o| String::from_utf8_lossy(&o.stdout).trim().to_string()).unwrap_or_default(); + log(&format!("{seq} reregister {}: the task now runs {now}", if ok { "ok" } else { "failed" })); + } HelperCmd::Dev(d) => { last_seq = seq; idle = Instant::now(); @@ -244,6 +289,10 @@ mod tests { assert_eq!(parse_line("5 403"), Some((5, HelperCmd::PowerLimit(403))), "the 0.3.9 form"); assert_eq!(parse_line("quit"), Some((0, HelperCmd::Quit))); assert_eq!(parse_line("remove"), Some((0, HelperCmd::Remove))); + assert_eq!(parse_line("12 reregister"), Some((12, HelperCmd::Reregister))); + assert_eq!(parse_line("12 reregister C:\\evil.exe"), None, "no path argument: the helper picks the install folder itself"); + assert_eq!(smi_args("0", &HelperCmd::Reregister), None); + assert!(readback_command().contains("Actions[0].Execute")); // nothing else: no shell, no path, no string argument, no oversized number for bad in ["7 pl 460; calc", "7 pl -460", "7 pl 4.60", "7 lgc 0,2472", "7 rm C:\\x", "x pl 460", "7 pl", "7 lgc 12345678", "7 dev ../1", "", "7 pl 460 extra"] { assert_eq!(parse_line(bad), None, "{bad:?}"); @@ -289,7 +338,11 @@ mod tests { let here = std::env::current_exe().unwrap(); assert!(here.is_file()); assert_eq!(task_exe(kit, &[here.clone()]), here); - assert_eq!(task_exe(own, &[here]), own.to_path_buf()); + // a measurement run's scratch copy (run 6, 6 October 2026) is not under jobs\ and still yields + let scratch = Path::new(r"C:\Users\Admin\AppData\Local\igneum-tune-20261006-170105\bin\igneum-app.exe"); + assert_eq!(task_exe(scratch, &[here.clone()]), here); + // the installed exe itself stands (matched by path, any case or separator) + assert_eq!(task_exe(own, &[PathBuf::from(r"c:/users/admin/appdata/local/programs/igneum miner/IGNEUM-APP.EXE"), here]), own.to_path_buf()); } #[test] diff --git a/docs/bench-log.md b/docs/bench-log.md index cf16b9ed..52b65be7 100644 --- a/docs/bench-log.md +++ b/docs/bench-log.md @@ -2244,3 +2244,13 @@ Run b (`segments-pc2-pv1b`, 07:20Z to 07:51Z) claimed nothing in 88 passes: the | The rule as shipped (no switch): fresh refused while the previous segment is pending (known-failed), accepted after it is unproven | 22 passed | 166.2 s | | `--fresh-rule 0`: fresh accepted while the previous segment is pending, `freshAdmissible` true, still refused after a proven one, the second offer a duplicate ("segment already paid") | 23 passed | 139.9 s | + +## 6 October 2026, 16:01Z: Ember run 6 on PC 1 (ember-tune-pc1-6, 0.3.13 + kit-6 = 564bdea, elevated, one click) + +The helper registered inside the run but on the scratch copy (fixed: task_exe, the `reregister` verb; see the plan's +run 6 notes). RTX 5090: chosen 1854 MHz at 100% = 127.71 MH/s at 226.8 W, 0.563 MH/W, against 127.9 at 311.0 W +(0.411) untuned: 84 W saved for 0.15% of rate. Every cap step 60 to 100% read 311 to 313 W (the cap never binds). +The clock ladder: 2781 MHz 298.8 W 0.428; 2472 MHz 262.0 W 0.488; 2163 MHz 239.9 W 0.533; 1854 MHz 226.8 W 0.563 +(the floor, not the optimum: the next cut's ladder goes to 45%). RTX 4070: caps 100 to 60% all 106.0 W 28.71 MH/s +(0.271); 50% 99.1 W 28.70 (0.290); clock 2794 MHz at 50% 99.1 W (0.290); 2484 MHz 81.1 W 28.73 (0.354); further rows +and the 9070 XT ladder below once the run closes. diff --git a/docs/plans/ember-tune.md b/docs/plans/ember-tune.md index 6d771c17..2531341b 100644 --- a/docs/plans/ember-tune.md +++ b/docs/plans/ember-tune.md @@ -202,7 +202,13 @@ scripts carry them), `relay/test/ember.test.mjs` (climb records aggregate into a Owed: the measured comparison on PC 1 (one climb per card against run 5's ladder rows, no prompt through the Power Helper), the temperature-band trigger, NVAPI/nvidia-settings for cards whose driver refuses `-lmc`. -## 7a. One administrator approval, ever (0.3.13; the project lead, 6 October 2026, 11:50 UTC) +## 7a. One administrator approval, ever + +Threat note, `reregister` (6 October 2026): the verb takes no path. The helper, already elevated, picks the installed +exe from the two install folders itself (`powertask::install_candidates`), so the most a writer of `cmd.txt` can do +is point the task back at the installed app. The exposure that remains is the one every per-user install with a +highest-run task has: the install folder is the user's own, so a process running as the user can replace the exe the +task runs. The installer's code signature and the OTA's hash check are the answer to that, not the task. (0.3.13; the project lead, 6 October 2026, 11:50 UTC) What 0.3.12 does: Power control on raises one prompt and sets every cap in that step; every later cap (an app start, a reboot, a slider move) and every tune's helper is another elevated launch, so another prompt. Not "once, ever". @@ -225,6 +231,35 @@ process, registry key or other binary is reachable through it. Tests: `powertask non-digit or extra argument, the arguments reach nvidia-smi as a list, the registration is per-user, highest, trigger-less and quote-safe, a stale command file runs nothing). +### Run 6 (6 October 2026, 16:01Z, PC 1 on 0.3.13 with kit-6 = 564bdea, elevated, the project lead's one click) + +The scheduled task registered inside the run ("helper registered=true"), but its action was the run's scratch copy +(`igneum-tune-20261006-170105\bin\igneum-app.exe`): `task_exe` preferred the installed exe only for a path under +`jobs\`. Fixed the same hour: the running exe stands only when it is itself an install candidate; and the helper +gained a `reregister` verb (no path argument, so a writer of cmd.txt can never choose what runs elevated: the helper +finds the install folder itself, re-registers, and logs the task's action read back). The scratch root stays until +that read-back shows the installed path. + +RTX 5090 (driver 617.14, class l128w0, no prior), full plan, every row ok, 65 C at most: + +| Step | Clock cap | Power | Draw | Rate | MH/W | +|---|---|---|---|---|---| +| 0 (before) | unlocked | 100% 575 W | 311.0 W | 127.90 | 0.411 | +| 1 to 4 | unlocked | 90, 80, 70, 60% | 313 W | 127.9 | 0.408 to 0.409 (the cap never binds) | +| 5 | 2781 MHz | 100% | 298.8 W | 127.89 | 0.428 | +| 6 | 2472 MHz | 100% | 262.0 W | 127.86 | 0.488 | +| 7 | 2163 MHz | 100% | 239.9 W | 127.82 | 0.533 | +| 8 (chosen) | 1854 MHz | 100% | 226.8 W | 127.71 | 0.563 | + +The per-user number: 84 W saved for 0.15% of rate, 37% more hashes per watt. The chosen clock is the ladder's floor +(60% of 3,090), not the optimum: `CLOCK_STEPS_PCT` now ends 60, 50, 45 and `CLOCK_FLOOR_PCT` is 45, with the 1% rate +tolerance as the guard; a chosen point on the floor carries `floor=1 note=floor, not optimum` in the chosen line and +the record. Ember 2's climb starts from the chosen point. + +RTX 4070 (same driver and class): caps 100 to 60% all 106 W at 28.71 MH/s (0.271); 50% (100 W) 99.1 W 28.70 (0.290); +clock 2794 at 50% 99.1 W (0.290); 2484 MHz 81.1 W 28.73 MH/s (0.354); the rest of the ladder and the 9070 XT rows in +the bench log entry for the run. + ## 8a. Next-cut notes (for the 0.3.12 shipper) | Commit | What | Where | diff --git a/relay/playbooks/ember-helper-repoint-pc1.ps1 b/relay/playbooks/ember-helper-repoint-pc1.ps1 new file mode 100644 index 00000000..0b20f192 --- /dev/null +++ b/relay/playbooks/ember-helper-repoint-pc1.ps1 @@ -0,0 +1,69 @@ +# Ember: re-point the Igneum Power Helper at the installed exe and prove the no-prompt cap (6 October 2026) +# Unelevated. Run 6 registered the task on the run's scratch copy of the engine (igneum-tune-\bin\igneum-app.exe). +# This script: (1) reads the task's action back; (2) puts the kit-7 engine (carries the `reregister` verb) in place of +# that scratch copy, which is the user's own file; (3) starts the task (no prompt: it is registered) and writes +# ` reregister` to the installed app's sweep\cmd.txt; the elevated helper picks the install folder itself and +# re-registers; (4) reads the action back again and prints it; (5) the proof: asks the installed app to apply its caps +# through the task and prints the "power cap: through the Igneum Power Helper task (no prompt)" line from its log. +# Never quits, pauses or resumes the installed app (the 5 October rule). The scratch root is left in place. +$ErrorActionPreference = 'Continue' +function Say([string] $s) { Write-Output ('[' + (Get-Date -Format 'HH:mm:ss') + '] ' + $s) } +$appDir = Join-Path $env:LOCALAPPDATA 'igneum\app' +$sweep = Join-Path $appDir 'sweep' +$cmd = Join-Path $sweep 'cmd.txt' +$hlog = Join-Path $sweep 'helper.log' +$installed = Join-Path $env:LOCALAPPDATA 'Programs\Igneum Miner\igneum-app.exe' +$kit = Join-Path $appDir 'jobs\ember-kit-7\igneum-app-ember.exe' +function Action() { $t = Get-ScheduledTask -TaskName 'Igneum Power Helper' -ErrorAction SilentlyContinue; if ($t) { return ($t.Actions[0].Execute + ' ' + $t.Actions[0].Arguments) } else { return '' } } +$before = Action +Write-Output ('RESULT TUNE task_action_before=' + $(if ($before) { $before } else { 'none' })) +if (-not $before) { Write-Output 'RESULT TUNE error=task_not_registered'; exit 1 } +$exe = ($before -split ' --power-helper')[0].Trim() +if ($exe -like '*Programs\Igneum Miner\igneum-app.exe') { Say 'the task already runs the installed exe' } +else { + if (-not (Test-Path -LiteralPath $kit)) { Write-Output ('RESULT TUNE error=no_kit_7 at ' + $kit); exit 1 } + if (-not (Test-Path -LiteralPath $exe)) { Write-Output ('RESULT TUNE error=task_exe_missing ' + $exe); exit 1 } + # a helper still running from an earlier start would hold the exe: ask it to quit through its own file, then wait + Add-Content -LiteralPath $cmd -Value 'quit' -Encoding ascii + Start-Sleep -Seconds 6 + Set-Content -LiteralPath $cmd -Value '' -Encoding ascii + $sha0 = (Get-FileHash -LiteralPath $kit -Algorithm SHA256).Hash.ToLower() + Copy-Item -LiteralPath $kit -Destination $exe -Force + $sha1 = (Get-FileHash -LiteralPath $exe -Algorithm SHA256).Hash.ToLower() + Write-Output ('RESULT TUNE task_exe_replaced=' + ($sha0 -eq $sha1) + ' sha256=' + $sha1 + ' path=' + $exe) + if ($sha0 -ne $sha1) { Write-Output 'RESULT TUNE error=copy_mismatch'; exit 1 } + $seq = 1 + if (Test-Path -LiteralPath $cmd) { foreach ($l in (Get-Content -LiteralPath $cmd)) { if ($l -match '^(\d+) ') { $n = [int]$Matches[1]; if ($n -ge $seq) { $seq = $n + 1 } } } } + $mark = 0; if (Test-Path -LiteralPath $hlog) { $mark = (Get-Content -LiteralPath $hlog).Count } + Start-ScheduledTask -TaskName 'Igneum Power Helper' + Start-Sleep -Seconds 3 + Add-Content -LiteralPath $cmd -Value ($seq.ToString() + ' reregister') -Encoding ascii + $got = '' + for ($i = 0; $i -lt 30; $i++) { + Start-Sleep -Seconds 2 + if (Test-Path -LiteralPath $hlog) { $lines = @(Get-Content -LiteralPath $hlog); if ($lines.Count -gt $mark) { $new = $lines[$mark..($lines.Count - 1)] | Where-Object { $_ -match 'reregister' }; if ($new) { $got = [string]$new[-1]; break } } } + } + Write-Output ('RESULT TUNE helper_log=' + $(if ($got) { $got } else { 'no reregister line within 60 s' })) +} +$after = Action +Write-Output ('RESULT TUNE task_action_after=' + $(if ($after) { $after } else { 'none' })) +if ($after -notlike '*Programs\Igneum Miner\igneum-app.exe --power-helper*') { Write-Output 'RESULT TUNE error=task_not_on_installed_exe'; exit 1 } +# the proof: the installed app applies its caps through the task; its log says so without a prompt +$u = Join-Path $appDir 'app.url' +if (Test-Path -LiteralPath $u) { + $base = (Get-Content -LiteralPath $u -Raw).Trim() + $logs = Get-ChildItem -Path (Join-Path $appDir 'logs') -Filter 'app-*.log' -ErrorAction SilentlyContinue | Sort-Object LastWriteTime -Descending | Select-Object -First 1 + $mark2 = 0; if ($logs) { $mark2 = (Get-Content -LiteralPath $logs.FullName).Count } + try { Invoke-WebRequest -Uri ($base + 'api/power/control') -Method POST -Body '{"on":true}' -ContentType 'application/json' -UseBasicParsing -TimeoutSec 5 | Out-Null } catch { Say ('power/control: ' + $_.Exception.Message) } + $line = '' + for ($i = 0; $i -lt 20; $i++) { + Start-Sleep -Seconds 3 + $logs = Get-ChildItem -Path (Join-Path $appDir 'logs') -Filter 'app-*.log' -ErrorAction SilentlyContinue | Sort-Object LastWriteTime -Descending | Select-Object -First 1 + if ($logs) { $all = @(Get-Content -LiteralPath $logs.FullName); if ($all.Count -gt $mark2) { $hit = $all[$mark2..($all.Count - 1)] | Where-Object { $_ -match 'power cap' }; if ($hit) { $line = ($hit | ForEach-Object { [string]$_ }) -join ' | '; if ($line -match 'Power Helper task') { break } } } } + } + Write-Output ('RESULT TUNE cap_proof=' + $(if ($line) { $line } else { 'no power cap line within 60 s' })) +} else { Write-Output 'RESULT TUNE cap_proof=no app.url (installed app not running)' } +$smi = Join-Path $env:ProgramFiles 'NVIDIA Corporation\NVSMI\nvidia-smi.exe' +if (-not (Test-Path $smi)) { $smi = Join-Path $env:SystemRoot 'System32\nvidia-smi.exe' } +if (Test-Path $smi) { Write-Output ('RESULT TUNE nvidia_now ' + ((& $smi --query-gpu=index,name,power.draw,power.limit,clocks.gr --format=csv,noheader 2>&1 | Out-String) -replace "`r?`n", ' | ')) } +exit 0 diff --git a/site/build.mjs b/site/build.mjs index 7760c3fb..736c1dc4 100644 --- a/site/build.mjs +++ b/site/build.mjs @@ -367,6 +367,8 @@ for (const [file, active] of PAGES) { '

Fleet tuning priors

', '

Ember Tune runs on every card the app mines with: the power limit and the core clock are stepped on the live program and the card keeps the point with the best MH per watt within 1% of its top rate. Every finished tune is reported back without anything that identifies the owner, and the fleet\'s median point per card model, driver major and program class comes back down inside the signed update manifest as the starting point for the next card of that model. A model needs ' + pj.min_samples + ' reports before its prior is used.

', ptable, + // the team's own measured rows (site/miner-priors.json "team"): the per-user sentence per card, with its job + ...((pj.team || []).length ? ['

Measured by the team

', '
    ' + pj.team.map(t => `
  • ${esc(t.card)} (driver ${esc(t.driver_major)}, class ${esc(t.class)}, ${esc(t.date)}, job ${esc(t.job)}): ${esc(t.point)}. ${esc(t.sentence)}${t.note ? ' ' + esc(t.note) : ''}
  • `).join('') + '
'] : []), `

Rows: ${prows.length}${pj.generated ? ', generated ' + pj.generated : ''}. Source file: site/miner-priors.json in the repository, written from the fleet records by tools/tuning.mjs --priors --site.

`, ].join('\n')); const toc = [{ lvl: 2, t: 'The table', id: 'table' }, { lvl: 2, t: 'How a row gets here', id: 'how' }, { lvl: 2, t: 'Fleet tuning priors', id: 'priors' }]; diff --git a/site/miner-priors.json b/site/miner-priors.json index cdb7b4fa..c34d8e4a 100644 --- a/site/miner-priors.json +++ b/site/miner-priors.json @@ -2,5 +2,24 @@ "_about": "Rows of the fleet priors table at /miners (site/build.mjs), written by tools/tuning.mjs --priors --site from the TUNE records every Igneum Miner uploads. One row per card model, driver major and program class: the median tuned point, MH per watt, the spread and the sample count. No machine names, no addresses.", "generated": null, "min_samples": 5, - "rows": [] + "rows": [], + "_team": "Rows measured on the team's own hardware, from the job log named in each row; they stand until the fleet's prior for the model reaches five samples.", + "team": [ + { + "card": "NVIDIA GeForce RTX 5090", + "driver_major": "617", + "class": "l128w0", + "date": "2026-10-06", + "job": "ember-tune-pc1-6", + "point": "1854 MHz core cap, power limit 100% (575 W)", + "mh_s": 127.71, + "watts": 226.8, + "mh_per_w": 0.563, + "untuned_mh_s": 127.9, + "untuned_watts": 311.0, + "untuned_mh_per_w": 0.411, + "sentence": "84 W saved for 0.15% of rate: 127.71 MH/s at 226.8 W (0.563 MH/W) against 127.9 MH/s at 311 W untuned (0.411 MH/W), 37% more hashes per watt.", + "note": "The chosen clock is the ladder's floor (60% of the maximum), not the optimum; the next cut's ladder goes to 45% and the climb walks on from here." + } + ] } diff --git a/site/miners.html b/site/miners.html index 5b1e1642..eab8aece 100644 --- a/site/miners.html +++ b/site/miners.html @@ -189,6 +189,8 @@ th{font-family:var(--f-mono);font-size:12px;letter-spacing:.12em;text-transform:

Fleet tuning priors

Ember Tune runs on every card the app mines with: the power limit and the core clock are stepped on the live program and the card keeps the point with the best MH per watt within 1% of its top rate. Every finished tune is reported back without anything that identifies the owner, and the fleet's median point per card model, driver major and program class comes back down inside the signed update manifest as the starting point for the next card of that model. A model needs 5 reports before its prior is used.

No tune reports yet. The first rows appear once five machines with the same card model have reported.

+

Measured by the team

+
  • NVIDIA GeForce RTX 5090 (driver 617, class l128w0, 2026-10-06, job ember-tune-pc1-6): 1854 MHz core cap, power limit 100% (575 W). 84 W saved for 0.15% of rate: 127.71 MH/s at 226.8 W (0.563 MH/W) against 127.9 MH/s at 311 W untuned (0.411 MH/W), 37% more hashes per watt. The chosen clock is the ladder's floor (60% of the maximum), not the optimum; the next cut's ladder goes to 45% and the climb walks on from here.

Rows: 0. Source file: site/miner-priors.json in the repository, written from the fleet records by tools/tuning.mjs --priors --site.

Generated from the repository at build time. Times are UTC. Machine names are model names.