Merge ca3-v4-amend a7ff10a2 into master (gate: green on a7ff10a2, recorded by tools/ci/pre-push.sh; landed on the build mirror)
This commit is contained in:
commit
da2fc101e0
62 changed files with 1959 additions and 103 deletions
|
|
@ -23,8 +23,15 @@ pub const POWER_STEPS_PCT: [u32; 6] = [100, 90, 80, 70, 60, 50];
|
|||
/// 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 = 45;
|
||||
/// A card's clock floor when the vendor reports none: this share of its maximum core clock. 7 October 2026, the PC 1
|
||||
/// efficiency passes (docs/bench-log.md): the 5090's best MH per watt sat at 1,200 to 1,300 MHz (39 to 42 percent of
|
||||
/// 3,090) and the rate fell past 5 percent only at 1,200 (class v3) and 1,100 (class v4), under the old 45 percent
|
||||
/// floor; so the ladder continues below 45 percent in [`CLOCK_FINE_STEP_MHZ`] steps down to 20 percent of the maximum
|
||||
/// (618 MHz on the 5090) and the stop rule, not the floor, ends the search.
|
||||
pub const CLOCK_FLOOR_PCT: u32 = 20;
|
||||
/// Below the percent ladder's last rung (45 percent) the clock ladder descends in steps of this many MHz until the
|
||||
/// rate falls more than the tolerance under the cap point's rate (the knee), a step faults, or the floor is reached.
|
||||
pub const CLOCK_FINE_STEP_MHZ: u32 = 100;
|
||||
/// 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).
|
||||
|
|
@ -193,6 +200,9 @@ pub struct Plan {
|
|||
pub tolerance_pct: f64,
|
||||
power: Vec<Step>,
|
||||
clock_pcts: Vec<u32>,
|
||||
/// the clock ladder below the percent rungs: MHz values from the last rung minus one fine step down to the floor
|
||||
/// (the core-clock knob of 7 October 2026; empty when the card has no readable maximum clock)
|
||||
clock_fine: Vec<u32>,
|
||||
fixed: Vec<Step>,
|
||||
/// Ember 2 (Climb): the start point, the step sizes and the step budget
|
||||
climb: Option<Climb>,
|
||||
|
|
@ -218,7 +228,7 @@ impl Plan {
|
|||
let mem_step = if limits.mem_max_mhz > limits.mem_default_mhz { ((limits.mem_max_mhz - limits.mem_default_mhz) / 20).max(25) } else { 0 };
|
||||
let core_step = if limits.clock_max_mhz > 0 { (limits.clock_max_mhz / 20).max(25) } else { 0 };
|
||||
let start = Point { clock_mhz: limits.clamp_clock(start.clock_mhz), power_pct: start.power_pct.clamp(50, 100), mem_mhz: limits.clamp_mem(start.mem_mhz) };
|
||||
Plan { kind: PlanKind::Climb, limits: limits.clone(), before: start, tolerance_pct: goal.tolerance_pct(tolerance_pct), power: Vec::new(), clock_pcts: Vec::new(), fixed: Vec::new(), climb: Some(Climb { start, mem_step, core_step, budget: 5, goal }) }
|
||||
Plan { kind: PlanKind::Climb, limits: limits.clone(), before: start, tolerance_pct: goal.tolerance_pct(tolerance_pct), power: Vec::new(), clock_pcts: Vec::new(), clock_fine: Vec::new(), fixed: Vec::new(), climb: Some(Climb { start, mem_step, core_step, budget: 5, goal }) }
|
||||
}
|
||||
|
||||
/// The goal's score of a row: MH per watt for efficiency and balanced, the rate for maximum rate.
|
||||
|
|
@ -285,7 +295,22 @@ impl Plan {
|
|||
}
|
||||
}
|
||||
let clock_pcts = if limits.clock_max_mhz > 0 { CLOCK_STEPS_PCT[1..].to_vec() } else { Vec::new() };
|
||||
Plan { kind: PlanKind::Full, limits: limits.clone(), before, tolerance_pct, power, clock_pcts, fixed: Vec::new(), climb: None }
|
||||
// the fine ladder: from the last percent rung down to the floor in CLOCK_FINE_STEP_MHZ steps (the knob of
|
||||
// 7 October 2026; the stop rule in `next` ends it at the knee)
|
||||
let mut clock_fine = Vec::new();
|
||||
if limits.clock_max_mhz > 0 {
|
||||
let last_pct = limits.clamp_clock(limits.clock_max_mhz * CLOCK_STEPS_PCT[CLOCK_STEPS_PCT.len() - 1] / 100);
|
||||
let floor = limits.clock_floor();
|
||||
let mut m = (last_pct / CLOCK_FINE_STEP_MHZ) * CLOCK_FINE_STEP_MHZ;
|
||||
if m >= last_pct {
|
||||
m = m.saturating_sub(CLOCK_FINE_STEP_MHZ);
|
||||
}
|
||||
while m >= floor && m > 0 {
|
||||
clock_fine.push(m);
|
||||
m = m.saturating_sub(CLOCK_FINE_STEP_MHZ);
|
||||
}
|
||||
}
|
||||
Plan { kind: PlanKind::Full, limits: limits.clone(), before, tolerance_pct, power, clock_pcts, clock_fine, fixed: Vec::new(), climb: None }
|
||||
}
|
||||
|
||||
/// The prior's point, then one neighbour: the next clock step up when the prior caps the clock (is the cap
|
||||
|
|
@ -304,14 +329,14 @@ impl Plan {
|
|||
if neighbour != p {
|
||||
fixed.push(Step { point: neighbour, watts: limits.watts_for(neighbour.power_pct), kind: Kind::Confirm });
|
||||
}
|
||||
Plan { kind: PlanKind::Confirm, limits: limits.clone(), before, tolerance_pct, power: Vec::new(), clock_pcts: Vec::new(), fixed, climb: None }
|
||||
Plan { kind: PlanKind::Confirm, limits: limits.clone(), before, tolerance_pct, power: Vec::new(), clock_pcts: Vec::new(), clock_fine: Vec::new(), fixed, climb: None }
|
||||
}
|
||||
|
||||
/// One step at the card's current point: the before number, and all a measure-only card (Apple, or NVIDIA
|
||||
/// with Power control off) reports.
|
||||
pub fn baseline(limits: &Limits, before: Point, tolerance_pct: f64) -> Plan {
|
||||
let fixed = vec![Step { point: before, watts: limits.watts_for(before.power_pct), kind: Kind::Baseline }];
|
||||
Plan { kind: PlanKind::Baseline, limits: limits.clone(), before, tolerance_pct, power: Vec::new(), clock_pcts: Vec::new(), fixed, climb: None }
|
||||
Plan { kind: PlanKind::Baseline, limits: limits.clone(), before, tolerance_pct, power: Vec::new(), clock_pcts: Vec::new(), clock_fine: Vec::new(), fixed, climb: None }
|
||||
}
|
||||
|
||||
/// How many steps the plan has at most (the clock ladder counts whether or not it runs).
|
||||
|
|
@ -319,7 +344,42 @@ impl Plan {
|
|||
if let Some(c) = &self.climb {
|
||||
return c.budget;
|
||||
}
|
||||
self.fixed.len() + self.power.len() + self.clock_pcts.len()
|
||||
self.fixed.len() + self.power.len() + self.clock_pcts.len() + self.clock_fine.len()
|
||||
}
|
||||
|
||||
/// The cap point's row: the power ladder's choice (the row the clock search is read against), else the first row.
|
||||
pub fn cap_row(&self, rows: &[Row]) -> Option<Row> {
|
||||
if self.power.is_empty() {
|
||||
rows.first().cloned()
|
||||
} else {
|
||||
choose(&rows[..self.power.len().min(rows.len())], self.tolerance_pct).or_else(|| rows.first().cloned())
|
||||
}
|
||||
}
|
||||
|
||||
/// Why the clock search ended after `rows`, in words, or None while it runs: a faulted clock row (a rejected or
|
||||
/// mismatched hash during the hold: the fingerprint check), the knee (the rate under the cap point's by more than
|
||||
/// the tolerance), or the floor.
|
||||
pub fn clock_stop_reason(&self, rows: &[Row]) -> Option<String> {
|
||||
let last = rows.last()?;
|
||||
if last.point.clock_mhz == 0 || !matches!(self.kind, PlanKind::Full) {
|
||||
return None;
|
||||
}
|
||||
let clock_rows = rows.len().saturating_sub(self.power.len());
|
||||
if clock_rows == 0 {
|
||||
return None;
|
||||
}
|
||||
if last.mark == Some(Mark::Faulted) {
|
||||
return Some(format!("fingerprint mismatch at {} MHz, clocks reset", last.point.clock_mhz));
|
||||
}
|
||||
if let Some(cap) = self.cap_row(rows) {
|
||||
if last.usable() && cap.usable() && cap.mhs > 0.0 && last.mhs < cap.mhs * (1.0 - self.tolerance_pct.max(0.0) / 100.0) {
|
||||
return Some(format!("rate fell {:.1} percent at {} MHz", 100.0 * (cap.mhs - last.mhs) / cap.mhs, last.point.clock_mhz));
|
||||
}
|
||||
}
|
||||
if clock_rows >= self.clock_pcts.len() + self.clock_fine.len() {
|
||||
return Some(format!("the floor at {} MHz", last.point.clock_mhz));
|
||||
}
|
||||
None
|
||||
}
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.len() == 0
|
||||
|
|
@ -338,10 +398,22 @@ impl Plan {
|
|||
return Some(self.power[i].clone());
|
||||
}
|
||||
let k = i - self.power.len();
|
||||
let pct = *self.clock_pcts.get(k)?;
|
||||
// the stop rule (7 October 2026): a faulted clock row or the knee ends the search; the choice is made among
|
||||
// the rows so far
|
||||
if k > 0 {
|
||||
if let Some(reason) = self.clock_stop_reason(rows) {
|
||||
if !reason.starts_with("the floor") {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
}
|
||||
let clock = if k < self.clock_pcts.len() {
|
||||
self.limits.clamp_clock(self.limits.clock_max_mhz * self.clock_pcts[k] / 100)
|
||||
} else {
|
||||
*self.clock_fine.get(k - self.clock_pcts.len())?
|
||||
};
|
||||
// the clock ladder rides the power point the power ladder chose (the before point when nothing won)
|
||||
let power_pct = if self.power.is_empty() { self.before.power_pct } else { choose(&rows[..self.power.len()], self.tolerance_pct).map(|r| r.point.power_pct).unwrap_or(self.before.power_pct) };
|
||||
let clock = self.limits.clamp_clock(self.limits.clock_max_mhz * pct / 100);
|
||||
// a step whose clamp lands on the previous step's clock is dropped (the floor was reached)
|
||||
if rows.last().map(|r| r.point.clock_mhz == clock).unwrap_or(false) {
|
||||
return None;
|
||||
|
|
@ -921,6 +993,38 @@ pub fn result_line(kind: PlanKind, mhs: f64, watts: f64, eff: f64) -> String {
|
|||
}
|
||||
}
|
||||
|
||||
/// The core-clock knob's result on a card (7 October 2026; the UI lane's field shape): the chosen lock against the cap
|
||||
/// point's unlocked row, and the stop reason in words.
|
||||
#[derive(Clone, Debug, Default, PartialEq)]
|
||||
pub struct LockResult {
|
||||
/// the chosen core clock cap (0 = unlocked)
|
||||
pub lock_mhz: u32,
|
||||
pub lock_mhs: f64,
|
||||
pub lock_w: f64,
|
||||
pub lock_mhw: f64,
|
||||
/// the cap point's row (clock 0): the rate and draw the lock is read against
|
||||
pub unlocked_mhs: f64,
|
||||
pub unlocked_w: f64,
|
||||
/// "rate fell 5.1 percent at 1,200 MHz", "fingerprint mismatch at 1,400 MHz, clocks reset", "the floor at 618 MHz",
|
||||
/// "no lever" (a card without a clock cap), "" while nothing ran
|
||||
pub lock_note: String,
|
||||
}
|
||||
|
||||
/// The knob's result from a finished plan's rows and its chosen row.
|
||||
pub fn lock_result(plan: &Plan, rows: &[Row], chosen: &Row) -> LockResult {
|
||||
let cap = plan.cap_row(rows);
|
||||
let (unlocked_mhs, unlocked_w) = cap.as_ref().map(|c| (c.mhs, c.watts)).unwrap_or((0.0, 0.0));
|
||||
let ran_clocks = rows.iter().any(|r| r.point.clock_mhz > 0);
|
||||
let note = if plan.limits.clock_max_mhz == 0 {
|
||||
"no lever".to_string()
|
||||
} else if !ran_clocks {
|
||||
String::new()
|
||||
} else {
|
||||
plan.clock_stop_reason(rows).unwrap_or_else(|| format!("stopped at {} MHz", rows.last().map(|r| r.point.clock_mhz).unwrap_or(0)))
|
||||
};
|
||||
LockResult { lock_mhz: chosen.point.clock_mhz, lock_mhs: chosen.mhs, lock_w: chosen.watts, lock_mhw: chosen.eff, unlocked_mhs, unlocked_w, lock_note: note }
|
||||
}
|
||||
|
||||
/// Why a card cannot be tuned beyond measuring, or None when both knobs are available.
|
||||
pub fn control_reason(vendor: &str, limits: &Limits, device: &str, power_control: bool, amd_helper: bool) -> Option<String> {
|
||||
match vendor {
|
||||
|
|
@ -952,7 +1056,9 @@ 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 + 6, "five power steps (60% and 50% clamp to 400 W; one kept) and six clock steps (90% down to 45%)");
|
||||
// five power steps (60% and 50% clamp to 400 W; one kept), six percent rungs (90% down to 45% = 1,390) and the
|
||||
// fine ladder 1,300 down to the 20% floor (618): 1,300, 1,200, ..., 700 = 7 steps
|
||||
assert_eq!(plan.len(), 5 + 6 + 7);
|
||||
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
|
||||
|
|
@ -974,24 +1080,21 @@ mod tests {
|
|||
let s = plan.next(&rows).unwrap();
|
||||
assert_eq!(s.point.clock_mhz, 2472);
|
||||
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");
|
||||
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!(s.point.clock_mhz, 2163, "70%");
|
||||
rows.push(row_at(s.point, 200.0, 118.0));
|
||||
// the stop rule (7 October 2026): 118 is 4.8% under the cap point's 124, past the 1% tolerance, so the search
|
||||
// ends here (the 6 October ladder went on to 1,854, 1,545 and 1,390)
|
||||
assert_eq!(plan.next(&rows), None);
|
||||
assert_eq!(plan.clock_stop_reason(&rows).as_deref(), Some("rate fell 4.8 percent at 2163 MHz"));
|
||||
// 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();
|
||||
assert_eq!(best.point, Point { clock_mhz: 2472, power_pct: 100, mem_mhz: 0 });
|
||||
// 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(), 6);
|
||||
// clocks only: six percent rungs (1,800 .. 900) then the fine ladder 800 .. 400 (the 20% floor) = 5 more
|
||||
assert_eq!(Plan::full(&Limits { clock_max_mhz: 2000, ..Default::default() }, Point::default(), 1.0).len(), 6 + 5);
|
||||
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());
|
||||
}
|
||||
|
|
@ -1017,11 +1120,12 @@ mod tests {
|
|||
#[test]
|
||||
fn limits_never_exceed_the_vendor_or_undercut_the_floor() {
|
||||
let l = l5090();
|
||||
assert_eq!(l.clock_floor(), 1390);
|
||||
assert_eq!(l.clamp_clock(1000), 1390);
|
||||
assert_eq!(l.clock_floor(), 618, "20% of 3,090 (7 October 2026; the 45% floor of 6 October sat on the 5090's knee)");
|
||||
assert_eq!(l.clamp_clock(1000), 1000);
|
||||
assert_eq!(l.clamp_clock(500), 618);
|
||||
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 45% 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 20% 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);
|
||||
|
|
@ -1321,4 +1425,138 @@ mod tests {
|
|||
assert!(control_reason("amd", &Limits::default(), "1", false, true).is_none());
|
||||
}
|
||||
}
|
||||
|
||||
/// The core-clock knob (7 October 2026, the PC 1 efficiency passes): a flat ladder walks below the old 45 percent
|
||||
/// floor in 100 MHz steps to the 20 percent floor, and the result names the floor.
|
||||
#[test]
|
||||
fn the_clock_ladder_continues_below_45_percent_in_100_mhz_steps_to_the_floor() {
|
||||
let plan = Plan::full(&l5090(), Point { clock_mhz: 0, power_pct: 100, mem_mhz: 0 }, 1.0);
|
||||
let mut rows = Vec::new();
|
||||
for _ in 0..5 {
|
||||
let s = plan.next(&rows).unwrap();
|
||||
rows.push(row_at(s.point, 300.0, 136.8));
|
||||
}
|
||||
let mut clocks = Vec::new();
|
||||
while let Some(s) = plan.next(&rows) {
|
||||
assert_eq!(s.kind, Kind::Clock);
|
||||
clocks.push(s.point.clock_mhz);
|
||||
// the rate holds (memory-bound): the draw falls with the clock
|
||||
rows.push(row_at(s.point, 300.0 - clocks.len() as f64 * 10.0, 136.0));
|
||||
}
|
||||
assert_eq!(clocks, vec![2781, 2472, 2163, 1854, 1545, 1390, 1300, 1200, 1100, 1000, 900, 800, 700]);
|
||||
assert_eq!(plan.clock_stop_reason(&rows).as_deref(), Some("the floor at 700 MHz"));
|
||||
let chosen = choose(&rows, 1.0).unwrap();
|
||||
assert_eq!(chosen.point.clock_mhz, 700, "flat rate: the lowest draw wins");
|
||||
let r = lock_result(&plan, &rows, &chosen);
|
||||
assert_eq!((r.lock_mhz, r.lock_w, r.unlocked_mhs, r.unlocked_w), (700, 170.0, 136.8, 300.0));
|
||||
assert_eq!(r.lock_note, "the floor at 700 MHz");
|
||||
}
|
||||
|
||||
/// The stop rule on PC 1's RTX 5090 rows of 7 October 2026 (class v3, the card alone): the first clock row more
|
||||
/// than the tolerance under the cap point's rate ends the search and the best MH per watt among the rows within
|
||||
/// tolerance is chosen. At the 1 percent tolerance the 5090's rate (136.6 at 2,781) is 1.24 percent down at
|
||||
/// 1,545 MHz, so the search ends there and 1,854 MHz (135.6 MH/s at 239.6 W) is the point; at 1.5 percent it runs
|
||||
/// on to 1,200 (5.2 percent down) and 1,300 MHz is the point. The tolerance is the manifest's.
|
||||
#[test]
|
||||
fn the_clock_search_stops_at_the_knee_and_names_it() {
|
||||
let measured: Vec<(u32, f64, f64)> = vec![(2781, 317.9, 136.6), (2472, 276.0, 136.4), (2163, 252.4, 136.1), (1854, 239.6, 135.6), (1545, 232.1, 134.9), (1390, 229.0, 134.85), (1300, 223.3, 134.6), (1200, 215.7, 129.5)];
|
||||
let walk = |tolerance: f64| -> (Plan, Vec<Row>) {
|
||||
let plan = Plan::full(&Limits { clock_max_mhz: 3090, ..Default::default() }, Point { clock_mhz: 0, power_pct: 100, mem_mhz: 0 }, tolerance);
|
||||
let mut rows = vec![];
|
||||
for (mhz, w, mhs) in &measured {
|
||||
let Some(s) = plan.next(&rows) else { break };
|
||||
assert_eq!(s.point.clock_mhz, *mhz);
|
||||
rows.push(row_at(s.point, *w, *mhs));
|
||||
}
|
||||
(plan, rows)
|
||||
};
|
||||
let (plan, rows) = walk(1.0);
|
||||
assert_eq!(rows.last().unwrap().point.clock_mhz, 1545, "the search ends on the first row over 1 percent under the cap row");
|
||||
assert_eq!(plan.next(&rows), None);
|
||||
let reason = plan.clock_stop_reason(&rows).unwrap();
|
||||
assert!(reason.starts_with("rate fell 1.2 percent at 1545 MHz"), "{reason}");
|
||||
let chosen = choose(&rows, 1.0).unwrap();
|
||||
assert_eq!(chosen.point.clock_mhz, 1854, "the best MH per watt within 1 percent of the fastest row");
|
||||
let r = lock_result(&plan, &rows, &chosen);
|
||||
assert_eq!((r.lock_mhz, r.unlocked_mhs), (1854, 136.6));
|
||||
assert!((r.lock_mhw - 135.6 / 239.6).abs() < 1e-6);
|
||||
let (plan, rows) = walk(1.5);
|
||||
assert_eq!(rows.last().unwrap().point.clock_mhz, 1200);
|
||||
assert_eq!(plan.next(&rows), None);
|
||||
assert!(plan.clock_stop_reason(&rows).unwrap().starts_with("rate fell 5.2 percent at 1200 MHz"));
|
||||
assert_eq!(choose(&rows, 1.5).unwrap().point.clock_mhz, 1300);
|
||||
}
|
||||
|
||||
/// The fingerprint rule: a clock row marked Faulted (a rejected or mismatched hash during the hold) ends the search
|
||||
/// at once; the choice is made among the usable rows and the note says why.
|
||||
#[test]
|
||||
fn a_faulted_clock_row_ends_the_search_and_the_note_says_so() {
|
||||
let plan = Plan::full(&Limits { clock_max_mhz: 3090, ..Default::default() }, Point { clock_mhz: 0, power_pct: 100, mem_mhz: 0 }, 1.0);
|
||||
let mut rows = vec![];
|
||||
for (mhz, w, mhs) in [(2781, 317.9, 136.6), (2472, 276.0, 136.4)] {
|
||||
let s = plan.next(&rows).unwrap();
|
||||
assert_eq!(s.point.clock_mhz, mhz);
|
||||
rows.push(row_at(s.point, w, mhs));
|
||||
}
|
||||
let s = plan.next(&rows).unwrap();
|
||||
assert_eq!(s.point.clock_mhz, 2163);
|
||||
let mut bad = row_at(s.point, 252.4, 136.1);
|
||||
bad.faults = 1;
|
||||
bad.mark = Some(Mark::Faulted);
|
||||
rows.push(bad);
|
||||
assert_eq!(plan.next(&rows), None, "the search ends on the faulted row");
|
||||
assert_eq!(plan.clock_stop_reason(&rows).as_deref(), Some("fingerprint mismatch at 2163 MHz, clocks reset"));
|
||||
let chosen = choose(&rows, 1.0).unwrap();
|
||||
assert_eq!(chosen.point.clock_mhz, 2472, "the faulted row never wins");
|
||||
assert_eq!(lock_result(&plan, &rows, &chosen).lock_note, "fingerprint mismatch at 2163 MHz, clocks reset");
|
||||
}
|
||||
|
||||
/// The same through the state machine with a fake helper (the known-failed case first: a mismatch mid-search must
|
||||
/// reset and abort): the run applies 2,781 and 2,472, a fault lands during 2,163's hold, the row comes out Faulted,
|
||||
/// the next step is none, and the run's final Apply is the chosen 2,472 point (the reset), then Finished.
|
||||
#[test]
|
||||
fn a_mismatch_mid_search_resets_to_the_chosen_point_and_finishes() {
|
||||
let plan = Plan::full(&Limits { clock_max_mhz: 3090, ..Default::default() }, Point { clock_mhz: 0, power_pct: 100, mem_mhz: 0 }, 1.0);
|
||||
let timing = Timing { settle: Duration::from_secs(1), hold: Duration::from_secs(2), apply: Duration::from_secs(3) };
|
||||
let t0 = Instant::now();
|
||||
let mut run = Run::new(0, "0", "card-0", plan, 300.0, false, timing, t0);
|
||||
let mut t = t0;
|
||||
let mut applied: Vec<Step> = Vec::new();
|
||||
let mut finished: Option<Row> = None;
|
||||
let watts_for = |mhz: u32| -> f64 { match mhz { 2781 => 317.9, 2472 => 276.0, _ => 252.4 } };
|
||||
for _ in 0..200 {
|
||||
t += Duration::from_millis(500);
|
||||
let acked = true;
|
||||
let limit = run.current.as_ref().map(|s| s.watts).unwrap_or(0.0);
|
||||
// the fake helper: every setting takes; during 2,163's hold the worker reports a mismatched hash
|
||||
if let Some(cur) = run.current.clone() {
|
||||
if matches!(run.phase, Phase::Holding { .. }) {
|
||||
run.sample_rate(136.4);
|
||||
run.sample_telemetry(watts_for(cur.point.clock_mhz), cur.point.clock_mhz as f64, 13801.0, 60.0);
|
||||
if cur.point.clock_mhz == 2163 {
|
||||
run.sample_fault();
|
||||
}
|
||||
}
|
||||
}
|
||||
for o in run.tick(t, Readback { limit_w: limit, acked }) {
|
||||
match o {
|
||||
Out::Apply(s) => applied.push(s),
|
||||
Out::Finished(r) => finished = Some(r),
|
||||
Out::Failed(e) => panic!("the run failed: {e}"),
|
||||
Out::Row(_) => {}
|
||||
}
|
||||
}
|
||||
if finished.is_some() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
let clocks: Vec<u32> = applied.iter().map(|s| s.point.clock_mhz).collect();
|
||||
assert_eq!(clocks, vec![2781, 2472, 2163, 2472], "2,781, 2,472, the faulted 2,163, then the reset to the chosen 2,472");
|
||||
assert_eq!(applied.last().unwrap().kind, Kind::Confirm);
|
||||
let f = finished.expect("finished");
|
||||
assert_eq!(f.point.clock_mhz, 2472);
|
||||
assert_eq!(run.rows.len(), 3);
|
||||
assert_eq!(run.rows[2].mark, Some(Mark::Faulted));
|
||||
assert_eq!(run.plan.clock_stop_reason(&run.rows).as_deref(), Some("fingerprint mismatch at 2163 MHz, clocks reset"));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2727,6 +2727,13 @@ impl Engine {
|
|||
cc.tune_steps = of;
|
||||
cc.tune_eta_s = eta;
|
||||
cc.tune_plan = plan.into();
|
||||
// the clock search's own step count while a clock step runs (the UI's "locking clocks: step 4 of 9")
|
||||
if let Some(r) = self.sweep.as_ref() {
|
||||
let power_steps = r.rows.iter().filter(|x| x.point.clock_mhz == 0 && x.mark.is_some()).count() as u32;
|
||||
let on_clock = r.current.as_ref().map(|s| s.kind == crate::ember::Kind::Clock).unwrap_or(false);
|
||||
cc.lock_step = if on_clock { step.saturating_sub(power_steps) } else { 0 };
|
||||
cc.lock_steps = if on_clock { of.saturating_sub(power_steps) } else { 0 };
|
||||
}
|
||||
}
|
||||
if self.shared.runtime.sweep_only {
|
||||
// the job playbook forwards this to the installed app's /api/tune-progress
|
||||
|
|
@ -2765,6 +2772,22 @@ impl Engine {
|
|||
c.tune_source = kind.name().into();
|
||||
c.tune_line = crate::ember::result_line(kind, row.mhs, row.watts, row.eff);
|
||||
c.tune_curve = run.rows.iter().map(|r| r.json()).collect();
|
||||
// the core-clock knob's result (7 October 2026): the chosen lock against the cap point, and why it stopped
|
||||
if kind == crate::ember::PlanKind::Baseline {
|
||||
c.lock_note = if c.vendor == "apple" || run.plan.limits.clock_max_mhz == 0 { "no lever".into() } else { c.sweep_note.clone() };
|
||||
} else {
|
||||
let lr = crate::ember::lock_result(&run.plan, &run.rows, &row);
|
||||
c.lock_mhz = lr.lock_mhz;
|
||||
c.lock_mhs = lr.lock_mhs;
|
||||
c.lock_w = lr.lock_w;
|
||||
c.lock_mhw = lr.lock_mhw;
|
||||
c.unlocked_mhs = lr.unlocked_mhs;
|
||||
c.unlocked_w = lr.unlocked_w;
|
||||
c.lock_at = unix as f64;
|
||||
c.lock_note = lr.lock_note;
|
||||
}
|
||||
c.lock_step = 0;
|
||||
c.lock_steps = 0;
|
||||
let control = c.tune_control;
|
||||
if kind == crate::ember::PlanKind::Baseline {
|
||||
c.sweep_note = if control { String::new() } else { c.sweep_note.clone() };
|
||||
|
|
|
|||
|
|
@ -135,6 +135,18 @@ pub struct CardState {
|
|||
// Ember 2: the memory clock the last tune chose and the measured curve (every row of the last plan)
|
||||
pub tune_mem_mhz: u32,
|
||||
pub tune_curve: Vec<serde_json::Value>,
|
||||
// the core-clock knob (7 October 2026, src/ember.rs lock_result; the UI lane's field shape): the chosen lock
|
||||
// against the cap point's unlocked row, the step while the clock search runs, the moment and the stop reason
|
||||
pub lock_mhz: u32, // the chosen core clock cap (0 = unlocked)
|
||||
pub lock_mhs: f64,
|
||||
pub lock_w: f64,
|
||||
pub lock_mhw: f64,
|
||||
pub unlocked_mhs: f64, // the cap point's row the lock is read against
|
||||
pub unlocked_w: f64,
|
||||
pub lock_step: u32, // the clock search's step while it runs (0 otherwise)
|
||||
pub lock_steps: u32,
|
||||
pub lock_at: f64, // unix s the lock point was taken (0 = never)
|
||||
pub lock_note: String, // "rate fell 5.1 percent at 1200 MHz", "fingerprint mismatch at 1400 MHz, clocks reset", "the floor at 700 MHz", "no lever"
|
||||
// the kernel variant race (docs/design/miner-tuning.md): what the worker's last race chose
|
||||
pub variant: String,
|
||||
pub race_mhs: f64,
|
||||
|
|
|
|||
|
|
@ -2497,6 +2497,10 @@ The second sub-version 3 commit is 017e70376489251e18564c0abce7e466e606c8b3 (pus
|
|||
|
||||
F8's 64-seed gate on 017e7037 (the attack-pass lane, box 2, last seed 16:00:20 UTC): 60 of 64 under 1.2x; the four over are the named tail and nothing else (p10 1.5036x, p8 1.3776x, p34 1.2505x, p4 1.2167x; hottest items 355 to 541 reads of 2^31, unattributed, inside the ratio's clean spread); p23, p19, p15, p18 and p56 under the line; the clean spread 0.9915x to 1.144x. Exhaustion on 017e7037: 0 in the attack-pass lane's 20,532 chain-shaped seeds (max attempt 29) plus this lane's 4,099 (max 17); the 10^6 count continues as a strengthening line. Cost line for the node: the chain's epoch draw now pays the 2^20 ratio pass on every candidate that reaches it (the accepted one, and the about 4 percent that fail there), 2.2 s per pass on one box-2 core, so about 2.3 s per epoch draw on that core and, by the attack-pass lane's reading, about 4 to 5 s per epoch per node on slower cores; the earlier rejections cost milliseconds. From the attack-pass lane sub-version 3 at 017e7037 reads green on both gates with the named tail; its close line went to the plan, main and the Counter ASIC lane. This row stays open on the tail (p4, p8, p10, p34) until it is attributed or ruled accepted.
|
||||
|
||||
Two in-house pass findings against sub-version 3 (adv-accept-3, 7 October 2026, evening), neither a chain consequence. (1) Documentation: program.json's `program_id_derivation` and spec 01 section 1.4.6 stated the plain form while generator 4 appends `'sub/' || sub_version_le16`, so a client written from the text derives another id (the devnet v4 pack: 8aa9f185d63f269e from the text against a785001687d8688a; Devnet 3's epoch 0: 30956569d8f3d8d7 against fce15bf61030be57). Fixed at 15008aca and 0f45c8be: one byte recipe (`generator::IdRecipe`) builds the id and the text, the emitter prints that text, the spec states the suffix, the class v5 form (generator 5, no suffix) and the rung form, `tests/derivation.rs` re-derives every pinned pack's id from its own text with the pack's fields only (the plain text as the known-failed case), 38 packs' program.json corrected one line each with every other file and every id and fingerprint byte-identical on re-export; the twelve readwidth mix packs of 5 October keep their text (a class name the parser no longer accepts). (2) Correctness, unreachable: the last-resort program after 256 failed attempts (`last_resort_v4`, the xor rewrite) fails the acceptance rule in 223 of 2,500 seeds (209 by part (a)) and is handed to the chain unchecked; at r = 0.67 to 0.68 the path is reached with probability about 4.6e-44 per epoch, so no chain reaches it and sub-version 3 is not reopened for it. Sub-version 3's last resort is therefore UNREACHABLE AND UNVERIFIED against the rule; class v5's generator (the v5 lane) carries the verified construction with the known-failed test on one of the 223 seeds, and its entry states it. From the same pass: the three lowest-ratio steering seeds of 4,975 at a 256-unit sample read 1.0002x worst live, so the 256-unit ratio is noise as a selector and the selector works only at the 2^20-unit read, which is what rule (c'') uses.
|
||||
|
||||
Owed (recorded, not run, by the founder's word): G2 (the CPU verifier on 1,024 hashes per card) on the amended stream; G3 (the Metal fuzz, edge, stats and determinism runs) on the amended stream; the hash-rate ladder re-measure on the M5 Max and the RTX 5090 (the amendment changes the base program's source draws, not the op mix or the load count, so the latency-bound rows of `docs/analysis/latency-shadow-2026-10-06.md` are expected to hold within their spread; unmeasured); AMD (the RX 9070 XT, PC 1); the 2019-class verifier core (O-1.14); F8's phase E (the 64-seed dynamic census) on the amended stream, which is the attack-pass lane's and the test of the per-op table. The row reads FIXED-AND-PASSED only after phase E passes against the amended class.
|
||||
|
||||
Owed (recorded, not run, by the founder's word): G2 (the CPU verifier on 1,024 hashes per card) on the amended stream; G3 (the Metal fuzz, edge, stats and determinism runs) on the amended stream; the hash-rate ladder re-measure on the M5 Max and the RTX 5090 (the amendment changes the base program's source draws, not the op mix or the load count, so the latency-bound rows of `docs/analysis/latency-shadow-2026-10-06.md` are expected to hold within their spread; unmeasured); AMD (the RX 9070 XT, PC 1); the 2019-class verifier core (O-1.14); F8's phase E (the 64-seed dynamic census) on the amended stream, which is the attack-pass lane's and the test of the per-op table. The row reads FIXED-AND-PASSED only after phase E passes against the amended class.
|
||||
|
||||
Status: Fixed in part, finding bounded, stated (7 October 2026, night, the Counter ASIC lane's words): class v4 sub-version 3 (igneum-pow 017e7037, the audit-freeze tag) is frozen with the dataflow rule, the shared-operand rule, the 0.98 ratio and the total draw; the in-house pass's F8 re-gate reads 60 of 64 seeds under 1.2x with the four-seed tail accepted by the coordinator as the window model's unattributed residue (no chip consequence); the pass then attributed the class by value (eight live hot sets at 1.54x to 2.24x in the lowest 30 of 29,032 accepted programs, each about 1 MB of items at 0.3 percent of reads, 1.002x to a chip); class v5 (1c420786, frozen 21:53 UK) carries the fix as rule (c'''), the per-site distinct-index floor at 0.995 on the state flag (its census refuses 2.435 percent of accepted programs; seven of seven live hot sets refused at 0.9821 to 0.9919; the eighth's ratio owed tonight), with a named residual (three mild shadow-block-written concentrations at 0.9992 to 0.9997, about 1.0004x, a value-level test in the next class); the record is `docs/plans/counter-asic-3-status.md` section 7c and the class v5 design's section 14. The eighth live hot set (seed 122960, id 4be7393ab6c84802, the deepest found: X_f +0.111 percent, 1.54x the window model, its hottest item at 475,616 reads from an all-ones source) reads minimum site 12 at 0.9824 at the acceptance's own 2^20 sample (live 0.9822), refused by class v5's (c''') floor at 0.995; so the floor refuses eight of eight live hot sets by X_f at or above f found in the tail of 88,051 accepted programs (minimum sites 0.9821 to 0.9919) against 0 hot sets in 20 random programs; what it misses stays the three mild shadow-block-written concentrations at 0.9992 to 0.9997 (Devnet 3's first program among them), about 1.0004x to a chip, the value-level test in the next class (22:41 BST; the logs under `docs/analysis/cryptanalysis/logs/adv-accept/` on branch adv-accept; the v5 design's section 14). The RTX 5080 grid's knee is not in tonight; X37 keeps the 5080's stock premium only.
|
||||
|
|
@ -2505,6 +2509,14 @@ Status: Fixed in part, finding bounded, stated (7 October 2026, night, the Count
|
|||
|
||||
The three genesis fields of `docs/analysis/mission/mission.md` section 2.8, built on the node fork branch `genesis-forward` (from release-0.3.19-node dc141409) and the repo branch `genesis-forward`; the design and the gates in `docs/design/genesis-forward.md`. Every switch is never on the devnet (its digest c562d70e... does not move); the testnet genesis sets all three (the testnet lane re-pins and re-digests).
|
||||
|
||||
### AP-F8-4. The program id's derivation text omitted generator 4's sub-version suffix (interoperability, documentation; no object change)
|
||||
|
||||
Found by the in-house pass adv-accept-3 (report-acceptance-rule-3.md at 0c150e3c, finding 2, 7 October 2026, evening): program.json's `program_id_derivation` and spec 01 section 1.4.6 stated `FNV-1a 64 over 'igneum-program/' || generator_le32 || seed_words || attempt_le32` while the code (generator.rs `program_id`) appends `'sub/' || sub_version_le16` under generator 4, so an implementation written from the text derives another id (the devnet v4 pack: 8aa9f185d63f269e from the text against the pack's and the chain's a785001687d8688a; Devnet 3's epoch 0: 30956569d8f3d8d7 against fce15bf61030be57); a second client written from the text would disagree with the chain on every program id. The code is the consensus; the text was wrong.
|
||||
|
||||
Fix (15008aca and 0f45c8be on ca3-v4-amend, landed on master through the gate on 7 October 2026 night): the id and its printed derivation come from one byte recipe (`generator::IdRecipe`, `program_id_recipe`, `program_id_class_recipe`, `Program::program_id_derivation`), so the two cannot drift; the emitter prints that text; spec 1.4.6 states the suffix, the class v5 form (generator 5, no suffix) and the class-rung form (`'igneum-program-rw/'` with the class fields); `igneum-pow/tests/derivation.rs` re-derives each of the 18 pinned packs' ids from its own derivation string with the pack's fields only (`every_pinned_pack_id_rederives_from_its_own_derivation_text`; the plain text as the known-failed case in `the_class_v4_derivation_states_the_suffix_and_the_plain_text_derives_another_id`), and the record maker `print_every_pack_derivation` (ignored) rebuilds every pack to set its line. 38 packs' program.json corrected by one line each; every kernel, header, vector and id byte-identical on re-export (the twelve 5 October readwidth mix packs keep their text, their class name no longer parses). igneum-pow against the frozen 017e7037 differs in emit.rs (the one print), generator.rs (the recipe refactor; the hashed bytes unchanged, every pinned id and the recheck pins hold) and the new test only; no acceptance or draw code moved. program.json is OUTSIDE the digested pack bytes: the miner's `kernel_sha256` covers kernel.cu, kernel_bound.cu, kernel.cl, kernel_bound.cl, program.h and memhard.h, and program.json carries that digest as a field; the chain pairs on the program id and the kernel text, never on program.json, so the correction rides any line. The spec read-back check (an implementation from the text reproduces the shipped constants and ids) is the site audit lane's tools/ci/spec-constants-check.mjs on spec-accept-23, written to the hash lane's file-and-line map of 017e7037; finding 3 (the spec does not describe the shipped acceptance rule) is that lane's text in the same landing.
|
||||
|
||||
Status: Fixed (7 October 2026, night): the id and its derivation text come from one byte recipe, every pinned pack's id re-derives from its own text in the suite (the igneum-pow suite on box 2 green at 0f45c8be: 64 + 2 + 7 + 4 + 19 + 2 + 7), the spec states the suffix; no object moved.
|
||||
|
||||
### GF1. A post-quantum signature scheme would need a hard fork, and every vote key is a public BLS12-381 point
|
||||
"When a quantum computer comes, every BLS vote key is forged and the chain has no way to change the scheme without a fork of the kind you say you never need."
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
Generated by `tools/ledger/export-public.mjs` from `docs/fud-ledger.md`; a gate check fails when the two drift. One row per item: the claim or criticism, its status, what was done, and the evidence. Internal identifiers, times of day and team-member names are left out on purpose; the full ledger is published with the repository.
|
||||
|
||||
191 items. By status: Conceded, stated 52; Fixed 30; Decided 22; Fixed on a branch, pending merge 14; Answered by design 9; Answered with evidence 6; Fixed, stated 4; Closed by rule 3; Open 3; Answered by design, with a correction to our own text 1; Answered with evidence, stated 1; Answered by design for finality, Conceded for the lottery 1; Conceded, implemented, stated 1; Rule implemented and measured; launch month simulated 1; Answered by design, with the concession stated 1; Conceded, stated in the litepaper and the design doc 1; Answered with evidence at 1 block/s 1; Conceded, stated in the simulation report 1; Answered by design, with the dependency conceded. Update 7… 1; Conceded, stated in the litepaper, with the dial explained 1; Closed by spec 1; Conceded by decision, stated in the design doc 1; Answered by design, with the founder's edge conceded 1; Answered by design, with a metrics caveat 1; Closed by removal, 3 October 2026 1; Conceded, stated in the litepaper 1; Conceded, stated in the design doc 1; Measured on the live node line, and the overlay does NOT… 1; Conceded, stated in the simulation 1; Conceded in part, labelled, stated 1; Fixed in the node 1; Answered with evidence for the largest body the rules allow 1; Spec fixed 1; Fixed in the proving code 1; Fixed in the spec 1; Rule fixed 1; Rule written 1; Fixed, logged 1; Answered with evidence for the test half 1; Fixed in the node and shipped, rule not yet activated on… 1; Simulation half run 1; Answered with evidence for all four 1; Written 1; Designed 1; Fixed and confirmed 1; Rolled out 1; Conceded by decision 1; Conceded, scheduled, stated 1; Conceded, contained by rule, stated 1; Fixed on a branch and verified locally 1; Answered with evidence and stated 1; Answered with evidence for PC 2 1; Answered by design and with evidence 1; Fixed, stated; restated 1; Fixed, stated; restated further 1; Fixed in part, finding bounded, stated 1; Fixed as a genesis lever, measurement owed 1.
|
||||
192 items. By status: Conceded, stated 52; Fixed 31; Decided 22; Fixed on a branch, pending merge 14; Answered by design 9; Answered with evidence 6; Fixed, stated 4; Closed by rule 3; Open 3; Answered by design, with a correction to our own text 1; Answered with evidence, stated 1; Answered by design for finality, Conceded for the lottery 1; Conceded, implemented, stated 1; Rule implemented and measured; launch month simulated 1; Answered by design, with the concession stated 1; Conceded, stated in the litepaper and the design doc 1; Answered with evidence at 1 block/s 1; Conceded, stated in the simulation report 1; Answered by design, with the dependency conceded. Update 7… 1; Conceded, stated in the litepaper, with the dial explained 1; Closed by spec 1; Conceded by decision, stated in the design doc 1; Answered by design, with the founder's edge conceded 1; Answered by design, with a metrics caveat 1; Closed by removal, 3 October 2026 1; Conceded, stated in the litepaper 1; Conceded, stated in the design doc 1; Measured on the live node line, and the overlay does NOT… 1; Conceded, stated in the simulation 1; Conceded in part, labelled, stated 1; Fixed in the node 1; Answered with evidence for the largest body the rules allow 1; Spec fixed 1; Fixed in the proving code 1; Fixed in the spec 1; Rule fixed 1; Rule written 1; Fixed, logged 1; Answered with evidence for the test half 1; Fixed in the node and shipped, rule not yet activated on… 1; Simulation half run 1; Answered with evidence for all four 1; Written 1; Designed 1; Fixed and confirmed 1; Rolled out 1; Conceded by decision 1; Conceded, scheduled, stated 1; Conceded, contained by rule, stated 1; Fixed on a branch and verified locally 1; Answered with evidence and stated 1; Answered with evidence for PC 2 1; Answered by design and with evidence 1; Fixed, stated; restated 1; Fixed, stated; restated further 1; Fixed in part, finding bounded, stated 1; Fixed as a genesis lever, measurement owed 1.
|
||||
|
||||
| Id | Claim or criticism | Status | What was done | Evidence |
|
||||
|---|---|---|---|---|
|
||||
|
|
@ -193,6 +193,7 @@ Generated by `tools/ledger/export-public.mjs` from `docs/fud-ledger.md`; a gate
|
|||
| N2 | Any peer could crash any pruned node with a sync request below its retention | Fixed | `SyncManager::antipast_hashes_between` (the IBD headers path, `RequestHeaders`) unwrapped the GHOSTDAG reads of the requested low block and of every chain block of the walk; a pruned node holds no GHOSTDAG data below… | unit test `a_sync_request_below_retention_is_an_error_not_a_panic` (a chain of six headers, the genesis's GHOSTDAG… |
|
||||
| P23 | An unwound transaction leaves the node's view until its sender resends it | Fixed on a branch, pending merge | a fork a commit (the P23 commit, on the merge of `ledger-fixes` and `ledger-fixes-2` onto the 0.3.11 fork tip a commit); `EvmPool::on_chain_removed` (igneum/exec/src/pool.rs) and `ExecService::requeue_unwound`… | [igneum/exec/src/pool.rs](../igneum/exec/src/pool.rs) |
|
||||
| AP-F8-1 | A load whose source was last written by `or`, `mul` or `mulhi` makes a cross-hash hot set | Fixed in part, finding bounded, stated | Class v4 sub-version 3 (igneum-pow a commit, the audit-freeze tag) is frozen with the dataflow rule, the shared-operand rule, the 0.98 ratio and the total draw; the in-house pass's F8 re-gate reads 60 of 64 seeds under… | [docs/analysis/ca3-v4-uniform.md](../docs/analysis/ca3-v4-uniform.md) |
|
||||
| AP-F8-4 | The program id's derivation text omitted generator 4's sub-version suffix (interoperability, documentation; no object change) | Fixed | The id and its printed derivation come from one byte recipe (`generator::IdRecipe`, `program_id_recipe`, `program_id_class_recipe`, `Program::program_id_derivation`), so the two cannot drift; the emitter prints that… | [igneum-pow/tests/derivation.rs](../igneum-pow/tests/derivation.rs) |
|
||||
| GF1 | A post-quantum signature scheme would need a hard fork, and every vote key is a public BLS12-381 point | Fixed | The byte costs nothing now and a fork later. | none named |
|
||||
| GF2 | A vote key cannot move: a miner who changes keys re-earns 30 days of weight, and so does the post-quantum migration | Fixed | The successor inherits the window, not a fresh one, so a key rotation costs no weight and the migration of GF1 is one item per key. | none named |
|
||||
| GF3 | A 256 MB on-chip cache makes the lottery hash 2 to 3x cheaper for the card that has it, and the cache size is a constant | Fixed as a genesis lever, measurement owed | Consumer LLC is 96 to 128 MB today and datacentre 256 MB (`chip-model-v3`, approximate), so the shortcut is a datacentre card's today and a consumer card's in a generation or two. | none named |
|
||||
|
|
|
|||
|
|
@ -178,7 +178,7 @@ Implemented (`igneum-pow/src/accept.rs`, `proto-metal/main.swift`; ledger M6 Fix
|
|||
|
||||
Attempts. Attempt 0 of a program seed `b` (the 32-byte epoch seed, or the UTF-8 of a seed string) is the candidate drawn from `seed_words_from_bytes(b)`. If it fails, attempt `k = 1, 2, ...` is drawn from `seed_words_from_bytes(b || k_le32)`; the first accepted candidate is the program of the epoch. Measured rejection rate under this generator: 5.14 percent over 100,000 seeds (census section 7) and the 20,000-seed confirmation of `docs/bench-log.md` (4 October 2026), so the probability that 32 consecutive candidates fail is below 2^-136, and an implementation MAY treat 32 consecutive failures as a consensus fault (`MAX_ATTEMPTS`).
|
||||
|
||||
Program id. `FNV-1a-64("igneum-program/" || generator_le32 || seed words as little-endian bytes || attempt_le32)` with `generator = 2` under class v2 and `generator = 3` under class v3, written into every pack. Two implementations that agree on the id agree on the generator version, the seed words and the attempt.
|
||||
Program id. `FNV-1a-64("igneum-program/" || generator_le32 || seed words as little-endian bytes || attempt_le32 || suffix)` with `generator = 2` under class v2 and `generator = 3` under class v3 (no suffix), and `generator = 4` under class v4 with the suffix `"sub/" || sub_version_le16` (the class v4 stream's sub-version, 3 since 7 October 2026: `PROGRAM_SUBVERSION_V4` in `generator.rs`, `IGNEUM_PROGRAM_SUBVERSION` in program.h, `"sub_version"` in program.json; without the suffix Devnet 3's epoch-0 id reads 30956569d8f3d8d7 where the chain and the packs say fce15bf61030be57); class v5 is `generator = 5` with no suffix. A program of a non-default class (a read-width, scratch, mixer, derivation, era, shadow or hot rung other than class v4's rung 0) uses the tag `"igneum-program-rw/"` and appends the class's fields after `attempt_le32` (`generator::program_id_class_recipe`). Every pack prints its own derivation as `program_id_derivation` in program.json, built from the same byte recipe the id is hashed from (`generator::IdRecipe`), and `igneum-pow/tests/derivation.rs` re-derives every pinned pack's id from that text. Two implementations that agree on the id agree on the generator version, the seed words, the attempt and, under class v4, the sub-version.
|
||||
|
||||
Why the closed form: the test is then a pure function of the program (no cache, no day), costs 1.3 to 3.4 ms on one core, and the census checked on 100,000 programs that its verdict agrees with the memory-hard dataset's on all but 39 threshold-edge cases (section 7.3). What the three parts catch: (a) the empty-list fallback of 1.4.3; (b) registers that saturate to all ones (2.4 percent of candidates); (c) zero-absorbing register sets, lane-constant load sites, output bias and value-level address repeats (2.1 percent). Not in the rule, and why: a contraction as the last write (80 percent of programs) and the `or` count are too common and (c) already catches the cases that matter; the load critical path is a hash-rate question, not a weakness.
|
||||
|
||||
|
|
|
|||
|
|
@ -1812,7 +1812,7 @@ pub fn program_json(p: &Program, day: &str, ds: &DatasetSource) -> String {
|
|||
s.push_str(&format!(" \"generator\": {},\n", p.generator));
|
||||
s.push_str(&format!(" \"attempt\": {},\n", p.attempt));
|
||||
s.push_str(&format!(" \"program_id\": {},\n", jhex64(p.program_id())));
|
||||
s.push_str(" \"program_id_derivation\": \"FNV-1a 64 over 'igneum-program/' || generator_le32 || seed_words as little-endian bytes || attempt_le32\",\n");
|
||||
s.push_str(&format!(" \"program_id_derivation\": {},\n", jstr(&p.program_id_derivation())));
|
||||
s.push_str(&format!(
|
||||
" \"dataset_mode\": {},\n",
|
||||
jstr(if memhard.is_some() { "memory-hard" } else { "closed-form" })
|
||||
|
|
|
|||
|
|
@ -1077,6 +1077,17 @@ impl Program {
|
|||
}
|
||||
}
|
||||
|
||||
/// The derivation of [`Program::program_id`] as text, from the same byte recipe (program.json's
|
||||
/// "program_id_derivation"; spec 01 section 1.4.6).
|
||||
pub fn program_id_derivation(&self) -> String {
|
||||
let v4_rung_0 = self.generator == GENERATOR_VERSION_V4 && LoadClass { era: None, ..self.class } == V4_CLASS;
|
||||
if self.class.is_v2() || self.generator == GENERATOR_VERSION_V3 || v4_rung_0 {
|
||||
program_id_recipe(self.generator, &self.seed, self.attempt).text()
|
||||
} else {
|
||||
program_id_class_recipe(self.generator, &self.seed, self.attempt, &self.class).text()
|
||||
}
|
||||
}
|
||||
|
||||
/// The program class of this program, from its generator version (3 = v3, 4 = v4, everything else v2).
|
||||
pub fn program_class(&self) -> ProgramClass {
|
||||
match self.generator {
|
||||
|
|
@ -1087,24 +1098,61 @@ impl Program {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn program_id(generator: u32, seed: &[u32; 8], attempt: u32) -> u64 {
|
||||
let mut b = Vec::with_capacity(PROGRAM_ID_TAG.len() + 4 + 32 + 4 + 6);
|
||||
b.extend_from_slice(PROGRAM_ID_TAG);
|
||||
b.extend_from_slice(&generator.to_le_bytes());
|
||||
for w in seed {
|
||||
b.extend_from_slice(&w.to_le_bytes());
|
||||
/// The byte recipe of a program id and the text that states it: the bytes FNV-1a 64 hashes and, part by part, the label
|
||||
/// of each part (`'literal'` or `field_le32`), so the derivation printed in program.json is built from the same list the id
|
||||
/// is hashed from and the two cannot drift (the documentation finding of 7 October 2026: program.json and spec 1.4.6 said
|
||||
/// the plain form while generator 4 appended `'sub/' || sub_version_le16`, so a client written from the text derived another
|
||||
/// id). `tests/derivation.rs` re-derives every pinned pack's id from its own derivation string.
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct IdRecipe {
|
||||
pub bytes: Vec<u8>,
|
||||
parts: Vec<String>,
|
||||
}
|
||||
|
||||
impl IdRecipe {
|
||||
fn lit(&mut self, s: &[u8]) {
|
||||
self.bytes.extend_from_slice(s);
|
||||
self.parts.push(format!("'{}'", String::from_utf8_lossy(s)));
|
||||
}
|
||||
b.extend_from_slice(&attempt.to_le_bytes());
|
||||
fn field(&mut self, bytes: &[u8], label: &str) {
|
||||
self.bytes.extend_from_slice(bytes);
|
||||
self.parts.push(label.to_string());
|
||||
}
|
||||
/// The derivation as program.json prints it: `FNV-1a 64 over <part> || <part> || ...`.
|
||||
pub fn text(&self) -> String {
|
||||
format!("FNV-1a 64 over {}", self.parts.join(" || "))
|
||||
}
|
||||
/// The id: FNV-1a 64 over the bytes.
|
||||
pub fn id(&self) -> u64 {
|
||||
fnv1a64(&self.bytes)
|
||||
}
|
||||
}
|
||||
|
||||
/// The recipe of [`program_id`].
|
||||
pub fn program_id_recipe(generator: u32, seed: &[u32; 8], attempt: u32) -> IdRecipe {
|
||||
let mut r = IdRecipe::default();
|
||||
r.lit(PROGRAM_ID_TAG);
|
||||
r.field(&generator.to_le_bytes(), "generator_le32");
|
||||
let mut sw = Vec::with_capacity(32);
|
||||
for w in seed {
|
||||
sw.extend_from_slice(&w.to_le_bytes());
|
||||
}
|
||||
r.field(&sw, "seed_words as little-endian bytes");
|
||||
r.field(&attempt.to_le_bytes(), "attempt_le32");
|
||||
if generator == GENERATOR_VERSION_V4 {
|
||||
// The class v4 sub-version (AP-F8-1 amendment, 7 October 2026): `"sub/" || sub_version as little-endian u16`
|
||||
// appended for generator 4 only, so a binary from before the load-source rule (sub-version 0, no suffix) and
|
||||
// one after it never share a program id for one seed; the node's id check then catches a split. v2 and v3
|
||||
// ids are byte-identical. The node reads the sub-version from [`PROGRAM_SUBVERSION_V4`]; packs carry it as
|
||||
// IGNEUM_PROGRAM_SUBVERSION and program.json "sub_version".
|
||||
b.extend_from_slice(b"sub/");
|
||||
b.extend_from_slice(&PROGRAM_SUBVERSION_V4.to_le_bytes());
|
||||
r.lit(b"sub/");
|
||||
r.field(&PROGRAM_SUBVERSION_V4.to_le_bytes(), "sub_version_le16");
|
||||
}
|
||||
fnv1a64(&b)
|
||||
r
|
||||
}
|
||||
|
||||
pub fn program_id(generator: u32, seed: &[u32; 8], attempt: u32) -> u64 {
|
||||
program_id_recipe(generator, seed, attempt).id()
|
||||
}
|
||||
|
||||
/// The sub-version of class v4's program stream, in every generator-4 program id and pack (AP-F8-3: 3 = the
|
||||
|
|
@ -1118,53 +1166,60 @@ pub const PROGRAM_ID_TAG_RW: &[u8] = b"igneum-program-rw/";
|
|||
|
||||
/// The program id of a non-default class: the tag, then the same fields as [`program_id`], then the three mix
|
||||
/// percentages and the slot count as bytes.
|
||||
pub fn program_id_class(generator: u32, seed: &[u32; 8], attempt: u32, class: &LoadClass) -> u64 {
|
||||
let mut b = Vec::with_capacity(PROGRAM_ID_TAG_RW.len() + 4 + 32 + 4 + 4);
|
||||
b.extend_from_slice(PROGRAM_ID_TAG_RW);
|
||||
b.extend_from_slice(&generator.to_le_bytes());
|
||||
/// The recipe of [`program_id_class`].
|
||||
pub fn program_id_class_recipe(generator: u32, seed: &[u32; 8], attempt: u32, class: &LoadClass) -> IdRecipe {
|
||||
let mut r = IdRecipe::default();
|
||||
r.lit(PROGRAM_ID_TAG_RW);
|
||||
r.field(&generator.to_le_bytes(), "generator_le32");
|
||||
let mut sw = Vec::with_capacity(32);
|
||||
for w in seed {
|
||||
b.extend_from_slice(&w.to_le_bytes());
|
||||
sw.extend_from_slice(&w.to_le_bytes());
|
||||
}
|
||||
b.extend_from_slice(&attempt.to_le_bytes());
|
||||
b.extend_from_slice(&class.mix);
|
||||
b.push(class.load_slots);
|
||||
r.field(&sw, "seed_words as little-endian bytes");
|
||||
r.field(&attempt.to_le_bytes(), "attempt_le32");
|
||||
r.field(&class.mix, "mix[3]");
|
||||
r.field(&[class.load_slots], "load_slots_u8");
|
||||
if let Some(k) = class.scratch {
|
||||
b.extend_from_slice(b"scratch/");
|
||||
b.push(k);
|
||||
b.push(class.scratch_kb);
|
||||
r.lit(b"scratch/");
|
||||
r.field(&[k], "scratch_k_u8");
|
||||
r.field(&[class.scratch_kb], "scratch_kb_u8");
|
||||
}
|
||||
if class.mixer_mult != 1 || class.growth {
|
||||
// Counter ASIC 2.0: the mixer multiplier and the growth rule are part of the construction, so a program of
|
||||
// the same seed under a different mixer carries a different id (under the v3 seam the id is
|
||||
// program_id(3, seed, attempt) and this branch is not taken)
|
||||
b.extend_from_slice(b"mixer/");
|
||||
b.push(class.mixer_mult);
|
||||
b.push(class.growth as u8);
|
||||
r.lit(b"mixer/");
|
||||
r.field(&[class.mixer_mult], "mixer_mult_u8");
|
||||
r.field(&[class.growth as u8], "growth_u8");
|
||||
}
|
||||
if class.derive_len != 0 {
|
||||
// Counter ASIC 3.0 item 2: the derivation program's length is part of the construction
|
||||
b.extend_from_slice(b"derive/");
|
||||
b.extend_from_slice(&class.derive_len.to_le_bytes());
|
||||
r.lit(b"derive/");
|
||||
r.field(&class.derive_len.to_le_bytes(), "derive_len_le16");
|
||||
}
|
||||
if let Some(e) = class.era {
|
||||
b.extend_from_slice(b"era/");
|
||||
b.extend_from_slice(&e.id_bytes());
|
||||
r.lit(b"era/");
|
||||
r.field(&e.id_bytes(), "era_id_bytes (allowed[3] || width_words_u8 || stride_mul_le32 || stride_rot_le32 || interleave[4])");
|
||||
}
|
||||
if let Some(sh) = class.shadow {
|
||||
// Counter ASIC 3.0 item 8: the shadow block's size and repeat count are part of the construction
|
||||
b.extend_from_slice(b"shadow/");
|
||||
b.extend_from_slice(&sh.instrs.to_le_bytes());
|
||||
b.extend_from_slice(&sh.reps.to_le_bytes());
|
||||
r.lit(b"shadow/");
|
||||
r.field(&sh.instrs.to_le_bytes(), "shadow_instrs_le16");
|
||||
r.field(&sh.reps.to_le_bytes(), "shadow_reps_le16");
|
||||
}
|
||||
if let Some(h) = class.hot {
|
||||
b.extend_from_slice(b"hot/");
|
||||
b.push(h.mb);
|
||||
b.push(h.k);
|
||||
r.lit(b"hot/");
|
||||
r.field(&[h.mb], "hot_mb_u8");
|
||||
r.field(&[h.k], "hot_k_u8");
|
||||
if h.added {
|
||||
b.extend_from_slice(b"added");
|
||||
r.lit(b"added");
|
||||
}
|
||||
}
|
||||
fnv1a64(&b)
|
||||
r
|
||||
}
|
||||
|
||||
pub fn program_id_class(generator: u32, seed: &[u32; 8], attempt: u32, class: &LoadClass) -> u64 {
|
||||
program_id_class_recipe(generator, seed, attempt, class).id()
|
||||
}
|
||||
|
||||
/// Weights of the ten non-load families under version 2, in draw order. Sum 75. The load family has no
|
||||
|
|
|
|||
142
igneum-pow/tests/derivation.rs
Normal file
142
igneum-pow/tests/derivation.rs
Normal file
|
|
@ -0,0 +1,142 @@
|
|||
//! Every pinned pack's program id re-derived from its own `program_id_derivation` text (the documentation finding of
|
||||
//! 7 October 2026, the in-house pass adv-accept-3: program.json and spec 1.4.6 stated the plain form while generator 4
|
||||
//! appends `'sub/' || sub_version_le16`, so a client written from the text derived 30956569d8f3d8d7 for Devnet 3's epoch
|
||||
//! 0 where the chain says fce15bf61030be57). The text is parsed part by part and the bytes come from the pack's own fields
|
||||
//! (generator, attempt, seed_words, sub_version, load_class), never from the code's id function, so the test fails the
|
||||
//! moment the printed derivation and the hashed bytes disagree.
|
||||
|
||||
use igneum_pow::generator::LoadClass;
|
||||
use serde_json::Value;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
fn fnv1a64(b: &[u8]) -> u64 {
|
||||
let mut h: u64 = 0xcbf2_9ce4_8422_2325;
|
||||
for &x in b {
|
||||
h ^= x as u64;
|
||||
h = h.wrapping_mul(0x0000_0100_0000_01b3);
|
||||
}
|
||||
h
|
||||
}
|
||||
|
||||
fn hex64(v: &Value) -> u64 {
|
||||
u64::from_str_radix(v.as_str().unwrap().trim_start_matches("0x"), 16).unwrap()
|
||||
}
|
||||
|
||||
/// The bytes a derivation part names, from the pack's own fields.
|
||||
fn part_bytes(part: &str, j: &Value, class: Option<&LoadClass>) -> Vec<u8> {
|
||||
if let Some(lit) = part.strip_prefix('\'').and_then(|p| p.strip_suffix('\'')) {
|
||||
return lit.as_bytes().to_vec();
|
||||
}
|
||||
let c = || class.unwrap_or_else(|| panic!("part {part} needs the pack's load_class"));
|
||||
match part {
|
||||
"generator_le32" => (j["generator"].as_u64().unwrap() as u32).to_le_bytes().to_vec(),
|
||||
"attempt_le32" => (j["attempt"].as_u64().unwrap() as u32).to_le_bytes().to_vec(),
|
||||
"seed_words as little-endian bytes" => j["seed_words"].as_array().unwrap().iter().flat_map(|w| (hex64(w) as u32).to_le_bytes()).collect(),
|
||||
"sub_version_le16" => (j["sub_version"].as_u64().expect("a generator-4 pack carries sub_version") as u16).to_le_bytes().to_vec(),
|
||||
"mix[3]" => c().mix.to_vec(),
|
||||
"load_slots_u8" => vec![c().load_slots],
|
||||
"scratch_k_u8" => vec![c().scratch.unwrap()],
|
||||
"scratch_kb_u8" => vec![c().scratch_kb],
|
||||
"mixer_mult_u8" => vec![c().mixer_mult],
|
||||
"growth_u8" => vec![c().growth as u8],
|
||||
"derive_len_le16" => c().derive_len.to_le_bytes().to_vec(),
|
||||
"shadow_instrs_le16" => c().shadow.unwrap().instrs.to_le_bytes().to_vec(),
|
||||
"shadow_reps_le16" => c().shadow.unwrap().reps.to_le_bytes().to_vec(),
|
||||
"hot_mb_u8" => vec![c().hot.unwrap().mb],
|
||||
"hot_k_u8" => vec![c().hot.unwrap().k],
|
||||
other => panic!("derivation part {other:?} is not one this test knows; teach it the field before pinning such a pack"),
|
||||
}
|
||||
}
|
||||
|
||||
fn rederive(dir: &Path) -> (u64, u64, String) {
|
||||
let j: Value = serde_json::from_str(&std::fs::read_to_string(dir.join("program.json")).unwrap()).unwrap();
|
||||
let text = j["program_id_derivation"].as_str().unwrap().to_string();
|
||||
let parts = text.strip_prefix("FNV-1a 64 over ").unwrap_or_else(|| panic!("{}: the derivation does not start with 'FNV-1a 64 over '", dir.display()));
|
||||
let class = j["load_class"].as_str().and_then(LoadClass::parse);
|
||||
let mut b = Vec::new();
|
||||
for part in parts.split(" || ") {
|
||||
b.extend(part_bytes(part.trim(), &j, class.as_ref()));
|
||||
}
|
||||
(fnv1a64(&b), hex64(&j["program_id"]), text)
|
||||
}
|
||||
|
||||
fn pinned_packs() -> Vec<PathBuf> {
|
||||
let root = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../proto-cuda");
|
||||
let mut out = Vec::new();
|
||||
for sub in ["packs-ca3-v4", "packs-ca3-shadow"] {
|
||||
let mut v: Vec<_> = std::fs::read_dir(root.join(sub)).unwrap().map(|e| e.unwrap().path()).filter(|p| p.join("program.json").is_file()).collect();
|
||||
v.sort();
|
||||
out.extend(v);
|
||||
}
|
||||
out.push(root.join("packs/igneum-devnet-v4-epoch0"));
|
||||
assert!(out.len() >= 18, "the pinned packs are missing; this test never skips ({} found)", out.len());
|
||||
out
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn every_pinned_pack_id_rederives_from_its_own_derivation_text() {
|
||||
let mut seen_sub = false;
|
||||
for dir in pinned_packs() {
|
||||
let (derived, pinned, text) = rederive(&dir);
|
||||
assert_eq!(derived, pinned, "{}: the id re-derived from \"{text}\" is {derived:016x}, the pack says {pinned:016x}", dir.display());
|
||||
if text.contains("'sub/'") {
|
||||
seen_sub = true;
|
||||
}
|
||||
}
|
||||
assert!(seen_sub, "at least one generator-4 pack states the 'sub/' || sub_version_le16 suffix");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_class_v4_derivation_states_the_suffix_and_the_plain_text_derives_another_id() {
|
||||
let root = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../proto-cuda/packs-ca3-v4/v4-devnet-epoch0");
|
||||
let j: Value = serde_json::from_str(&std::fs::read_to_string(root.join("program.json")).unwrap()).unwrap();
|
||||
let text = j["program_id_derivation"].as_str().unwrap();
|
||||
assert!(text.ends_with("attempt_le32 || 'sub/' || sub_version_le16"), "{text}");
|
||||
// the known-failed case: the text as printed before 7 October 2026 (no suffix) derives a different id
|
||||
let plain = "FNV-1a 64 over 'igneum-program/' || generator_le32 || seed_words as little-endian bytes || attempt_le32";
|
||||
let mut b = Vec::new();
|
||||
for part in plain.strip_prefix("FNV-1a 64 over ").unwrap().split(" || ") {
|
||||
b.extend(part_bytes(part, &j, None));
|
||||
}
|
||||
assert_ne!(fnv1a64(&b), hex64(&j["program_id"]), "the plain text must not derive the pinned id (it did before the fix)");
|
||||
assert_eq!(hex64(&j["program_id"]), 0xa785_0016_87d8_688a);
|
||||
}
|
||||
|
||||
/// The record maker (ignored in the suite): prints `DERIVATION <dir> <id> <text>` for every pack directory under proto-cuda,
|
||||
/// the program rebuilt as the pack tests rebuild it (generator 3 with era bytes under class v2's era layout for packs-ca2-era,
|
||||
/// generator 3 through the v3 seam otherwise, generator 2 with its load class, plain generator 2), so program.json's line can
|
||||
/// be set from the code's own recipe after a derivation change. `cargo test --release --test derivation -- --ignored --nocapture`.
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn print_every_pack_derivation() {
|
||||
use igneum_pow::generator::{generate_era, generate_from_seed_bytes, generate_from_seed_bytes_class, generate_from_seed_bytes_program_class, ProgramClass, GENERATOR_VERSION_V3};
|
||||
let root = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../proto-cuda");
|
||||
let mut dirs: Vec<PathBuf> = std::fs::read_dir(&root).unwrap().map(|e| e.unwrap().path()).filter(|p| p.file_name().unwrap().to_str().unwrap().starts_with("packs")).flat_map(|g| std::fs::read_dir(g).unwrap().map(|e| e.unwrap().path())).filter(|p| p.join("program.json").is_file()).collect();
|
||||
dirs.sort();
|
||||
for dir in dirs {
|
||||
let j: Value = serde_json::from_str(&std::fs::read_to_string(dir.join("program.json")).unwrap()).unwrap();
|
||||
let seed = j["seed"].as_str().unwrap();
|
||||
let seed_bytes = igneum_pow::bind::unhex(j["seed_bytes"].as_str().unwrap()).unwrap();
|
||||
let gen = j["generator"].as_u64().unwrap() as u32;
|
||||
let era = j.get("era_seed_bytes").and_then(|v| v.as_str()).map(|h| igneum_pow::bind::unhex(h).unwrap());
|
||||
let lc = j.get("load_class").and_then(|c| c.as_str());
|
||||
let program = if gen == GENERATOR_VERSION_V3 && dir.parent().unwrap().file_name().unwrap() == "packs-ca2-era" {
|
||||
let allowed: Vec<u8> = j["era"]["allowed_widths"].as_array().unwrap().iter().map(|v| v.as_u64().unwrap() as u8).collect();
|
||||
generate_era(seed, &seed_bytes, LoadClass::V2, era.as_deref().unwrap(), &allowed)
|
||||
} else if gen == GENERATOR_VERSION_V3 {
|
||||
generate_from_seed_bytes_program_class(seed, &seed_bytes, ProgramClass::V3, era.as_deref())
|
||||
} else if gen == 4 {
|
||||
generate_from_seed_bytes_program_class(seed, &seed_bytes, ProgramClass::V4, era.as_deref())
|
||||
} else if let Some(c) = lc {
|
||||
let Some(class) = LoadClass::parse(c) else {
|
||||
println!("SKIP {} load_class {c:?} is a name the parser no longer accepts (an old experiment's pack); its derivation line stands as written", dir.strip_prefix(&root).unwrap().display());
|
||||
continue;
|
||||
};
|
||||
generate_from_seed_bytes_class(seed, &seed_bytes, class)
|
||||
} else {
|
||||
generate_from_seed_bytes(seed, &seed_bytes)
|
||||
};
|
||||
println!("DERIVATION {} {:016x} {}", dir.strip_prefix(&root).unwrap().display(), program.program_id(), program.program_id_derivation());
|
||||
assert_eq!(program.program_id(), hex64(&j["program_id"]), "{}: the rebuilt program's id is the pack's", dir.display());
|
||||
}
|
||||
}
|
||||
|
|
@ -9,7 +9,7 @@
|
|||
# public key compiled into src/manifest.rs, runs each job that targets it ONCE per id, and reports to the log intake
|
||||
# as run_id job-<id>-<machine id8> (read back with tools/jobs.mjs).
|
||||
#
|
||||
# packaging/ota/publish-jobs.sh add --kind run --target 1ccfe586 --script path.ps1 [--elevated] [--stop-miners] \
|
||||
# packaging/ota/publish-jobs.sh add --kind run --target 1ccfe586 --script path.ps1 [--stop-miners] \ (--elevated is REFUSED: the 7 Oct 2026 no-prompt rule)
|
||||
# [--cards-off key,key] [--timeout-minutes 60] [--shell powershell|bash] --title "..." [--expires-hours 48] [--deploy]
|
||||
# (--cards-off: the RUNNER switches these cards off through the app's own card path before the script and puts them
|
||||
# back exactly on any exit, done, failed, timeout, aborted or the app quitting; keys with or without the device
|
||||
|
|
@ -72,7 +72,15 @@ while [ $# -gt 0 ]; do
|
|||
--expires-hours) EXPIRES_H="$2"; shift 2 ;;
|
||||
--script) SCRIPT="$2"; shift 2 ;;
|
||||
--shell) SHELL_KIND="$2"; shift 2 ;;
|
||||
--elevated) ELEVATED=1; shift ;;
|
||||
--elevated)
|
||||
# STANDING RULE (the founder through main, 7 October 2026, 18:5x UTC): no PC job may raise a UAC prompt or need a click, ever. A run
|
||||
# job published --elevated launches the script through an administrator prompt on the PC (the 18:27Z job
|
||||
# run-ca3-pc1-v4-eff-5090-20261007 waited 2 minutes for a click and died with exit 251, the card switched off for nothing).
|
||||
# Anything needing rights goes through the installed app's Igneum Power Helper task (app/igneum-app/src/powertask.rs,
|
||||
# present when Power control is on); a job reads the task's presence first and, without it, reports
|
||||
# "no elevation path: Power control off on <machine>" with the measured-only rows it can still take. The flag is refused here.
|
||||
echo "publish-jobs: --elevated is refused (standing rule of 7 October 2026: no PC job raises a UAC prompt; use the Igneum Power Helper task from the script, see app/igneum-app/src/powertask.rs and tools/ca3-v4-amend/pc1-v4-efficiency.ps1)" >&2
|
||||
exit 3 ;;
|
||||
--stop-miners) STOP_MINERS=1; shift ;;
|
||||
--cards-off) CARDS_OFF="$2"; shift 2 ;;
|
||||
--timeout-minutes) TIMEOUT_MIN="$2"; shift 2 ;;
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
"generator": 2,
|
||||
"attempt": 0,
|
||||
"program_id": "0x3370a4466e5f322c",
|
||||
"program_id_derivation": "FNV-1a 64 over 'igneum-program/' || generator_le32 || seed_words as little-endian bytes || attempt_le32",
|
||||
"program_id_derivation": "FNV-1a 64 over 'igneum-program-rw/' || generator_le32 || seed_words as little-endian bytes || attempt_le32 || mix[3] || load_slots_u8 || 'hot/' || hot_mb_u8 || hot_k_u8",
|
||||
"dataset_mode": "memory-hard",
|
||||
"seed": "igneum-genesis",
|
||||
"seed_bytes": "69676e65756d2d67656e65736973",
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
"generator": 2,
|
||||
"attempt": 0,
|
||||
"program_id": "0xd40c36ab07d82002",
|
||||
"program_id_derivation": "FNV-1a 64 over 'igneum-program/' || generator_le32 || seed_words as little-endian bytes || attempt_le32",
|
||||
"program_id_derivation": "FNV-1a 64 over 'igneum-program-rw/' || generator_le32 || seed_words as little-endian bytes || attempt_le32 || mix[3] || load_slots_u8 || 'hot/' || hot_mb_u8 || hot_k_u8 || 'added'",
|
||||
"dataset_mode": "memory-hard",
|
||||
"seed": "igneum-genesis",
|
||||
"seed_bytes": "69676e65756d2d67656e65736973",
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
"generator": 2,
|
||||
"attempt": 0,
|
||||
"program_id": "0x322a62466d49ff66",
|
||||
"program_id_derivation": "FNV-1a 64 over 'igneum-program/' || generator_le32 || seed_words as little-endian bytes || attempt_le32",
|
||||
"program_id_derivation": "FNV-1a 64 over 'igneum-program-rw/' || generator_le32 || seed_words as little-endian bytes || attempt_le32 || mix[3] || load_slots_u8 || 'hot/' || hot_mb_u8 || hot_k_u8",
|
||||
"dataset_mode": "memory-hard",
|
||||
"seed": "igneum-genesis",
|
||||
"seed_bytes": "69676e65756d2d67656e65736973",
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
"generator": 2,
|
||||
"attempt": 0,
|
||||
"program_id": "0x322a64466d4a02cc",
|
||||
"program_id_derivation": "FNV-1a 64 over 'igneum-program/' || generator_le32 || seed_words as little-endian bytes || attempt_le32",
|
||||
"program_id_derivation": "FNV-1a 64 over 'igneum-program-rw/' || generator_le32 || seed_words as little-endian bytes || attempt_le32 || mix[3] || load_slots_u8 || 'hot/' || hot_mb_u8 || hot_k_u8",
|
||||
"dataset_mode": "memory-hard",
|
||||
"seed": "igneum-genesis",
|
||||
"seed_bytes": "69676e65756d2d67656e65736973",
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
"generator": 2,
|
||||
"attempt": 0,
|
||||
"program_id": "0x7f581216e95eb062",
|
||||
"program_id_derivation": "FNV-1a 64 over 'igneum-program/' || generator_le32 || seed_words as little-endian bytes || attempt_le32",
|
||||
"program_id_derivation": "FNV-1a 64 over 'igneum-program-rw/' || generator_le32 || seed_words as little-endian bytes || attempt_le32 || mix[3] || load_slots_u8 || 'hot/' || hot_mb_u8 || hot_k_u8 || 'added'",
|
||||
"dataset_mode": "memory-hard",
|
||||
"seed": "igneum-genesis",
|
||||
"seed_bytes": "69676e65756d2d67656e65736973",
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
"generator": 2,
|
||||
"attempt": 0,
|
||||
"program_id": "0x322a68466d4a0998",
|
||||
"program_id_derivation": "FNV-1a 64 over 'igneum-program/' || generator_le32 || seed_words as little-endian bytes || attempt_le32",
|
||||
"program_id_derivation": "FNV-1a 64 over 'igneum-program-rw/' || generator_le32 || seed_words as little-endian bytes || attempt_le32 || mix[3] || load_slots_u8 || 'hot/' || hot_mb_u8 || hot_k_u8",
|
||||
"dataset_mode": "memory-hard",
|
||||
"seed": "igneum-genesis",
|
||||
"seed_bytes": "69676e65756d2d67656e65736973",
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
"generator": 2,
|
||||
"attempt": 0,
|
||||
"program_id": "0x329724466da667ec",
|
||||
"program_id_derivation": "FNV-1a 64 over 'igneum-program/' || generator_le32 || seed_words as little-endian bytes || attempt_le32",
|
||||
"program_id_derivation": "FNV-1a 64 over 'igneum-program-rw/' || generator_le32 || seed_words as little-endian bytes || attempt_le32 || mix[3] || load_slots_u8 || 'hot/' || hot_mb_u8 || hot_k_u8",
|
||||
"dataset_mode": "memory-hard",
|
||||
"seed": "igneum-genesis",
|
||||
"seed_bytes": "69676e65756d2d67656e65736973",
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
"generator": 2,
|
||||
"attempt": 0,
|
||||
"program_id": "0x2ff8e52c952a62c2",
|
||||
"program_id_derivation": "FNV-1a 64 over 'igneum-program/' || generator_le32 || seed_words as little-endian bytes || attempt_le32",
|
||||
"program_id_derivation": "FNV-1a 64 over 'igneum-program-rw/' || generator_le32 || seed_words as little-endian bytes || attempt_le32 || mix[3] || load_slots_u8 || 'hot/' || hot_mb_u8 || hot_k_u8 || 'added'",
|
||||
"dataset_mode": "memory-hard",
|
||||
"seed": "igneum-genesis",
|
||||
"seed_bytes": "69676e65756d2d67656e65736973",
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
"generator": 2,
|
||||
"attempt": 0,
|
||||
"program_id": "0xa6892c64d9ddd7f7",
|
||||
"program_id_derivation": "FNV-1a 64 over 'igneum-program/' || generator_le32 || seed_words as little-endian bytes || attempt_le32",
|
||||
"program_id_derivation": "FNV-1a 64 over 'igneum-program-rw/' || generator_le32 || seed_words as little-endian bytes || attempt_le32 || mix[3] || load_slots_u8 || 'mixer/' || mixer_mult_u8 || growth_u8",
|
||||
"dataset_mode": "memory-hard",
|
||||
"seed": "igneum-epoch/edc4fa844da9dc98d37e965176f6558a31560e40502ab3ae5491b21aaaabfb07/day/69676e65756d2d6461792ffa50000000000000",
|
||||
"seed_bytes": "edc4fa844da9dc98d37e965176f6558a31560e40502ab3ae5491b21aaaabfb07",
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
"generator": 2,
|
||||
"attempt": 0,
|
||||
"program_id": "0x951b89584750bd75",
|
||||
"program_id_derivation": "FNV-1a 64 over 'igneum-program/' || generator_le32 || seed_words as little-endian bytes || attempt_le32",
|
||||
"program_id_derivation": "FNV-1a 64 over 'igneum-program-rw/' || generator_le32 || seed_words as little-endian bytes || attempt_le32 || mix[3] || load_slots_u8 || 'mixer/' || mixer_mult_u8 || growth_u8",
|
||||
"dataset_mode": "memory-hard",
|
||||
"seed": "igneum-genesis",
|
||||
"seed_bytes": "69676e65756d2d67656e65736973",
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
"generator": 2,
|
||||
"attempt": 0,
|
||||
"program_id": "0x7f4a5ca0a3637820",
|
||||
"program_id_derivation": "FNV-1a 64 over 'igneum-program/' || generator_le32 || seed_words as little-endian bytes || attempt_le32",
|
||||
"program_id_derivation": "FNV-1a 64 over 'igneum-program-rw/' || generator_le32 || seed_words as little-endian bytes || attempt_le32 || mix[3] || load_slots_u8 || 'mixer/' || mixer_mult_u8 || growth_u8 || 'derive/' || derive_len_le16",
|
||||
"dataset_mode": "memory-hard",
|
||||
"seed": "igneum-epoch/edc4fa844da9dc98d37e965176f6558a31560e40502ab3ae5491b21aaaabfb07/day/69676e65756d2d6461792ffa50000000000000",
|
||||
"seed_bytes": "edc4fa844da9dc98d37e965176f6558a31560e40502ab3ae5491b21aaaabfb07",
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
"generator": 2,
|
||||
"attempt": 0,
|
||||
"program_id": "0x72c1d8048aef9542",
|
||||
"program_id_derivation": "FNV-1a 64 over 'igneum-program/' || generator_le32 || seed_words as little-endian bytes || attempt_le32",
|
||||
"program_id_derivation": "FNV-1a 64 over 'igneum-program-rw/' || generator_le32 || seed_words as little-endian bytes || attempt_le32 || mix[3] || load_slots_u8 || 'mixer/' || mixer_mult_u8 || growth_u8 || 'derive/' || derive_len_le16",
|
||||
"dataset_mode": "memory-hard",
|
||||
"seed": "igneum-genesis",
|
||||
"seed_bytes": "69676e65756d2d67656e65736973",
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
"generator": 2,
|
||||
"attempt": 0,
|
||||
"program_id": "0xb42f8c8549967581",
|
||||
"program_id_derivation": "FNV-1a 64 over 'igneum-program/' || generator_le32 || seed_words as little-endian bytes || attempt_le32",
|
||||
"program_id_derivation": "FNV-1a 64 over 'igneum-program-rw/' || generator_le32 || seed_words as little-endian bytes || attempt_le32 || mix[3] || load_slots_u8 || 'mixer/' || mixer_mult_u8 || growth_u8 || 'shadow/' || shadow_instrs_le16 || shadow_reps_le16",
|
||||
"dataset_mode": "memory-hard",
|
||||
"seed": "igneum-genesis",
|
||||
"seed_bytes": "69676e65756d2d67656e65736973",
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
"generator": 2,
|
||||
"attempt": 0,
|
||||
"program_id": "0x9a5be3853b011f6c",
|
||||
"program_id_derivation": "FNV-1a 64 over 'igneum-program/' || generator_le32 || seed_words as little-endian bytes || attempt_le32",
|
||||
"program_id_derivation": "FNV-1a 64 over 'igneum-program-rw/' || generator_le32 || seed_words as little-endian bytes || attempt_le32 || mix[3] || load_slots_u8 || 'mixer/' || mixer_mult_u8 || growth_u8 || 'shadow/' || shadow_instrs_le16 || shadow_reps_le16",
|
||||
"dataset_mode": "memory-hard",
|
||||
"seed": "igneum-genesis",
|
||||
"seed_bytes": "69676e65756d2d67656e65736973",
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
"generator": 2,
|
||||
"attempt": 0,
|
||||
"program_id": "0x9a3d4d853ae722fb",
|
||||
"program_id_derivation": "FNV-1a 64 over 'igneum-program/' || generator_le32 || seed_words as little-endian bytes || attempt_le32",
|
||||
"program_id_derivation": "FNV-1a 64 over 'igneum-program-rw/' || generator_le32 || seed_words as little-endian bytes || attempt_le32 || mix[3] || load_slots_u8 || 'mixer/' || mixer_mult_u8 || growth_u8 || 'shadow/' || shadow_instrs_le16 || shadow_reps_le16",
|
||||
"dataset_mode": "memory-hard",
|
||||
"seed": "igneum-genesis",
|
||||
"seed_bytes": "69676e65756d2d67656e65736973",
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
"generator": 2,
|
||||
"attempt": 0,
|
||||
"program_id": "0x9a8b77853b298baa",
|
||||
"program_id_derivation": "FNV-1a 64 over 'igneum-program/' || generator_le32 || seed_words as little-endian bytes || attempt_le32",
|
||||
"program_id_derivation": "FNV-1a 64 over 'igneum-program-rw/' || generator_le32 || seed_words as little-endian bytes || attempt_le32 || mix[3] || load_slots_u8 || 'mixer/' || mixer_mult_u8 || growth_u8 || 'shadow/' || shadow_instrs_le16 || shadow_reps_le16",
|
||||
"dataset_mode": "memory-hard",
|
||||
"seed": "igneum-genesis",
|
||||
"seed_bytes": "69676e65756d2d67656e65736973",
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
"generator": 2,
|
||||
"attempt": 0,
|
||||
"program_id": "0x9abe71853b54db11",
|
||||
"program_id_derivation": "FNV-1a 64 over 'igneum-program/' || generator_le32 || seed_words as little-endian bytes || attempt_le32",
|
||||
"program_id_derivation": "FNV-1a 64 over 'igneum-program-rw/' || generator_le32 || seed_words as little-endian bytes || attempt_le32 || mix[3] || load_slots_u8 || 'mixer/' || mixer_mult_u8 || growth_u8 || 'shadow/' || shadow_instrs_le16 || shadow_reps_le16",
|
||||
"dataset_mode": "memory-hard",
|
||||
"seed": "igneum-genesis",
|
||||
"seed_bytes": "69676e65756d2d67656e65736973",
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
"generator": 2,
|
||||
"attempt": 0,
|
||||
"program_id": "0x9ae3d3853b749dd4",
|
||||
"program_id_derivation": "FNV-1a 64 over 'igneum-program/' || generator_le32 || seed_words as little-endian bytes || attempt_le32",
|
||||
"program_id_derivation": "FNV-1a 64 over 'igneum-program-rw/' || generator_le32 || seed_words as little-endian bytes || attempt_le32 || mix[3] || load_slots_u8 || 'mixer/' || mixer_mult_u8 || growth_u8 || 'shadow/' || shadow_instrs_le16 || shadow_reps_le16",
|
||||
"dataset_mode": "memory-hard",
|
||||
"seed": "igneum-genesis",
|
||||
"seed_bytes": "69676e65756d2d67656e65736973",
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
"generator": 2,
|
||||
"attempt": 0,
|
||||
"program_id": "0x9a477f853aefcc76",
|
||||
"program_id_derivation": "FNV-1a 64 over 'igneum-program/' || generator_le32 || seed_words as little-endian bytes || attempt_le32",
|
||||
"program_id_derivation": "FNV-1a 64 over 'igneum-program-rw/' || generator_le32 || seed_words as little-endian bytes || attempt_le32 || mix[3] || load_slots_u8 || 'mixer/' || mixer_mult_u8 || growth_u8 || 'shadow/' || shadow_instrs_le16 || shadow_reps_le16",
|
||||
"dataset_mode": "memory-hard",
|
||||
"seed": "igneum-genesis",
|
||||
"seed_bytes": "69676e65756d2d67656e65736973",
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
"generator": 2,
|
||||
"attempt": 0,
|
||||
"program_id": "0x9b6191853bdf72c1",
|
||||
"program_id_derivation": "FNV-1a 64 over 'igneum-program/' || generator_le32 || seed_words as little-endian bytes || attempt_le32",
|
||||
"program_id_derivation": "FNV-1a 64 over 'igneum-program-rw/' || generator_le32 || seed_words as little-endian bytes || attempt_le32 || mix[3] || load_slots_u8 || 'mixer/' || mixer_mult_u8 || growth_u8 || 'shadow/' || shadow_instrs_le16 || shadow_reps_le16",
|
||||
"dataset_mode": "memory-hard",
|
||||
"seed": "igneum-genesis",
|
||||
"seed_bytes": "69676e65756d2d67656e65736973",
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
"generator": 2,
|
||||
"attempt": 0,
|
||||
"program_id": "0x90cd909ac3e78d4a",
|
||||
"program_id_derivation": "FNV-1a 64 over 'igneum-program/' || generator_le32 || seed_words as little-endian bytes || attempt_le32",
|
||||
"program_id_derivation": "FNV-1a 64 over 'igneum-program-rw/' || generator_le32 || seed_words as little-endian bytes || attempt_le32 || mix[3] || load_slots_u8 || 'mixer/' || mixer_mult_u8 || growth_u8 || 'shadow/' || shadow_instrs_le16 || shadow_reps_le16",
|
||||
"dataset_mode": "memory-hard",
|
||||
"seed": "igneum-genesis",
|
||||
"seed_bytes": "69676e65756d2d67656e65736973",
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
"generator": 4,
|
||||
"attempt": 1,
|
||||
"program_id": "0xa785001687d8688a",
|
||||
"program_id_derivation": "FNV-1a 64 over 'igneum-program/' || generator_le32 || seed_words as little-endian bytes || attempt_le32",
|
||||
"program_id_derivation": "FNV-1a 64 over 'igneum-program/' || generator_le32 || seed_words as little-endian bytes || attempt_le32 || 'sub/' || sub_version_le16",
|
||||
"dataset_mode": "memory-hard",
|
||||
"seed": "igneum-epoch/edc4fa844da9dc98d37e965176f6558a31560e40502ab3ae5491b21aaaabfb07/day/69676e65756d2d6461792ffa50000000000000",
|
||||
"seed_bytes": "edc4fa844da9dc98d37e965176f6558a31560e40502ab3ae5491b21aaaabfb07",
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
"generator": 4,
|
||||
"attempt": 1,
|
||||
"program_id": "0xa785001687d8688a",
|
||||
"program_id_derivation": "FNV-1a 64 over 'igneum-program/' || generator_le32 || seed_words as little-endian bytes || attempt_le32",
|
||||
"program_id_derivation": "FNV-1a 64 over 'igneum-program/' || generator_le32 || seed_words as little-endian bytes || attempt_le32 || 'sub/' || sub_version_le16",
|
||||
"dataset_mode": "memory-hard",
|
||||
"seed": "igneum-epoch/edc4fa844da9dc98d37e965176f6558a31560e40502ab3ae5491b21aaaabfb07/day/69676e65756d2d6461792ffa50000000000000",
|
||||
"seed_bytes": "edc4fa844da9dc98d37e965176f6558a31560e40502ab3ae5491b21aaaabfb07",
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
"generator": 4,
|
||||
"attempt": 1,
|
||||
"program_id": "0xa785001687d8688a",
|
||||
"program_id_derivation": "FNV-1a 64 over 'igneum-program/' || generator_le32 || seed_words as little-endian bytes || attempt_le32",
|
||||
"program_id_derivation": "FNV-1a 64 over 'igneum-program/' || generator_le32 || seed_words as little-endian bytes || attempt_le32 || 'sub/' || sub_version_le16",
|
||||
"dataset_mode": "memory-hard",
|
||||
"seed": "igneum-epoch/edc4fa844da9dc98d37e965176f6558a31560e40502ab3ae5491b21aaaabfb07/day/69676e65756d2d6461792ffa50000000000000",
|
||||
"seed_bytes": "edc4fa844da9dc98d37e965176f6558a31560e40502ab3ae5491b21aaaabfb07",
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
"generator": 4,
|
||||
"attempt": 1,
|
||||
"program_id": "0xa785001687d8688a",
|
||||
"program_id_derivation": "FNV-1a 64 over 'igneum-program/' || generator_le32 || seed_words as little-endian bytes || attempt_le32",
|
||||
"program_id_derivation": "FNV-1a 64 over 'igneum-program/' || generator_le32 || seed_words as little-endian bytes || attempt_le32 || 'sub/' || sub_version_le16",
|
||||
"dataset_mode": "memory-hard",
|
||||
"seed": "igneum-epoch/edc4fa844da9dc98d37e965176f6558a31560e40502ab3ae5491b21aaaabfb07/day/69676e65756d2d6461792ffa50000000000000",
|
||||
"seed_bytes": "edc4fa844da9dc98d37e965176f6558a31560e40502ab3ae5491b21aaaabfb07",
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
"generator": 4,
|
||||
"attempt": 1,
|
||||
"program_id": "0xa785001687d8688a",
|
||||
"program_id_derivation": "FNV-1a 64 over 'igneum-program/' || generator_le32 || seed_words as little-endian bytes || attempt_le32",
|
||||
"program_id_derivation": "FNV-1a 64 over 'igneum-program/' || generator_le32 || seed_words as little-endian bytes || attempt_le32 || 'sub/' || sub_version_le16",
|
||||
"dataset_mode": "memory-hard",
|
||||
"seed": "igneum-epoch/edc4fa844da9dc98d37e965176f6558a31560e40502ab3ae5491b21aaaabfb07/day/69676e65756d2d6461792ffa50000000000000",
|
||||
"seed_bytes": "edc4fa844da9dc98d37e965176f6558a31560e40502ab3ae5491b21aaaabfb07",
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
"generator": 4,
|
||||
"attempt": 1,
|
||||
"program_id": "0xa785001687d8688a",
|
||||
"program_id_derivation": "FNV-1a 64 over 'igneum-program/' || generator_le32 || seed_words as little-endian bytes || attempt_le32",
|
||||
"program_id_derivation": "FNV-1a 64 over 'igneum-program/' || generator_le32 || seed_words as little-endian bytes || attempt_le32 || 'sub/' || sub_version_le16",
|
||||
"dataset_mode": "memory-hard",
|
||||
"seed": "igneum-epoch/edc4fa844da9dc98d37e965176f6558a31560e40502ab3ae5491b21aaaabfb07/day/69676e65756d2d6461792ffa50000000000000",
|
||||
"seed_bytes": "edc4fa844da9dc98d37e965176f6558a31560e40502ab3ae5491b21aaaabfb07",
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
"generator": 4,
|
||||
"attempt": 1,
|
||||
"program_id": "0xa785001687d8688a",
|
||||
"program_id_derivation": "FNV-1a 64 over 'igneum-program/' || generator_le32 || seed_words as little-endian bytes || attempt_le32",
|
||||
"program_id_derivation": "FNV-1a 64 over 'igneum-program/' || generator_le32 || seed_words as little-endian bytes || attempt_le32 || 'sub/' || sub_version_le16",
|
||||
"dataset_mode": "memory-hard",
|
||||
"seed": "igneum-epoch/edc4fa844da9dc98d37e965176f6558a31560e40502ab3ae5491b21aaaabfb07/day/69676e65756d2d6461792ffa50000000000000",
|
||||
"seed_bytes": "edc4fa844da9dc98d37e965176f6558a31560e40502ab3ae5491b21aaaabfb07",
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
"generator": 2,
|
||||
"attempt": 0,
|
||||
"program_id": "0xe0b70cd155c28f4b",
|
||||
"program_id_derivation": "FNV-1a 64 over 'igneum-program/' || generator_le32 || seed_words as little-endian bytes || attempt_le32",
|
||||
"program_id_derivation": "FNV-1a 64 over 'igneum-program-rw/' || generator_le32 || seed_words as little-endian bytes || attempt_le32 || mix[3] || load_slots_u8 || 'scratch/' || scratch_k_u8 || scratch_kb_u8",
|
||||
"dataset_mode": "memory-hard",
|
||||
"seed": "igneum-genesis",
|
||||
"seed_bytes": "69676e65756d2d67656e65736973",
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
"generator": 2,
|
||||
"attempt": 0,
|
||||
"program_id": "0xe0afe0d155bc25d9",
|
||||
"program_id_derivation": "FNV-1a 64 over 'igneum-program/' || generator_le32 || seed_words as little-endian bytes || attempt_le32",
|
||||
"program_id_derivation": "FNV-1a 64 over 'igneum-program-rw/' || generator_le32 || seed_words as little-endian bytes || attempt_le32 || mix[3] || load_slots_u8 || 'scratch/' || scratch_k_u8 || scratch_kb_u8",
|
||||
"dataset_mode": "memory-hard",
|
||||
"seed": "igneum-genesis",
|
||||
"seed_bytes": "69676e65756d2d67656e65736973",
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
"generator": 2,
|
||||
"attempt": 0,
|
||||
"program_id": "0xe0b080d155bd35b9",
|
||||
"program_id_derivation": "FNV-1a 64 over 'igneum-program/' || generator_le32 || seed_words as little-endian bytes || attempt_le32",
|
||||
"program_id_derivation": "FNV-1a 64 over 'igneum-program-rw/' || generator_le32 || seed_words as little-endian bytes || attempt_le32 || mix[3] || load_slots_u8 || 'scratch/' || scratch_k_u8 || scratch_kb_u8",
|
||||
"dataset_mode": "memory-hard",
|
||||
"seed": "igneum-genesis",
|
||||
"seed_bytes": "69676e65756d2d67656e65736973",
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
"generator": 2,
|
||||
"attempt": 0,
|
||||
"program_id": "0xe0c444d155cd78cf",
|
||||
"program_id_derivation": "FNV-1a 64 over 'igneum-program/' || generator_le32 || seed_words as little-endian bytes || attempt_le32",
|
||||
"program_id_derivation": "FNV-1a 64 over 'igneum-program-rw/' || generator_le32 || seed_words as little-endian bytes || attempt_le32 || mix[3] || load_slots_u8 || 'scratch/' || scratch_k_u8 || scratch_kb_u8",
|
||||
"dataset_mode": "memory-hard",
|
||||
"seed": "igneum-genesis",
|
||||
"seed_bytes": "69676e65756d2d67656e65736973",
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
"generator": 2,
|
||||
"attempt": 0,
|
||||
"program_id": "0xe0c4a4d155ce1bef",
|
||||
"program_id_derivation": "FNV-1a 64 over 'igneum-program/' || generator_le32 || seed_words as little-endian bytes || attempt_le32",
|
||||
"program_id_derivation": "FNV-1a 64 over 'igneum-program-rw/' || generator_le32 || seed_words as little-endian bytes || attempt_le32 || mix[3] || load_slots_u8 || 'scratch/' || scratch_k_u8 || scratch_kb_u8",
|
||||
"dataset_mode": "memory-hard",
|
||||
"seed": "igneum-genesis",
|
||||
"seed_bytes": "69676e65756d2d67656e65736973",
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
"generator": 2,
|
||||
"attempt": 0,
|
||||
"program_id": "0xe0d1dcd155d90573",
|
||||
"program_id_derivation": "FNV-1a 64 over 'igneum-program/' || generator_le32 || seed_words as little-endian bytes || attempt_le32",
|
||||
"program_id_derivation": "FNV-1a 64 over 'igneum-program-rw/' || generator_le32 || seed_words as little-endian bytes || attempt_le32 || mix[3] || load_slots_u8 || 'scratch/' || scratch_k_u8 || scratch_kb_u8",
|
||||
"dataset_mode": "memory-hard",
|
||||
"seed": "igneum-genesis",
|
||||
"seed_bytes": "69676e65756d2d67656e65736973",
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
"generator": 2,
|
||||
"attempt": 0,
|
||||
"program_id": "0xe0d27cd155da1553",
|
||||
"program_id_derivation": "FNV-1a 64 over 'igneum-program/' || generator_le32 || seed_words as little-endian bytes || attempt_le32",
|
||||
"program_id_derivation": "FNV-1a 64 over 'igneum-program-rw/' || generator_le32 || seed_words as little-endian bytes || attempt_le32 || mix[3] || load_slots_u8 || 'scratch/' || scratch_k_u8 || scratch_kb_u8",
|
||||
"dataset_mode": "memory-hard",
|
||||
"seed": "igneum-genesis",
|
||||
"seed_bytes": "69676e65756d2d67656e65736973",
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
"generator": 2,
|
||||
"attempt": 0,
|
||||
"program_id": "0x6a183165e23fd2bc",
|
||||
"program_id_derivation": "FNV-1a 64 over 'igneum-program/' || generator_le32 || seed_words as little-endian bytes || attempt_le32",
|
||||
"program_id_derivation": "FNV-1a 64 over 'igneum-program-rw/' || generator_le32 || seed_words as little-endian bytes || attempt_le32 || mix[3] || load_slots_u8",
|
||||
"dataset_mode": "memory-hard",
|
||||
"seed": "igneum-genesis",
|
||||
"seed_bytes": "69676e65756d2d67656e65736973",
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
"generator": 2,
|
||||
"attempt": 0,
|
||||
"program_id": "0x5c427d666b4ed4f4",
|
||||
"program_id_derivation": "FNV-1a 64 over 'igneum-program/' || generator_le32 || seed_words as little-endian bytes || attempt_le32",
|
||||
"program_id_derivation": "FNV-1a 64 over 'igneum-program-rw/' || generator_le32 || seed_words as little-endian bytes || attempt_le32 || mix[3] || load_slots_u8",
|
||||
"dataset_mode": "memory-hard",
|
||||
"seed": "igneum-genesis",
|
||||
"seed_bytes": "69676e65756d2d67656e65736973",
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
"generator": 2,
|
||||
"attempt": 0,
|
||||
"program_id": "0x5c4269666b4eb2f8",
|
||||
"program_id_derivation": "FNV-1a 64 over 'igneum-program/' || generator_le32 || seed_words as little-endian bytes || attempt_le32",
|
||||
"program_id_derivation": "FNV-1a 64 over 'igneum-program-rw/' || generator_le32 || seed_words as little-endian bytes || attempt_le32 || mix[3] || load_slots_u8",
|
||||
"dataset_mode": "memory-hard",
|
||||
"seed": "igneum-genesis",
|
||||
"seed_bytes": "69676e65756d2d67656e65736973",
|
||||
|
|
|
|||
149
tools/ca3-v4-amend/pc1-amd-family-20261007.ps1
Normal file
149
tools/ca3-v4-amend/pc1-amd-family-20261007.ps1
Normal file
|
|
@ -0,0 +1,149 @@
|
|||
# 7 October 2026 copy for the PC 1 queue: tools/ca3-pc1-amd/pc1-amd-family.ps1 (run e) unchanged except the kit id, since a machine
|
||||
# runs a fetch id once and the 0.3.21 host build wipes the jobs folder: fetch-ca3-pc1-amd-family-20261007 (the probe rebuilt on
|
||||
# build-1 from this tree's proto-opencl/family-probe.c).
|
||||
# Counter ASIC 3.0, PC 1 AMD job 2 (6 October 2026, run e: the 9070 XT ALONE): item 6's step cost of every reserve candidate family of spec
|
||||
# 1.13.2 on PC 1's RX 9070 XT (machine ae432dc7, gfx1201), docs/plans/counter-asic-3-reserve.md, status file section
|
||||
# 3 "Item 6" (the OWED AMD column). Run a (run-ca3-pc1-amd-family-20261006, 150 s, exit 0) measured ordinal 0, PC 1's
|
||||
# integrated gfx1036 (one CU: alu 40.59 G steps/s against the 9070 XT's expected 1,000 to 2,000): the device list parse
|
||||
# ran a second -match after the capturing one, which overwrote $Matches, so every device read idx 0 and an empty name,
|
||||
# and the probe took a bare ordinal. Run b: the probe picks the card BY NAME (`--device-name gfx1201`, the match on the
|
||||
# newest AMD platform by driver version, the kit worker's dedup rule; `RESULT device_choice name= index= platform= driver=
|
||||
# cus=`), prints the device's name, CUs and platform on every RESULT line, and refuses with `RESULT error` when no
|
||||
# gfx1201 is listed; the older-platform duplicate and the gfx1036 run after it by explicit ordinal, as their own labelled
|
||||
# columns. Run d (17:17Z, the card by name, every variant built) showed a dependent-chain ratio does NOT survive the
|
||||
# card's own miner: the alu chain read 226 then 195 G steps/s and the ratios swung from 5.9 to 11.8x to 0.17 to 1.3x
|
||||
# between runs (the loaded card's scheduler, not the ops), while the Mac and 5090 columns were taken with the card alone.
|
||||
# Run e: the 9070 XT is switched OFF by the RUNNER (`--cards-off amd:gfx1201` at publish, the app's own card path, put
|
||||
# back on ANY exit with its own enabled flag, identities and cap: rule of 6 October 2026, a script never posts to
|
||||
# /api/cards) for its three runs; this script only confirms by the process list that the card's opencl worker is gone;
|
||||
# the 5090 and the 4070 keep mining (NOT --stop-miners). The gfx1036 and the old-platform columns run
|
||||
# AFTER the restore, beside the miners, as they are not the card under test. mm8 stays exact=unverified: the gfx12 WMMA
|
||||
# iu8 16x16x16 fragment layout is in no source at hand, and a guessed CPU layout would turn a wrong guess into exact=no. The probe is the family kit's
|
||||
# family-probe-cl.exe (proto-opencl/family-probe.c of branch ca3-pc1-amd, cross-compiled with mingw, OpenCL.dll at run
|
||||
# time; the Mac ran the same source bit-exact on Apple OpenCL), three runs per device, each best of 3 with a fresh seed
|
||||
# per repetition, device event time, bit-exact against the CPU reference on two whole 32-lane groups. Every family is
|
||||
# tried through every form AMD's OpenCL C offers; a form that does not compile prints build=failed with the first line
|
||||
# of the build log and the run goes on (run a's finding: AMD's OpenCL C compiles no amd_perm, no sudot4, no WMMA
|
||||
# builtin; ds_bpermute does compile). Never quits, pauses, resumes or updates the installed app; never writes
|
||||
# settings.json. Lines: the probe's own `RESULT FAMILY name= ms= gsteps= ratio= exact= path= variant= ... device= cus=
|
||||
# platform=` and `RESULT FAMILYBEST ...`, each prefixed `RESULT run=<n> dev=<label>`; a `SUMMARY {json}` line at the end.
|
||||
# Read back with `node tools/jobs.mjs <job id>`.
|
||||
$ErrorActionPreference = 'Continue'
|
||||
$jobName = 'pc1-amd-family'
|
||||
$kitId = 'fetch-ca3-pc1-amd-family-20261007'
|
||||
$started = Get-Date
|
||||
function Stamp { (Get-Date).ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ssZ') }
|
||||
function Summary([string] $status, [hashtable] $extra) {
|
||||
$o = [ordered]@{ job = $jobName; status = $status; duration_s = [int]((Get-Date) - $started).TotalSeconds; finished_at = (Stamp) }
|
||||
foreach ($k in $extra.Keys) { $o[$k] = $extra[$k] }
|
||||
'SUMMARY ' + ($o | ConvertTo-Json -Compress -Depth 4)
|
||||
}
|
||||
"RESULT start $(Stamp) job=$jobName machine=$env:COMPUTERNAME app_version=$env:IGNEUM_APP_VERSION"
|
||||
$jobs = Split-Path $env:IGNEUM_JOB_DIR
|
||||
$kit = Join-Path $jobs $kitId
|
||||
if (-not (Test-Path $kit)) { "RESULT error kit missing at $kit (the fetch job $kitId runs first; republish it after any app update)"; Summary 'failed' @{ error = 'kit missing' }; exit 2 }
|
||||
$probe = Join-Path $kit 'bin\family-probe-cl.exe'
|
||||
$src = Join-Path $kit 'src\family-probe.c'
|
||||
if (-not (Test-Path $probe)) { "RESULT error probe missing at $probe"; Summary 'failed' @{ error = 'probe missing' }; exit 2 }
|
||||
"RESULT probe $probe sha256 $((Get-FileHash -Algorithm SHA256 $probe).Hash.ToLower()) bytes $((Get-Item $probe).Length)"
|
||||
if (Test-Path $src) { "RESULT source family-probe.c sha256 $((Get-FileHash -Algorithm SHA256 $src).Hash.ToLower()) bytes $((Get-Item $src).Length)" }
|
||||
|
||||
# what the card carries during the run (read only; nothing is switched)
|
||||
function Workers { @(Get-CimInstance Win32_Process -Filter "Name = 'igneum-worker-opencl.exe' OR Name = 'igneum-worker-cuda.exe'" -ErrorAction SilentlyContinue | ForEach-Object { "$($_.Name):$($_.ProcessId):[$($_.CommandLine -replace '\s+', ' ')]" }) }
|
||||
$w = @(Workers)
|
||||
$amdLoaded = ($w | Where-Object { $_ -match 'igneum-worker-opencl' }).Count -gt 0
|
||||
$cardState = if ($amdLoaded) { 'loaded' } else { 'quiet' }
|
||||
"RESULT workers_before $(Stamp) $($w -join ' ')"
|
||||
"RESULT context card_state=$(if ($amdLoaded) { 'LOADED (an igneum-worker-opencl process mines on an AMD card: absolutes are shared-card figures, ratios stand)' } else { 'quiet (no igneum-worker-opencl process)' })"
|
||||
|
||||
# ---- the installed app and the card's settings.json entries (READ ONLY), for the card-alone runs ----
|
||||
$inst = @("$env:LOCALAPPDATA\Programs\Igneum Miner", "$env:ProgramFiles\Igneum Miner") | Where-Object { Test-Path (Join-Path $_ 'igneum-app.exe') } | Select-Object -First 1
|
||||
$appDir = if ($env:IGNEUM_APP_DIR) { $env:IGNEUM_APP_DIR } else { Join-Path $env:LOCALAPPDATA 'igneum\app' }
|
||||
$urlFile = Join-Path $appDir 'app.url'
|
||||
$base = $null
|
||||
if (Test-Path $urlFile) { $base = (Get-Content -LiteralPath $urlFile -Raw).Trim().TrimEnd('/') }
|
||||
"RESULT app install=$inst app_dir=$appDir url_present=$([bool]$base)"
|
||||
# the 9070 XT's OpenCL device index as the APP numbers it (the installed worker's --list; the probe's own list below
|
||||
# numbers devices differently), for the process-list check of its worker
|
||||
$dev = $null
|
||||
if ($inst -and (Test-Path (Join-Path $inst 'igneum-worker-opencl.exe'))) {
|
||||
$wl = @(& (Join-Path $inst 'igneum-worker-opencl.exe') --list 2>&1 | ForEach-Object { "$_" })
|
||||
foreach ($l in $wl) { if ($l -match '^\s*\[(\d+)\].*gfx1201' -and $l -notmatch 'dup') { $dev = [int]$Matches[1]; break } }
|
||||
}
|
||||
"RESULT app_device $(if ($null -ne $dev) { $dev } else { 'unknown (no installed worker list: the quiet check falls back to any opencl worker)' })"
|
||||
# the card is switched off by the RUNNER (`--cards-off amd:gfx1201` at publish), never by this script (rule of 6 October
|
||||
# 2026, tools/ci/playbook-quit-check.sh rule 2); below only the read-only confirmation that its worker is gone
|
||||
function CardWorkers {
|
||||
$procs = @(Get-CimInstance Win32_Process -Filter "Name = 'igneum-worker-opencl.exe'" -ErrorAction SilentlyContinue)
|
||||
if ($null -ne $dev) { $procs = @($procs | Where-Object { $_.CommandLine -match "--device\s+$dev(\s|$)" }) }
|
||||
@($procs | ForEach-Object { $_.ProcessId })
|
||||
}
|
||||
|
||||
# the probe's own device list: the 9070 XT by name first (the probe picks the newest platform itself), then the
|
||||
# older-platform duplicate and the gfx1036 by explicit ordinal as their own columns
|
||||
$list = @(& $probe --list 2>&1 | ForEach-Object { "$_" })
|
||||
$list | ForEach-Object { "RESULT list $_" }
|
||||
$extra = @()
|
||||
$gfx1201Count = 0; $newest = ''
|
||||
foreach ($l in $list) {
|
||||
if ($l -match '^\[(\d+)\] (.*?) \| (.*?) \| driver (\S+)') {
|
||||
$idx = [int] $Matches[1]; $name = $Matches[2]; $plat = $Matches[3]; $drv = $Matches[4]
|
||||
if ($name -match 'gfx1201') { $gfx1201Count++; if ($drv -gt $newest) { $newest = $drv } }
|
||||
if ($name -match 'gfx1201|gfx1036|Radeon' -and $name -notmatch 'NVIDIA') { $extra += [pscustomobject]@{ idx = $idx; name = $name; platform = $plat; driver = $drv } }
|
||||
}
|
||||
}
|
||||
if ($gfx1201Count -eq 0) { "RESULT error no gfx1201 in the probe's list (the eGPU is off the bus: the 9070 XT column stays OWED); listed AMD devices: $(($extra | ForEach-Object { "[$($_.idx)] $($_.name) on $($_.platform) driver $($_.driver)" }) -join ' ; ')"; Summary 'failed' @{ error = 'no gfx1201' }; exit 2 }
|
||||
# the by-ordinal runs after the by-name one: the gfx1201 entries NOT on the newest driver (the older platform), then the rest
|
||||
$byOrdinal = @($extra | Where-Object { -not ($_.name -match 'gfx1201' -and $_.driver -eq $newest) })
|
||||
"RESULT devices by_name=gfx1201 (newest driver $newest, $gfx1201Count gfx1201 entries) then by_ordinal=[$(($byOrdinal | ForEach-Object { "[$($_.idx)] $($_.name) on $($_.platform) driver $($_.driver)" }) -join ' ; ')]"
|
||||
|
||||
$runs = 0; $exactRows = 0; $rowsTotal = 0; $built = 0; $failedBuilds = 0; $probeErrors = 0; $mainRows = 0
|
||||
function RunProbe([string] $label, [string[]] $probeArgs, [int] $r) {
|
||||
# never name a parameter after the automatic variable for unbound arguments: a splat of it is then empty (run b, 17:10Z)
|
||||
"RESULT run=$r dev=$label start $(Stamp) args=[$($probeArgs -join ' ')] card_state=$cardState"
|
||||
$t0 = Get-Date
|
||||
$lines = @(& $probe @probeArgs --reps 3 2>&1 | ForEach-Object { "$_" })
|
||||
$rc = $LASTEXITCODE
|
||||
foreach ($l in $lines) {
|
||||
if ($l -match '^RESULT ') { "RESULT run=$r dev=$label $($l.Substring(7))" } else { "RESULT run=$r dev=$label text $l" }
|
||||
if ($l -match '^RESULT FAMILY name=\S+ ms=') { $script:rowsTotal++; if ($l -match ' exact=yes ') { $script:exactRows++ }; if ($label -eq '9070xt') { $script:mainRows++ } }
|
||||
if ($l -match '^RESULT FAMILY .*build=failed') { $script:failedBuilds++ }
|
||||
if ($l -match '^RESULT FAMILYBEST name=\S+ ms=') { $script:built++ }
|
||||
if ($l -match '^RESULT error') { $script:probeErrors++ }
|
||||
}
|
||||
"RESULT run=$r dev=$label end $(Stamp) exit=$rc wall_s=$([int]((Get-Date) - $t0).TotalSeconds)"
|
||||
if ($rc -ne 0) { "RESULT run=$r dev=$label error=probe exit $rc" }
|
||||
$script:runs++
|
||||
}
|
||||
# ---- the 9070 XT alone: off in the app, confirmed quiet, three runs, restored whatever happens ----
|
||||
$cardAlone = $false
|
||||
try {
|
||||
$before = @(CardWorkers)
|
||||
"RESULT card_workers_before pids=[$($before -join ' ')]"
|
||||
if ($before.Count -eq 0) { "RESULT card already quiet: no opencl worker for the card (the runner's --cards-off took)"; $cardAlone = $true }
|
||||
else {
|
||||
$t = 0
|
||||
while ($t -lt 150) { Start-Sleep -Seconds 5; $t += 5; if ((@(CardWorkers)).Count -eq 0) { break } }
|
||||
$left = @(CardWorkers)
|
||||
if ($left.Count -eq 0) { "RESULT card $(Stamp) quiet after $t s: the card's igneum-worker-opencl process is gone (the card to itself)"; $cardAlone = $true }
|
||||
else { "RESULT card $(Stamp) UNCONFIRMED after $t s: opencl worker pids [$($left -join ' ')] still run (was the job published with --cards-off amd:gfx1201?); the 9070 XT rows below are LOADED-card figures (run d's class)" }
|
||||
Start-Sleep -Seconds 8
|
||||
}
|
||||
$cardState = if ($cardAlone) { 'alone' } else { 'loaded' }
|
||||
"RESULT workers_during $(Stamp) $((Workers) -join ' ')"
|
||||
for ($r = 1; $r -le 3; $r++) { RunProbe '9070xt' @('--device-name', 'gfx1201') $r; Start-Sleep -Seconds 2 }
|
||||
} finally {
|
||||
"RESULT runs_9070xt_done $(Stamp) (the card comes back through the RUNNER's restore on any exit, not here)"
|
||||
}
|
||||
# ---- the other AMD columns beside the miners (not the card under test) ----
|
||||
$cardState = if ((@(Get-CimInstance Win32_Process -Filter "Name = 'igneum-worker-opencl.exe'" -ErrorAction SilentlyContinue)).Count -gt 0) { 'loaded' } else { 'quiet' }
|
||||
foreach ($d in $byOrdinal) {
|
||||
$label = if ($d.name -match 'gfx1201') { 'gfx1201-old-platform' } elseif ($d.name -match 'gfx1036') { 'gfx1036' } else { 'amd-' + $d.idx }
|
||||
for ($r = 1; $r -le 3; $r++) { RunProbe $label @('--device', "$($d.idx)") $r; Start-Sleep -Seconds 2 }
|
||||
}
|
||||
"RESULT workers_after $(Stamp) $((Workers) -join ' ')"
|
||||
"RESULT end $(Stamp)"
|
||||
$status = if ($mainRows -gt 0) { 'done' } elseif ($rowsTotal -gt 0) { 'partial' } else { 'failed' }
|
||||
Summary $status @{ runs = $runs; family_rows = $rowsTotal; rows_9070xt = $mainRows; card_alone_9070xt = $cardAlone; exact_rows = $exactRows; failed_builds = $failedBuilds; familybest_rows = $built; probe_errors = $probeErrors }
|
||||
if ($status -eq 'failed') { exit 1 }
|
||||
exit 0
|
||||
79
tools/ca3-v4-amend/pc1-ca4-microbench.ps1
Normal file
79
tools/ca3-v4-amend/pc1-ca4-microbench.ps1
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
# Counter ASIC 4 research (7 October 2026): the per-block micro-benchmark of the research lane (ad6a2bd47d4a46105) on PC 1's RTX 5090,
|
||||
# the card alone through the RUNNER's --cards-off, no prompt. The exe is the research lane's scratch worker with --microbench
|
||||
# (igneum-worker-cuda-ca4mb.exe, sha256 49aa60c4b28ed77a54655476d0f07c06a54aa22e4f6f6ccadd4019b84cbf0268; its own fetch job
|
||||
# fetch-ca4-microbench-exe-20261007). Twice: at the unlocked clock, and at the 1,300 MHz knee lock when the Power Helper task
|
||||
# answers (the lock path of pc1-v4-efficiency.ps1: the task started by the owning user, `<seq> dev <idx>` + `<seq> lgc 1300` through
|
||||
# cmd.txt, the base sequence from cmd.txt and helper.log, rgc in finally; a silent helper skips the locked state and says so). Per
|
||||
# state: `--microbench --mb-seconds 60` (20 probes, about 22 min) with nvidia-smi at 1 Hz; every `RESULT microbench probe=...` line
|
||||
# of the worker is kept verbatim and a `RESULT ca4-research probe=<name> state=<unlocked|lock1300> watts=<mean> watts_instant= watts_average=
|
||||
# sm_mhz= mem_mhz= G_ops_s= checksum= status=` row is derived from the probe's start_utc..end_utc window (the mean from 8 s in).
|
||||
# Compile failures and skips print as the worker prints them. Never quits, pauses, resumes or updates the installed app.
|
||||
$ErrorActionPreference = 'Continue'
|
||||
$jobName = 'pc1-ca4-microbench'
|
||||
$started = Get-Date
|
||||
function Stamp { (Get-Date).ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ssZ') }
|
||||
function Summary([string] $status, [hashtable] $extra) { $o = [ordered]@{ job = $jobName; status = $status; duration_s = [int]((Get-Date) - $started).TotalSeconds; finished_at = (Stamp) }; foreach ($k in $extra.Keys) { $o[$k] = $extra[$k] }; 'SUMMARY ' + ($o | ConvertTo-Json -Compress -Depth 4) }
|
||||
$jobDir = $env:IGNEUM_JOB_DIR; if (-not $jobDir) { $jobDir = Join-Path $env:TEMP 'igneum-ca4-mb' }
|
||||
New-Item -ItemType Directory -Force -Path $jobDir | Out-Null
|
||||
$jobs = Split-Path $jobDir
|
||||
$exe = Join-Path (Join-Path $jobs 'fetch-ca4-microbench-exe-20261007') 'igneum-worker-cuda-ca4mb.exe'
|
||||
if (-not (Test-Path $exe)) { "RESULT error exe missing at $exe (the fetch job fetch-ca4-microbench-exe-20261007 runs first)"; Summary 'failed' @{ error = 'exe missing' }; exit 2 }
|
||||
"RESULT start $(Stamp) job=$jobName exe=$exe sha256=$((Get-FileHash -Algorithm SHA256 $exe).Hash.ToLower()) label=ca4-research"
|
||||
$smi = (Get-Command nvidia-smi -ErrorAction SilentlyContinue).Source
|
||||
if (-not $smi) { foreach ($p in @((Join-Path $env:ProgramFiles 'NVIDIA Corporation\NVSMI\nvidia-smi.exe'), (Join-Path $env:SystemRoot 'System32\nvidia-smi.exe'))) { if (Test-Path $p) { $smi = $p; break } } }
|
||||
$row = @(& $smi --query-gpu=index,name,uuid,pci.bus_id --format=csv,noheader 2>&1 | ForEach-Object { "$_" }) | Where-Object { $_ -match 'RTX 5090' } | Select-Object -First 1
|
||||
if (-not $row) { 'RESULT error no RTX 5090'; Summary 'failed' @{ error = 'no 5090' }; exit 2 }
|
||||
$fld = $row -split ',\s*'; $idx = $fld[0].Trim(); $uuid = $fld[2].Trim(); $bus = $fld[3].Trim()
|
||||
"RESULT card index=$idx uuid=$uuid bus=$bus"
|
||||
$apps = @(& $smi --query-compute-apps=gpu_bus_id,pid,process_name --format=csv,noheader 2>$null | ForEach-Object { "$_" } | Where-Object { $_ -match [regex]::Escape($bus) -and $_ -match 'igneum' })
|
||||
$t = 0; while ($t -lt 150 -and $apps.Count -gt 0) { Start-Sleep -Seconds 5; $t += 5; $apps = @(& $smi --query-compute-apps=gpu_bus_id,pid,process_name --format=csv,noheader 2>$null | ForEach-Object { "$_" } | Where-Object { $_ -match [regex]::Escape($bus) -and $_ -match 'igneum' }) }
|
||||
"RESULT card $(Stamp) $(if ($apps.Count -eq 0) { "quiet after $t s (the runner's --cards-off took)" } else { "UNCONFIRMED: $($apps -join '; ')" })"
|
||||
# ---- the helper (the lock path), as pc1-v4-efficiency.ps1 ----
|
||||
$helperDir = Join-Path $env:LOCALAPPDATA 'igneum\app\sweep'; $cmdFile = Join-Path $helperDir 'cmd.txt'; $helperLog = Join-Path $helperDir 'helper.log'
|
||||
function HelperSeqBase() { $m = 0; foreach ($f in @($cmdFile, $helperLog)) { if (Test-Path $f) { foreach ($l in (Get-Content -LiteralPath $f -ErrorAction SilentlyContinue)) { if ($l -match '(?:^|\s)(\d{1,6}) (?:dev|pl|lgc|rgc|lmc|rmc|nvidia-smi|reregister)\b') { $v = [int]$Matches[1]; if ($v -gt $m) { $m = $v } } } } }; return $m + 100 }
|
||||
function HelperSend([string] $verb) {
|
||||
$s0 = HelperSeqBase; $s1 = $s0 + 1
|
||||
$mark = 0; if (Test-Path $helperLog) { $mark = @(Get-Content -LiteralPath $helperLog -ErrorAction SilentlyContinue).Count }
|
||||
$pad = ((0..3) | ForEach-Object { "$($s0 - 8 + 2 * $_) dev $idx" }) -join "`n"; Set-Content -LiteralPath $cmdFile -Value "$pad`n$s0 dev $idx`n$s1 $verb`n" -NoNewline -Encoding ascii
|
||||
$t = 0; while ($t -lt 30) { Start-Sleep -Seconds 1; $t++; $new = @(Get-Content -LiteralPath $helperLog -ErrorAction SilentlyContinue); if ($new.Count -gt $mark) { $hit = $new[$mark..($new.Count - 1)] | Where-Object { $_ -match "\b$s1 nvidia-smi " } | Select-Object -First 1; if ($hit) { return ($hit -replace '^\d+\s+', '') } } }
|
||||
return "no helper.log line for seq $s1 within 30 s"
|
||||
}
|
||||
$viaHelper = $false
|
||||
if (Get-ScheduledTask -TaskName 'Igneum Power Helper' -ErrorAction SilentlyContinue) { try { Start-ScheduledTask -TaskName 'Igneum Power Helper' -ErrorAction Stop; Start-Sleep -Seconds 3; $viaHelper = $true; 'RESULT helper task started' } catch { "RESULT helper start failed: $_" } } else { 'RESULT helper task not registered: the locked state is skipped' }
|
||||
# ---- the sampler ----
|
||||
$samples = Join-Path $jobDir 'nv-samples.csv'; if (Test-Path $samples) { Remove-Item -LiteralPath $samples -Force }
|
||||
$sampler = Start-Process -FilePath $smi -ArgumentList @('-i', $idx, '--query-gpu=timestamp,power.draw,clocks.sm,clocks.mem,temperature.gpu,utilization.gpu,power.draw.instant,power.draw.average', '--format=csv,noheader,nounits', '-lms', '1000') -RedirectStandardOutput $samples -WindowStyle Hidden -PassThru
|
||||
Start-Sleep -Seconds 5
|
||||
"RESULT sampler pid=$($sampler.Id) first=[$((Get-Content -LiteralPath $samples -ErrorAction SilentlyContinue | Select-Object -First 1))]"
|
||||
function Mean($vals) { $v = @($vals | ForEach-Object { [double]$_ }); if ($v.Count -eq 0) { return $null }; return [math]::Round((($v | Measure-Object -Sum).Sum / $v.Count), 1) }
|
||||
function Window([datetime] $t0, [datetime] $t1) { $sel = @(); if (Test-Path $samples) { foreach ($l in (Get-Content -LiteralPath $samples -ErrorAction SilentlyContinue)) { $p = $l -split ',\s*'; if ($p.Count -lt 6) { continue }; try { $ts = [datetime]::ParseExact($p[0].Trim(), 'yyyy/MM/dd HH:mm:ss.fff', [cultureinfo]::InvariantCulture) } catch { continue }; if ($ts -ge $t0 -and $ts -le $t1) { $sel += ,$p } } }; return ,$sel }
|
||||
function ParseUtc([string] $s) { try { return [datetime]::Parse($s, [cultureinfo]::InvariantCulture, [System.Globalization.DateTimeStyles]::AdjustToUniversal).ToLocalTime() } catch { return $null } }
|
||||
function RunState([string] $state) {
|
||||
$env:CUDA_VISIBLE_DEVICES = $uuid
|
||||
"RESULT microbench-state $state start $(Stamp) $((& $smi -i $idx --query-gpu=clocks.sm,clocks.applications.graphics,power.draw --format=csv,noheader 2>&1 | Out-String).Trim())"
|
||||
$lines = @(& $exe --microbench --mb-seconds 60 --device 0 2>&1 | ForEach-Object { "$_" })
|
||||
$rc = $LASTEXITCODE
|
||||
foreach ($l in $lines) { if ($l -match '^RESULT microbench') { "$l state=$state" } elseif ($l -match 'error|fail|compile|skipped') { "RESULT microbench-note state=$state $l" } }
|
||||
foreach ($l in ($lines | Where-Object { $_ -match '^RESULT microbench probe=' })) {
|
||||
$name = ''; $g = ''; $ck = ''; $st = ''; $su = ''; $eu = ''
|
||||
if ($l -match 'probe=(\S+)') { $name = $Matches[1] }; if ($l -match 'G_ops_s=(\S+)') { $g = $Matches[1] }; if ($l -match 'checksum=(\S+)') { $ck = $Matches[1] }; if ($l -match 'status=(\S+)') { $st = $Matches[1] }
|
||||
if ($l -match 'start_utc=(\S+)') { $su = $Matches[1] }; if ($l -match 'end_utc=(\S+)') { $eu = $Matches[1] }
|
||||
$t0 = ParseUtc $su; $t1 = ParseUtc $eu
|
||||
if ($t0 -and $t1) { $w = Window $t0.AddSeconds(8) $t1; $watts = Mean ($w | ForEach-Object { $_[1] }); $sm = Mean ($w | ForEach-Object { $_[2] }); $mem = Mean ($w | ForEach-Object { $_[3] }); $util = Mean ($w | ForEach-Object { $_[5] }); $wi = Mean ($w | Where-Object { $_.Count -ge 8 -and $_[6] -match '^\s*[\d.]+\s*$' } | ForEach-Object { $_[6] }); $wa = Mean ($w | Where-Object { $_.Count -ge 8 -and $_[7] -match '^\s*[\d.]+\s*$' } | ForEach-Object { $_[7] }); "RESULT ca4-research probe=$name state=$state watts=$watts watts_instant=$wi watts_average=$wa sm_mhz=$sm mem_mhz=$mem util=$util G_ops_s=$g checksum=$ck status=$st samples=$($w.Count)" }
|
||||
else { "RESULT ca4-research probe=$name state=$state watts= (no start_utc/end_utc on the line) G_ops_s=$g checksum=$ck status=$st" }
|
||||
}
|
||||
"RESULT microbench-state $state end $(Stamp) exit=$rc lines=$($lines.Count)"
|
||||
}
|
||||
$locked = $false
|
||||
try {
|
||||
RunState 'unlocked'
|
||||
if ($viaHelper) {
|
||||
$r = HelperSend 'lgc 1300'
|
||||
if ($r -match 'no helper.log') { "RESULT lock1300 skipped: the helper gave no answer ($r)" } else { "RESULT lock1300 set: $r"; $locked = $true; Start-Sleep -Seconds 3; RunState 'lock1300' }
|
||||
} else { 'RESULT lock1300 skipped: no helper path' }
|
||||
Summary 'done' @{ locked_state_ran = $locked }
|
||||
exit 0
|
||||
} finally {
|
||||
if ($locked) { $r = HelperSend 'rgc'; "RESULT restore rgc: $r ; applications clock: $((& $smi -i $idx --query-gpu=clocks.applications.graphics --format=csv,noheader 2>&1 | Out-String).Trim())" }
|
||||
if ($sampler -and -not $sampler.HasExited) { Stop-Process -Id $sampler.Id -Force -ErrorAction SilentlyContinue; 'RESULT restore sampler stopped' }
|
||||
}
|
||||
96
tools/ca3-v4-amend/pc1-ca4-packs.ps1
Normal file
96
tools/ca3-v4-amend/pc1-ca4-packs.ps1
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
# Counter ASIC 4 research (7 October 2026): the two prototype classes of the research lane (ad6a2bd47d4a46105) on PC 1's RTX 5090,
|
||||
# the card alone through the RUNNER's --cards-off, no prompt. The kit (fetch job fetch-ca4-packs-20261007): mx8-genesis (the class v3
|
||||
# control from packs-ca2-mixer), mx8_sh256x27 (the class v4-shaped shadow control), mx8_shl256x27 (the per-load shadow, 16 blocks of
|
||||
# 16 after each load), mx8_mm128, mx8_mm512, mx8_mm1430 (the int8 mma.m8n8k16 tile block, inline PTX in the kernel text); every pack
|
||||
# generator 2 over seed igneum-genesis, day 2026-10-03, 1 GiB. The INSTALLED igneum-worker-cuda.exe --bench takes each pack as it took
|
||||
# the 6 October ladder packs (NVRTC compiles the pack's own kernel text; an NVRTC refusal of a tile pack is the row). Per pack, 250
|
||||
# batches of 2^24 at --block-warps 1 with nvidia-smi at 1 Hz (the mean from 8 s in): MH/s, watts (all three power fields), SM MHz,
|
||||
# the fingerprint and the self-test verdict (check=PASS: the 96 vector lanes through the bound kernel, the CUDA-against-CPU gate for
|
||||
# both prototypes; a FAIL row is the finding). Once unlocked and once at the 1,300 MHz knee lock through the Power Helper task when it
|
||||
# answers (the sequence rule; rgc in finally; a silent helper skips the locked state and says so). Rows: `RESULT ca4-research pack=
|
||||
# state= mhs= watts= ... fingerprint= check=`, the worker's own RESULT line verbatim. Never quits, pauses, resumes or updates the app.
|
||||
$ErrorActionPreference = 'Continue'
|
||||
$jobName = 'pc1-ca4-packs'
|
||||
$started = Get-Date
|
||||
function Stamp { (Get-Date).ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ssZ') }
|
||||
function Summary([string] $status, [hashtable] $extra) { $o = [ordered]@{ job = $jobName; status = $status; duration_s = [int]((Get-Date) - $started).TotalSeconds; finished_at = (Stamp) }; foreach ($k in $extra.Keys) { $o[$k] = $extra[$k] }; 'SUMMARY ' + ($o | ConvertTo-Json -Compress -Depth 4) }
|
||||
$jobDir = $env:IGNEUM_JOB_DIR; if (-not $jobDir) { $jobDir = Join-Path $env:TEMP 'igneum-ca4-mb' }
|
||||
New-Item -ItemType Directory -Force -Path $jobDir | Out-Null
|
||||
$jobs = Split-Path $jobDir
|
||||
$packs = Join-Path (Join-Path $jobs 'fetch-ca4-packs-20261007') 'packs'
|
||||
if (-not (Test-Path $packs)) { "RESULT error packs missing at $packs (the fetch job fetch-ca4-packs-20261007 runs first)"; Summary 'failed' @{ error = 'packs missing' }; exit 2 }
|
||||
$inst = @("$env:LOCALAPPDATA\Programs\Igneum Miner", "$env:ProgramFiles\Igneum Miner") | Where-Object { Test-Path (Join-Path $_ 'igneum-worker-cuda.exe') } | Select-Object -First 1
|
||||
if (-not $inst) { 'RESULT error no installed igneum-worker-cuda.exe'; Summary 'failed' @{ error = 'no worker' }; exit 2 }
|
||||
$exe = Join-Path $inst 'igneum-worker-cuda.exe'
|
||||
# mx8_shl256x27 is the research lane's first per-load export (id 854050a4293f0615, the known-failed record: 11 percent duplicate reads from a lossy
|
||||
# base writer under the sub-blocks) and mx8_shl256x27_v2 its fix (counter-asic-4 2f718001, attempt 3, id bd64b207a30413fb); both run and every row
|
||||
# names its pack directory and the program id read from its program.json
|
||||
$packOrder = @('mx8-genesis', 'mx8_sh256x27', 'mx8_shl256x27', 'mx8_shl256x27_v2', 'mx8_mm128', 'mx8_mm512', 'mx8_mm1430')
|
||||
foreach ($pk in $packOrder) { $d = Join-Path $packs $pk; if (-not (Test-Path $d)) { "RESULT error pack missing at $d"; Summary 'failed' @{ error = "pack $pk missing" }; exit 2 }; foreach ($f in (Get-ChildItem $d -File | Sort-Object Name)) { "RESULT kitfile $pk/$($f.Name) sha256 $((Get-FileHash -Algorithm SHA256 $f.FullName).Hash.ToLower())" } }
|
||||
"RESULT start $(Stamp) job=$jobName exe=$exe sha256=$((Get-FileHash -Algorithm SHA256 $exe).Hash.ToLower()) label=ca4-research"
|
||||
$smi = (Get-Command nvidia-smi -ErrorAction SilentlyContinue).Source
|
||||
if (-not $smi) { foreach ($p in @((Join-Path $env:ProgramFiles 'NVIDIA Corporation\NVSMI\nvidia-smi.exe'), (Join-Path $env:SystemRoot 'System32\nvidia-smi.exe'))) { if (Test-Path $p) { $smi = $p; break } } }
|
||||
$row = @(& $smi --query-gpu=index,name,uuid,pci.bus_id --format=csv,noheader 2>&1 | ForEach-Object { "$_" }) | Where-Object { $_ -match 'RTX 5090' } | Select-Object -First 1
|
||||
if (-not $row) { 'RESULT error no RTX 5090'; Summary 'failed' @{ error = 'no 5090' }; exit 2 }
|
||||
$fld = $row -split ',\s*'; $idx = $fld[0].Trim(); $uuid = $fld[2].Trim(); $bus = $fld[3].Trim()
|
||||
"RESULT card index=$idx uuid=$uuid bus=$bus"
|
||||
$apps = @(& $smi --query-compute-apps=gpu_bus_id,pid,process_name --format=csv,noheader 2>$null | ForEach-Object { "$_" } | Where-Object { $_ -match [regex]::Escape($bus) -and $_ -match 'igneum' })
|
||||
$t = 0; while ($t -lt 150 -and $apps.Count -gt 0) { Start-Sleep -Seconds 5; $t += 5; $apps = @(& $smi --query-compute-apps=gpu_bus_id,pid,process_name --format=csv,noheader 2>$null | ForEach-Object { "$_" } | Where-Object { $_ -match [regex]::Escape($bus) -and $_ -match 'igneum' }) }
|
||||
"RESULT card $(Stamp) $(if ($apps.Count -eq 0) { "quiet after $t s (the runner's --cards-off took)" } else { "UNCONFIRMED: $($apps -join '; ')" })"
|
||||
# ---- the helper (the lock path), as pc1-v4-efficiency.ps1 ----
|
||||
$helperDir = Join-Path $env:LOCALAPPDATA 'igneum\app\sweep'; $cmdFile = Join-Path $helperDir 'cmd.txt'; $helperLog = Join-Path $helperDir 'helper.log'
|
||||
function HelperSeqBase() { $m = 0; foreach ($f in @($cmdFile, $helperLog)) { if (Test-Path $f) { foreach ($l in (Get-Content -LiteralPath $f -ErrorAction SilentlyContinue)) { if ($l -match '(?:^|\s)(\d{1,6}) (?:dev|pl|lgc|rgc|lmc|rmc|nvidia-smi|reregister)\b') { $v = [int]$Matches[1]; if ($v -gt $m) { $m = $v } } } } }; return $m + 100 }
|
||||
function HelperSend([string] $verb) {
|
||||
$s0 = HelperSeqBase; $s1 = $s0 + 1
|
||||
$mark = 0; if (Test-Path $helperLog) { $mark = @(Get-Content -LiteralPath $helperLog -ErrorAction SilentlyContinue).Count }
|
||||
$pad = ((0..3) | ForEach-Object { "$($s0 - 8 + 2 * $_) dev $idx" }) -join "`n"; Set-Content -LiteralPath $cmdFile -Value "$pad`n$s0 dev $idx`n$s1 $verb`n" -NoNewline -Encoding ascii
|
||||
$t = 0; while ($t -lt 30) { Start-Sleep -Seconds 1; $t++; $new = @(Get-Content -LiteralPath $helperLog -ErrorAction SilentlyContinue); if ($new.Count -gt $mark) { $hit = $new[$mark..($new.Count - 1)] | Where-Object { $_ -match "\b$s1 nvidia-smi " } | Select-Object -First 1; if ($hit) { return ($hit -replace '^\d+\s+', '') } } }
|
||||
return "no helper.log line for seq $s1 within 30 s"
|
||||
}
|
||||
$viaHelper = $false
|
||||
if (Get-ScheduledTask -TaskName 'Igneum Power Helper' -ErrorAction SilentlyContinue) { try { Start-ScheduledTask -TaskName 'Igneum Power Helper' -ErrorAction Stop; Start-Sleep -Seconds 3; $viaHelper = $true; 'RESULT helper task started' } catch { "RESULT helper start failed: $_" } } else { 'RESULT helper task not registered: the locked state is skipped' }
|
||||
# ---- the sampler ----
|
||||
$samples = Join-Path $jobDir 'nv-samples.csv'; if (Test-Path $samples) { Remove-Item -LiteralPath $samples -Force }
|
||||
$sampler = Start-Process -FilePath $smi -ArgumentList @('-i', $idx, '--query-gpu=timestamp,power.draw,clocks.sm,clocks.mem,temperature.gpu,utilization.gpu,power.draw.instant,power.draw.average', '--format=csv,noheader,nounits', '-lms', '1000') -RedirectStandardOutput $samples -WindowStyle Hidden -PassThru
|
||||
Start-Sleep -Seconds 5
|
||||
"RESULT sampler pid=$($sampler.Id) first=[$((Get-Content -LiteralPath $samples -ErrorAction SilentlyContinue | Select-Object -First 1))]"
|
||||
function Mean($vals) { $v = @($vals | ForEach-Object { [double]$_ }); if ($v.Count -eq 0) { return $null }; return [math]::Round((($v | Measure-Object -Sum).Sum / $v.Count), 1) }
|
||||
function Window([datetime] $t0, [datetime] $t1) { $sel = @(); if (Test-Path $samples) { foreach ($l in (Get-Content -LiteralPath $samples -ErrorAction SilentlyContinue)) { $p = $l -split ',\s*'; if ($p.Count -lt 6) { continue }; try { $ts = [datetime]::ParseExact($p[0].Trim(), 'yyyy/MM/dd HH:mm:ss.fff', [cultureinfo]::InvariantCulture) } catch { continue }; if ($ts -ge $t0 -and $ts -le $t1) { $sel += ,$p } } }; return ,$sel }
|
||||
function ParseUtc([string] $s) { try { return [datetime]::Parse($s, [cultureinfo]::InvariantCulture, [System.Globalization.DateTimeStyles]::AdjustToUniversal).ToLocalTime() } catch { return $null } }
|
||||
function RunState([string] $state) {
|
||||
$env:CUDA_VISIBLE_DEVICES = $uuid
|
||||
"RESULT packs-state $state start $(Stamp) $((& $smi -i $idx --query-gpu=clocks.sm,clocks.applications.graphics,power.draw --format=csv,noheader 2>&1 | Out-String).Trim())"
|
||||
foreach ($pk in $packOrder) {
|
||||
$d = Join-Path $packs $pk
|
||||
$cls = ''; $pid = ''; try { $pj = Get-Content -LiteralPath (Join-Path $d 'program.json') -Raw | ConvertFrom-Json; $cls = [string] $pj.load_class; $pid = [string] $pj.program_id } catch { }
|
||||
$t0 = Get-Date
|
||||
$lines = @(& $exe --bench --pack $d --batches 250 --batch-log2 24 --block-warps 1 --device 0 2>&1 | ForEach-Object { "$_" })
|
||||
$rc = $LASTEXITCODE
|
||||
$t1 = Get-Date
|
||||
$res = $lines | Where-Object { $_ -match '^RESULT pack=' } | Select-Object -First 1
|
||||
foreach ($l in $lines) { if ($l -match '^RESULT pack=|error|FAIL|MISMATCH|compile|nvrtc|vector|lane|self-test|checksum' ) { "RESULT worker pack=$pk state=$state $l" } }
|
||||
$fp = ''; $mhs = ''; $check = ''
|
||||
if ($res -and $res -match 'fingerprint=([0-9a-f]{16})') { $fp = $Matches[1] }; if ($res -and $res -match 'mhs=([\d.]+)') { $mhs = $Matches[1] }; if ($res -and $res -match 'check=(\S+)') { $check = $Matches[1] }
|
||||
$w = Window $t0.AddSeconds(8) $t1
|
||||
$watts = Mean ($w | ForEach-Object { $_[1] }); $sm = Mean ($w | ForEach-Object { $_[2] }); $mem = Mean ($w | ForEach-Object { $_[3] }); $util = Mean ($w | ForEach-Object { $_[5] })
|
||||
$wi = Mean ($w | Where-Object { $_.Count -ge 8 -and $_[6] -match '^\s*[\d.]+\s*$' } | ForEach-Object { $_[6] }); $wa = Mean ($w | Where-Object { $_.Count -ge 8 -and $_[7] -match '^\s*[\d.]+\s*$' } | ForEach-Object { $_[7] })
|
||||
$eff = if ($watts -and $mhs) { [math]::Round([double]$mhs / $watts, 4) } else { '' }
|
||||
$err = ''; if (-not $res -or $rc -ne 0) { $e = ($lines | Where-Object { $_ -match 'FAIL|error|MISMATCH|compile' } | Select-Object -First 1); $err = if ($e) { ($e -replace '\s+', ' ') } else { "exit $rc, no RESULT line" } }
|
||||
"RESULT ca4-research pack=$pk dir=$d program_id=$pid class=$cls state=$state mhs=$mhs watts=$watts watts_instant=$wi watts_average=$wa mh_per_w=$eff sm_mhz=$sm mem_mhz=$mem util=$util fingerprint=$fp check=$check exit=$rc wall_s=$([int]($t1 - $t0).TotalSeconds) samples=$($w.Count) error=$err"
|
||||
Start-Sleep -Seconds 3
|
||||
}
|
||||
"RESULT packs-state $state end $(Stamp)"
|
||||
}
|
||||
$locked = $false
|
||||
try {
|
||||
RunState 'unlocked'
|
||||
if ($viaHelper) {
|
||||
$r = HelperSend 'lgc 1300'
|
||||
if ($r -match 'no helper.log') { "RESULT lock1300 skipped: the helper gave no answer ($r)" } else { "RESULT lock1300 set: $r"; $locked = $true; Start-Sleep -Seconds 3; RunState 'lock1300' }
|
||||
} else { 'RESULT lock1300 skipped: no helper path' }
|
||||
Summary 'done' @{ locked_state_ran = $locked }
|
||||
exit 0
|
||||
} finally {
|
||||
if ($locked) { $r = HelperSend 'rgc'; "RESULT restore rgc: $r ; applications clock: $((& $smi -i $idx --query-gpu=clocks.applications.graphics --format=csv,noheader 2>&1 | Out-String).Trim())" }
|
||||
if ($sampler -and -not $sampler.HasExited) { Stop-Process -Id $sampler.Id -Force -ErrorAction SilentlyContinue; 'RESULT restore sampler stopped' }
|
||||
}
|
||||
40
tools/ca3-v4-amend/pc1-clocks-restore.ps1
Normal file
40
tools/ca3-v4-amend/pc1-clocks-restore.ps1
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
# PC 1: put the RTX 5090's clocks back to the driver default through the Igneum Power Helper task (7 October 2026, 20:4x UTC).
|
||||
# The second efficiency pass (run-ca3-pc1-v4-eff-5090-floor-20261007) lost the helper at its 1,000 MHz step (no helper.log line
|
||||
# within 30 s for that and every later command, the `nvidia-smi -lgc 0,1000` most likely hanging inside the helper), so its
|
||||
# closing `rgc` never ran and the card stayed at the 1,100 MHz lock (sm 1,087 on the unlocked-end rows; the live miner at 118
|
||||
# to 122 MH/s instead of 136). This script stops the task (the owning user may stop their own task), starts it again, sends
|
||||
# `<seq> dev <idx>` then `<seq> rgc` through cmd.txt, waits for helper.log's answer and prints nvidia-smi's clocks before and
|
||||
# after. No prompt, no card switch, nothing posted to the app, settings.json untouched; runs beside the miners.
|
||||
$ErrorActionPreference = 'Continue'
|
||||
function Stamp { (Get-Date).ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ssZ') }
|
||||
$smi = $null; $c = Get-Command nvidia-smi -ErrorAction SilentlyContinue; if ($c) { $smi = $c.Source }
|
||||
if (-not $smi) { foreach ($p in @((Join-Path $env:ProgramFiles 'NVIDIA Corporation\NVSMI\nvidia-smi.exe'), (Join-Path $env:SystemRoot 'System32\nvidia-smi.exe'))) { if (Test-Path $p) { $smi = $p; break } } }
|
||||
if (-not $smi) { 'RESULT error nvidia-smi not found'; exit 2 }
|
||||
"RESULT before $(Stamp) $((& $smi --query-gpu=index,name,clocks.sm,clocks.applications.graphics,clocks_throttle_reasons.active,power.draw --format=csv,noheader 2>&1 | Out-String).Trim() -replace "`r?`n", ' | ')"
|
||||
$row = @(& $smi --query-gpu=index,name --format=csv,noheader 2>&1 | ForEach-Object { "$_" }) | Where-Object { $_ -match 'RTX 5090' } | Select-Object -First 1
|
||||
if (-not $row) { 'RESULT error no RTX 5090 in the list'; exit 2 }
|
||||
$idx = ($row -split ',')[0].Trim()
|
||||
$helperDir = Join-Path $env:LOCALAPPDATA 'igneum\app\sweep'
|
||||
$cmdFile = Join-Path $helperDir 'cmd.txt'; $helperLog = Join-Path $helperDir 'helper.log'
|
||||
if (Test-Path $helperLog) { "RESULT helper.log tail: $((Get-Content -LiteralPath $helperLog -Tail 4) -join ' || ')" }
|
||||
$task = Get-ScheduledTask -TaskName 'Igneum Power Helper' -ErrorAction SilentlyContinue
|
||||
if (-not $task) { 'RESULT error the Igneum Power Helper task is not registered: no reset path without a prompt (Power control off)'; exit 3 }
|
||||
"RESULT task state before: $($task.State)"
|
||||
try { Stop-ScheduledTask -TaskName 'Igneum Power Helper' -ErrorAction Stop; 'RESULT task stopped' } catch { "RESULT task stop failed: $_" }
|
||||
Start-Sleep -Seconds 3
|
||||
try { Start-ScheduledTask -TaskName 'Igneum Power Helper' -ErrorAction Stop; 'RESULT task started' } catch { "RESULT task start failed: $_"; exit 3 }
|
||||
Start-Sleep -Seconds 3
|
||||
"RESULT task state now: $((Get-ScheduledTask -TaskName 'Igneum Power Helper').State)"
|
||||
$seq = 0
|
||||
if (Test-Path $cmdFile) { $seq = @(Get-Content -LiteralPath $cmdFile -ErrorAction SilentlyContinue | ForEach-Object { if ($_ -match '^(\d{1,6})\s') { [int]$Matches[1] } }) | Measure-Object -Maximum | Select-Object -ExpandProperty Maximum }
|
||||
if (-not $seq) { $seq = 0 }
|
||||
$s0 = [int]$seq + 100; $s1 = $s0 + 1
|
||||
$mark = 0; if (Test-Path $helperLog) { $mark = @(Get-Content -LiteralPath $helperLog -ErrorAction SilentlyContinue).Count }
|
||||
Set-Content -LiteralPath $cmdFile -Value "$s0 dev $idx`n$s1 rgc`n" -NoNewline -Encoding ascii
|
||||
$t = 0; $hit = $null
|
||||
while ($t -lt 45 -and -not $hit) { Start-Sleep -Seconds 1; $t++; $new = @(Get-Content -LiteralPath $helperLog -ErrorAction SilentlyContinue); if ($new.Count -gt $mark) { $hit = $new[$mark..($new.Count - 1)] | Where-Object { $_ -match "\b$s1 nvidia-smi " } | Select-Object -First 1 } }
|
||||
if ($hit) { "RESULT reset $(Stamp) $hit" } else { "RESULT reset $(Stamp) no helper.log line for seq $s1 within 45 s" }
|
||||
Start-Sleep -Seconds 5
|
||||
"RESULT after $(Stamp) $((& $smi --query-gpu=index,name,clocks.sm,clocks.applications.graphics,clocks_throttle_reasons.active,power.draw --format=csv,noheader 2>&1 | Out-String).Trim() -replace "`r?`n", ' | ')"
|
||||
$app = @(& $smi --query-gpu=index,clocks.applications.graphics --format=csv,noheader 2>&1 | ForEach-Object { "$_" }) | Where-Object { $_ -match "^$idx," } | Select-Object -First 1
|
||||
if ($hit -and $app -notmatch '\b1[0-4]\d\d MHz') { 'SUMMARY {"job":"pc1-clocks-restore","status":"done"}'; exit 0 } else { 'SUMMARY {"job":"pc1-clocks-restore","status":"check"}'; exit 1 }
|
||||
74
tools/ca3-v4-amend/pc1-ember-card.ps1
Normal file
74
tools/ca3-v4-amend/pc1-ember-card.ps1
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
# Counter ASIC 3.0, PC 1 queue (7 October 2026): Ember Tune on ONE card of the installed app, by the job id (an id carrying
|
||||
# "9070" tunes the RX 9070 XT, "5080" the RTX 5080, otherwise the RTX 5090), the other cards untouched and mining on. The shape
|
||||
# is relay/playbooks/ember-installed-tune-pc1.ps1 (6 October 2026): the SHIPPED app tunes itself through the registered Power
|
||||
# Helper task, this script only asks (POST api/tune/goal climb on, POST api/sweep/start for the one card) and watches the TUNE
|
||||
# rows in the app's own log and api/state, then puts the climb switch back. On 0.3.21 (ember.rs may_set) a step is SET on an
|
||||
# AMD card only when igneum-gpu-telemetry beside the app gives the card a tune line (ADLX) and Power control is on, else the
|
||||
# tune is measure-only and the TUNE rows carry the reason; this script prints the card's tune_line before and after so the
|
||||
# "measure only" reading and its reason are on RESULT lines. Never quits, pauses or resumes the installed app, never posts to
|
||||
# /api/cards, never writes settings.json. Not elevated (the task carries the rights).
|
||||
$ErrorActionPreference = 'Continue'
|
||||
function Say([string] $s) { Write-Output ('[' + (Get-Date -Format 'HH:mm:ss') + '] ' + $s) }
|
||||
$appDir = Join-Path $env:LOCALAPPDATA 'igneum\app'
|
||||
$u = Join-Path $appDir 'app.url'
|
||||
if (-not (Test-Path -LiteralPath $u)) { Write-Output 'RESULT TUNE error=no_app_url (installed app not running)'; exit 1 }
|
||||
$base = (Get-Content -LiteralPath $u -Raw).Trim()
|
||||
function State() { try { return ((Invoke-WebRequest -Uri ($base + 'api/state') -UseBasicParsing -TimeoutSec 5).Content | ConvertFrom-Json) } catch { return $null } }
|
||||
function Post([string] $path, [string] $json) { try { Invoke-WebRequest -Uri ($base + $path) -Method POST -Body $json -ContentType 'application/json' -UseBasicParsing -TimeoutSec 5 | Out-Null; return $true } catch { Say ($path + ': ' + $_.Exception.Message); return $false } }
|
||||
$s = State
|
||||
if (-not $s) { Write-Output 'RESULT TUNE error=state_unreadable'; exit 1 }
|
||||
$ver = [string]$s.version
|
||||
Write-Output ('RESULT TUNE installed app ' + $ver + ' power_control=' + $s.settings.power_control + ' tuning_off=' + $s.settings.tuning_off + ' climb_before=' + $s.settings.tune_climb + ' goal_before=' + $s.settings.tune_goal)
|
||||
$t = Get-ScheduledTask -TaskName 'Igneum Power Helper' -ErrorAction SilentlyContinue
|
||||
Write-Output ('RESULT TUNE task ' + $(if ($t) { $t.Actions[0].Execute + ' state=' + $t.State } else { 'NOT REGISTERED' }))
|
||||
if (-not $t) { Write-Output 'RESULT TUNE error=task_not_registered (the NVIDIA climbs would need a prompt; stopping)'; exit 1 }
|
||||
if (-not $s.settings.power_control) { Write-Output 'RESULT TUNE error=power_control_off'; exit 1 }
|
||||
$climbBefore = [bool]$s.settings.tune_climb
|
||||
$jobDir = $env:IGNEUM_JOB_DIR; $jobId = if ($jobDir) { Split-Path -Leaf $jobDir } else { '' }
|
||||
$want = if ($jobId -match '9070') { '9070' } elseif ($jobId -match '5080') { '5080' } else { '5090' }
|
||||
Write-Output ('RESULT TUNE job_id=' + $jobId + ' card_match=' + $want)
|
||||
$cards = @($s.mining.cards | Where-Object { $_.enabled -and ($_.vendor -eq 'nvidia' -or $_.vendor -eq 'amd') -and $_.name -notmatch 'Radeon\(TM\) Graphics' -and $_.name -match $want })
|
||||
if ($cards.Count -eq 0) { Write-Output ('RESULT TUNE error=no enabled card matching ' + $want + ' in api/state (names: ' + (($s.mining.cards | ForEach-Object { $_.name }) -join ', ') + ')'); exit 1 }
|
||||
Write-Output ('RESULT TUNE cards ' + (($cards | ForEach-Object { $_.key + ' (' + $_.name + ', ' + $_.vendor + ', sweep_state ' + $_.sweep_state + ', line "' + $_.tune_line + '")' }) -join ' | '))
|
||||
foreach ($c in $cards) { Write-Output ('RESULT TUNE before key=' + $c.key + ' power_w=' + $c.power_w + ' power_pct=' + $c.power_pct + ' clock_cap=' + $c.clock_cap_mhz + ' mhs=' + $c.mhs + ' line=' + $c.tune_line) }
|
||||
# the climb on (NVIDIA cards; an AMD card has no clock knob and runs its ladder regardless)
|
||||
if (-not (Post 'api/tune/goal' '{"goal":"balanced","climb":true}')) { Write-Output 'RESULT TUNE error=tune_goal_refused'; exit 1 }
|
||||
Start-Sleep -Seconds 2
|
||||
$order = @($cards | Where-Object { $_.vendor -eq 'amd' }) + @($cards | Where-Object { $_.vendor -eq 'nvidia' })
|
||||
$deadline = (Get-Date).AddMinutes(27)
|
||||
$logs = Join-Path $appDir 'logs'
|
||||
function LogMark() { $l = Get-ChildItem -Path $logs -Filter 'app-*.log' -ErrorAction SilentlyContinue | Sort-Object LastWriteTime -Descending | Select-Object -First 1; if ($l) { return @{ f = $l.FullName; n = (Get-Content -LiteralPath $l.FullName).Count } } else { return @{ f = ''; n = 0 } } }
|
||||
function LogSince($m) { if (-not $m.f) { return @() }; $all = @(Get-Content -LiteralPath $m.f -ErrorAction SilentlyContinue); if ($all.Count -le $m.n) { return @() }; return @($all[$m.n..($all.Count - 1)]) }
|
||||
$rows = 0
|
||||
foreach ($c in $order) {
|
||||
if ((Get-Date) -gt $deadline) { Say 'budget spent before ' + $c.key; break }
|
||||
$m = LogMark
|
||||
$body = '{"key":"' + ($c.key -replace '"', '\"') + '"}'
|
||||
if (-not (Post 'api/sweep/start' $body)) { Write-Output ('RESULT TUNE error=sweep_start_refused key=' + $c.key); continue }
|
||||
Say ('tune asked for ' + $c.key)
|
||||
$seen = 0; $done = $false; $started = Get-Date
|
||||
while (-not $done -and (Get-Date) -lt $deadline) {
|
||||
Start-Sleep -Seconds 10
|
||||
$new = @(LogSince $m)
|
||||
while ($seen -lt $new.Count) { $l = [string]$new[$seen]; $seen++; if ($l -match 'TUNE (card=|chosen|start|aborted|progress)') { if ($l -notmatch 'TUNE progress') { Write-Output ('RESULT ' + ($l -replace '^.*?(TUNE )', 'TUNE ')) }; if ($l -match 'TUNE card=') { $rows++ } } }
|
||||
$st = State
|
||||
if ($st) {
|
||||
$cc = $st.mining.cards | Where-Object { $_.key -eq $c.key } | Select-Object -First 1
|
||||
if ($cc) {
|
||||
$running = ($cc.sweep_state -eq 'running')
|
||||
if (-not $running -and ((Get-Date) - $started).TotalSeconds -gt 180) { $done = $true }
|
||||
if ($running -and $cc.tune_steps -gt 0 -and ($seen % 6 -eq 0)) { Say ($c.key + ': step ' + $cc.tune_step + ' of ' + $cc.tune_steps + ', ' + $cc.sweep_note) }
|
||||
}
|
||||
}
|
||||
}
|
||||
$st = State
|
||||
if ($st) { $cc = $st.mining.cards | Where-Object { $_.key -eq $c.key } | Select-Object -First 1; if ($cc) { Write-Output ('RESULT TUNE after key=' + $c.key + ' power_w=' + $cc.power_w + ' power_pct=' + $cc.power_pct + ' clock_cap=' + $cc.clock_cap_mhz + ' line=' + $cc.tune_line + ' source=' + $cc.tune_source + ' note=' + $cc.sweep_note); if ($cc.tune_curve) { Write-Output ('RESULT TUNE curve key=' + $c.key + ' ' + (($cc.tune_curve | ForEach-Object { ($_.clock_mhz) + 'MHz/' + ($_.mem_mhz) + 'mem/' + ($_.power_pct) + '%:' + $_.mhs + 'MH/s ' + $_.watts + 'W ' + $_.eff + ' ' + $_.mark }) -join ' | ')) } } }
|
||||
}
|
||||
# the climb switch back to what it was
|
||||
Post 'api/tune/goal' ('{"climb":' + $(if ($climbBefore) { 'true' } else { 'false' }) + '}') | Out-Null
|
||||
Write-Output ('RESULT TUNE climb_restored=' + $climbBefore + ' rows=' + $rows)
|
||||
$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,clocks.mem --format=csv,noheader 2>&1 | Out-String) -replace "`r?`n", ' | ')) }
|
||||
if ($rows -eq 0) { Write-Output 'RESULT TUNE error=no_rows'; exit 1 }
|
||||
exit 0
|
||||
44
tools/ca3-v4-amend/pc1-helper-diag.ps1
Normal file
44
tools/ca3-v4-amend/pc1-helper-diag.ps1
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
# PC 1: why does the Igneum Power Helper task give no answer (7 October 2026, 21:2x UTC)? The 5080 pass -c sent its commands
|
||||
# through cmd.txt with sequence numbers above every number in cmd.txt and helper.log and got no helper.log line within 30 s,
|
||||
# before and after a Stop/Start of the task. Read-only except the task restart and one `rgc` on the RTX 5080 (nothing is
|
||||
# locked there, so the reset is a no-op that proves the channel): the last 40 helper.log lines with their ages, cmd.txt as it
|
||||
# stands, the task's state and last run result, the helper process (igneum-app.exe --power-helper) with its start time and
|
||||
# its child nvidia-smi processes (a hung nvidia-smi call is the suspected class), every card's sweep_state and tune_step from
|
||||
# api/state (read only: the app's own tune writing the same file is the other suspect), then Stop-ScheduledTask, Start, one
|
||||
# `dev <idx>` + `rgc`, a 45 s wait for the answer, and the tail again. No prompt, no card switch, nothing posted to the app.
|
||||
$ErrorActionPreference = 'Continue'
|
||||
function Stamp { (Get-Date).ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ssZ') }
|
||||
$helperDir = Join-Path $env:LOCALAPPDATA 'igneum\app\sweep'
|
||||
$cmdFile = Join-Path $helperDir 'cmd.txt'; $helperLog = Join-Path $helperDir 'helper.log'
|
||||
$nowUnix = [int][double]::Parse((Get-Date -UFormat %s))
|
||||
"RESULT start $(Stamp)"
|
||||
if (Test-Path $helperLog) { foreach ($l in (Get-Content -LiteralPath $helperLog -Tail 40)) { $age = ''; if ($l -match '^(\d{9,10}) ') { $age = " (age $($nowUnix - [int]$Matches[1]) s)" }; "RESULT helperlog$age $l" } } else { 'RESULT helperlog none' }
|
||||
if (Test-Path $cmdFile) { "RESULT cmdtxt $((Get-Content -LiteralPath $cmdFile -Raw) -replace "`r?`n", ' | ')" } else { 'RESULT cmdtxt none' }
|
||||
$task = Get-ScheduledTask -TaskName 'Igneum Power Helper' -ErrorAction SilentlyContinue
|
||||
if ($task) { $info = Get-ScheduledTaskInfo -TaskName 'Igneum Power Helper' -ErrorAction SilentlyContinue; "RESULT task state=$($task.State) last_run=$($info.LastRunTime) last_result=$($info.LastTaskResult) next_run=$($info.NextRunTime) action=$($task.Actions[0].Execute) $($task.Actions[0].Arguments)" } else { 'RESULT task NOT REGISTERED' }
|
||||
$procs = @(Get-CimInstance Win32_Process -ErrorAction SilentlyContinue | Where-Object { $_.CommandLine -match 'power-helper' -or $_.Name -eq 'nvidia-smi.exe' })
|
||||
foreach ($p in $procs) { $age = ''; try { $age = " age $([int]((Get-Date) - $p.ConvertToDateTime($p.CreationDate)).TotalSeconds) s" } catch { }; "RESULT proc pid=$($p.ProcessId) parent=$($p.ParentProcessId)$age cmd=$($p.CommandLine)" }
|
||||
if ($procs.Count -eq 0) { 'RESULT proc none (no igneum-app --power-helper and no nvidia-smi process)' }
|
||||
$appDir = Join-Path $env:LOCALAPPDATA 'igneum\app'; $u = Join-Path $appDir 'app.url'
|
||||
if (Test-Path $u) { try { $base = (Get-Content -LiteralPath $u -Raw).Trim(); $s = (Invoke-WebRequest -Uri ($base + 'api/state') -UseBasicParsing -TimeoutSec 5).Content | ConvertFrom-Json; "RESULT app version=$($s.version) power_control=$($s.settings.power_control) tuning_off=$($s.settings.tuning_off) climb=$($s.settings.tune_climb)"; foreach ($c in $s.mining.cards) { "RESULT appcard key=$($c.key) enabled=$($c.enabled) sweep_state=$($c.sweep_state) tune_step=$($c.tune_step)/$($c.tune_steps) clock_cap=$($c.clock_cap_mhz) note=$($c.sweep_note)" } } catch { "RESULT app state unreadable: $_" } } else { 'RESULT app no app.url' }
|
||||
$smi = (Get-Command nvidia-smi -ErrorAction SilentlyContinue).Source
|
||||
$idx = $null
|
||||
if ($smi) { $row = @(& $smi --query-gpu=index,name --format=csv,noheader 2>&1 | ForEach-Object { "$_" }) | Where-Object { $_ -match 'RTX 5080' } | Select-Object -First 1; if ($row) { $idx = ($row -split ',')[0].Trim() }; "RESULT gpus $((& $smi --query-gpu=index,name,clocks.sm,clocks.applications.graphics,power.draw --format=csv,noheader 2>&1 | Out-String).Trim() -replace "`r?`n", ' | ')" }
|
||||
if (-not $task) { 'SUMMARY {"job":"pc1-helper-diag","status":"no-task"}'; exit 3 }
|
||||
try { Stop-ScheduledTask -TaskName 'Igneum Power Helper' -ErrorAction Stop; 'RESULT task stopped' } catch { "RESULT task stop failed: $_" }
|
||||
Start-Sleep -Seconds 3
|
||||
$left = @(Get-CimInstance Win32_Process -ErrorAction SilentlyContinue | Where-Object { $_.CommandLine -match 'power-helper' })
|
||||
"RESULT after stop: helper processes left $($left.Count) $(($left | ForEach-Object { $_.ProcessId }) -join ' ')"
|
||||
try { Start-ScheduledTask -TaskName 'Igneum Power Helper' -ErrorAction Stop; 'RESULT task started' } catch { "RESULT task start failed: $_" }
|
||||
Start-Sleep -Seconds 4
|
||||
"RESULT task state now $((Get-ScheduledTask -TaskName 'Igneum Power Helper').State); helper processes $(@(Get-CimInstance Win32_Process -ErrorAction SilentlyContinue | Where-Object { $_.CommandLine -match 'power-helper' }).Count)"
|
||||
$m = 0; foreach ($f in @($cmdFile, $helperLog)) { if (Test-Path $f) { foreach ($l in (Get-Content -LiteralPath $f -ErrorAction SilentlyContinue)) { if ($l -match '(?:^|\s)(\d{1,6}) (?:dev|pl|lgc|rgc|lmc|rmc|nvidia-smi|reregister)\b') { $v = [int]$Matches[1]; if ($v -gt $m) { $m = $v } } } } }
|
||||
$s0 = $m + 100; $s1 = $s0 + 1
|
||||
$mark = 0; if (Test-Path $helperLog) { $mark = @(Get-Content -LiteralPath $helperLog).Count }
|
||||
if ($idx) { Set-Content -LiteralPath $cmdFile -Value "$s0 dev $idx`n$s1 rgc`n" -NoNewline -Encoding ascii; "RESULT sent $(Stamp) '$s0 dev $idx' + '$s1 rgc'" } else { 'RESULT sent nothing (no 5080 index)' }
|
||||
$t = 0; $hit = $null
|
||||
while ($t -lt 45 -and -not $hit -and $idx) { Start-Sleep -Seconds 1; $t++; $new = @(Get-Content -LiteralPath $helperLog -ErrorAction SilentlyContinue); if ($new.Count -gt $mark) { $hit = $new[$mark..($new.Count - 1)] | Where-Object { $_ -match "\b$s1 nvidia-smi " } | Select-Object -First 1 } }
|
||||
if ($hit) { "RESULT answer after $t s: $hit" } else { "RESULT no answer within $t s" }
|
||||
if (Test-Path $helperLog) { foreach ($l in (Get-Content -LiteralPath $helperLog -Tail 6)) { "RESULT helperlog-after $l" } }
|
||||
if (Test-Path $cmdFile) { "RESULT cmdtxt-after $((Get-Content -LiteralPath $cmdFile -Raw) -replace "`r?`n", ' | ')" }
|
||||
if ($hit) { 'SUMMARY {"job":"pc1-helper-diag","status":"answered"}'; exit 0 } else { 'SUMMARY {"job":"pc1-helper-diag","status":"silent"}'; exit 1 }
|
||||
49
tools/ca3-v4-amend/pc1-helper-diag2.ps1
Normal file
49
tools/ca3-v4-amend/pc1-helper-diag2.ps1
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
# PC 1: why does the Power Helper process die right after "helper started" (7 October 2026, 21:3x UTC)? Read only, plus one
|
||||
# 20-second UNELEVATED probe: the task's exe path and the file's mtime, version and sha256 against the running app's exe; the
|
||||
# task's scheduler history (events 100 to 203 of the last hour: start, action start, action end with its exit code); the helper
|
||||
# started by hand as a child of this job (`igneum-app.exe --power-helper`, no rights, so a `dev` line is answered with the
|
||||
# helper's own log line and an nvidia-smi permission text at most, which proves the loop runs) for 20 s, its exit code, and
|
||||
# helper.log's new lines; then the child is ended by its pid. Nothing is set on any card, nothing posted to the app, settings.json
|
||||
# untouched, no prompt. Rows: RESULT lines, a SUMMARY {json}.
|
||||
$ErrorActionPreference = 'Continue'
|
||||
function Stamp { (Get-Date).ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ssZ') }
|
||||
"RESULT start $(Stamp)"
|
||||
$task = Get-ScheduledTask -TaskName 'Igneum Power Helper' -ErrorAction SilentlyContinue
|
||||
if (-not $task) { 'RESULT task NOT REGISTERED'; 'SUMMARY {"job":"pc1-helper-diag2","status":"no-task"}'; exit 3 }
|
||||
$exe = $task.Actions[0].Execute.Trim('"')
|
||||
$info = Get-ScheduledTaskInfo -TaskName 'Igneum Power Helper' -ErrorAction SilentlyContinue
|
||||
"RESULT task state=$($task.State) exe=$exe args=$($task.Actions[0].Arguments) runlevel=$($task.Principal.RunLevel) user=$($task.Principal.UserId) last_run=$($info.LastRunTime) last_result=$($info.LastTaskResult) missed=$($info.NumberOfMissedRuns)"
|
||||
if (Test-Path -LiteralPath $exe) { $f = Get-Item -LiteralPath $exe; "RESULT taskexe mtime=$($f.LastWriteTimeUtc.ToString('yyyy-MM-ddTHH:mm:ssZ')) bytes=$($f.Length) version=$($f.VersionInfo.FileVersion) product=$($f.VersionInfo.ProductVersion) sha256=$((Get-FileHash -Algorithm SHA256 -LiteralPath $exe).Hash.ToLower())" } else { "RESULT taskexe MISSING at $exe" }
|
||||
$app = @(Get-CimInstance Win32_Process -Filter "Name = 'igneum-app.exe'" -ErrorAction SilentlyContinue)
|
||||
foreach ($p in $app) { $path = $p.ExecutablePath; $v = ''; $sha = ''; if ($path -and (Test-Path -LiteralPath $path)) { $v = (Get-Item -LiteralPath $path).VersionInfo.FileVersion; $sha = (Get-FileHash -Algorithm SHA256 -LiteralPath $path).Hash.ToLower() }; "RESULT appproc pid=$($p.ProcessId) started=$($p.ConvertToDateTime($p.CreationDate).ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ssZ')) path=$path version=$v sha256=$sha cmd=$($p.CommandLine)" }
|
||||
try { $ver = (& $exe --version 2>&1 | Out-String).Trim() -replace '\s+', ' '; "RESULT taskexe --version: $ver" } catch { "RESULT taskexe --version failed: $_" }
|
||||
# the scheduler's history for the task (needs the TaskScheduler operational log enabled; absent = says so)
|
||||
try {
|
||||
$since = (Get-Date).AddHours(-2)
|
||||
$ev = @(Get-WinEvent -FilterHashtable @{ LogName = 'Microsoft-Windows-TaskScheduler/Operational'; StartTime = $since } -ErrorAction Stop | Where-Object { $_.Message -match 'Igneum Power Helper' } | Sort-Object TimeCreated | Select-Object -Last 30)
|
||||
if ($ev.Count -eq 0) { 'RESULT taskhistory no events for the task in 2 h (the operational log may be off)' }
|
||||
foreach ($e in $ev) { "RESULT taskhistory $($e.TimeCreated.ToUniversalTime().ToString('HH:mm:ssZ')) id=$($e.Id) $(($e.Message -replace '\s+', ' ').Substring(0, [math]::Min(160, ($e.Message -replace '\s+', ' ').Length)))" }
|
||||
} catch { "RESULT taskhistory unreadable: $(($_ | Out-String).Trim() -replace '\s+', ' ')" }
|
||||
# the application log: a crash of igneum-app.exe in the last 2 h (Windows Error Reporting / Application Error)
|
||||
try {
|
||||
$crash = @(Get-WinEvent -FilterHashtable @{ LogName = 'Application'; StartTime = (Get-Date).AddHours(-2) } -ErrorAction Stop | Where-Object { $_.Message -match 'igneum-app' -and ($_.ProviderName -match 'Application Error|Windows Error Reporting|\.NET Runtime') } | Select-Object -Last 6)
|
||||
if ($crash.Count -eq 0) { 'RESULT crashlog no igneum-app crash events in 2 h' }
|
||||
foreach ($e in $crash) { "RESULT crashlog $($e.TimeCreated.ToUniversalTime().ToString('HH:mm:ssZ')) $($e.ProviderName): $(($e.Message -replace '\s+', ' ').Substring(0, [math]::Min(220, ($e.Message -replace '\s+', ' ').Length)))" }
|
||||
} catch { "RESULT crashlog unreadable: $(($_ | Out-String).Trim() -replace '\s+', ' ')" }
|
||||
# the unelevated probe: the helper loop as this user, 20 s, one dev line
|
||||
$helperDir = Join-Path $env:LOCALAPPDATA 'igneum\app\sweep'; $cmdFile = Join-Path $helperDir 'cmd.txt'; $helperLog = Join-Path $helperDir 'helper.log'
|
||||
$mark = 0; if (Test-Path $helperLog) { $mark = @(Get-Content -LiteralPath $helperLog).Count }
|
||||
$m = 0; foreach ($f in @($cmdFile, $helperLog)) { if (Test-Path $f) { foreach ($l in (Get-Content -LiteralPath $f -ErrorAction SilentlyContinue)) { if ($l -match '(?:^|\s)(\d{1,6}) (?:dev|pl|lgc|rgc|lmc|rmc|nvidia-smi|reregister)\b') { $v = [int]$Matches[1]; if ($v -gt $m) { $m = $v } } } } }
|
||||
$probeOut = Join-Path $env:TEMP 'igneum-helper-probe.out'; $probeErr = Join-Path $env:TEMP 'igneum-helper-probe.err'
|
||||
if (Test-Path $probeOut) { Remove-Item -LiteralPath $probeOut -Force }; if (Test-Path $probeErr) { Remove-Item -LiteralPath $probeErr -Force }
|
||||
$child = $null
|
||||
try { $child = Start-Process -FilePath $exe -ArgumentList '--power-helper' -WindowStyle Hidden -PassThru -RedirectStandardOutput $probeOut -RedirectStandardError $probeErr; "RESULT probe started pid=$($child.Id) $(Stamp)" } catch { "RESULT probe start failed: $_" }
|
||||
Start-Sleep -Seconds 4
|
||||
if ($child) { "RESULT probe alive_after_4s=$(-not $child.HasExited) exit=$(if ($child.HasExited) { $child.ExitCode } else { '' })" }
|
||||
if ($child -and -not $child.HasExited) { $s0 = $m + 100; Set-Content -LiteralPath $cmdFile -Value "$s0 dev 1`n" -NoNewline -Encoding ascii; "RESULT probe sent '$s0 dev 1'"; Start-Sleep -Seconds 6; "RESULT probe alive_after_10s=$(-not $child.HasExited)" }
|
||||
Start-Sleep -Seconds 10
|
||||
if ($child) { "RESULT probe alive_after_20s=$(-not $child.HasExited) exit=$(if ($child.HasExited) { $child.ExitCode } else { '' })"; if (-not $child.HasExited) { try { Stop-Process -Id $child.Id -Force; 'RESULT probe ended by pid' } catch { } } }
|
||||
foreach ($f in @($probeOut, $probeErr)) { if (Test-Path $f) { $t = (Get-Content -LiteralPath $f -Raw -ErrorAction SilentlyContinue); if ($t) { "RESULT probe $(Split-Path -Leaf $f): $(($t -replace '\s+', ' ').Substring(0, [math]::Min(400, ($t -replace '\s+', ' ').Length)))" } } }
|
||||
$new = @(Get-Content -LiteralPath $helperLog -ErrorAction SilentlyContinue); if ($new.Count -gt $mark) { foreach ($l in $new[$mark..($new.Count - 1)]) { "RESULT helperlog-new $l" } } else { 'RESULT helperlog-new none' }
|
||||
'SUMMARY {"job":"pc1-helper-diag2","status":"done"}'
|
||||
exit 0
|
||||
31
tools/ca3-v4-amend/pc1-helper-read.ps1
Normal file
31
tools/ca3-v4-amend/pc1-helper-read.ps1
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
# PC 1, read only (the update-return lane's content, 7 October 2026, 22:3x BST, through the shipper): the Power Helper's state
|
||||
# for its owner. Under the signed-in user, nothing written, nothing set, nothing posted to the app, no prompt. (1) helper.log tail
|
||||
# 60, cmd.txt, helper.alive, the sweep folder listing with LastWriteTime; (2) schtasks /query /v /fo list; (3) every igneum
|
||||
# process with its path, parent and creation time; (4) Application log events 1000/1001/1026 naming igneum; (5) the Task
|
||||
# Scheduler operational events naming the task; (6) --version of the task's exe and of the install folder's exe; (7) the app's
|
||||
# log lines about the helper, power cap, power control and tune since 21:00 UTC. Every line printed as RESULT text.
|
||||
$ErrorActionPreference = 'Continue'
|
||||
function Stamp { (Get-Date).ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ssZ') }
|
||||
function Say([string] $tag, [string] $text) { foreach ($l in ($text -split "`r?`n")) { if ($l.Trim()) { "RESULT $tag $l" } } }
|
||||
"RESULT start $(Stamp)"
|
||||
$sweep = Join-Path $env:LOCALAPPDATA 'igneum\app\sweep'
|
||||
foreach ($f in @('helper.log', 'cmd.txt', 'helper.alive')) { $p = Join-Path $sweep $f; if (Test-Path -LiteralPath $p) { $t = if ($f -eq 'helper.log') { (Get-Content -LiteralPath $p -Tail 60) -join "`n" } else { Get-Content -LiteralPath $p -Raw }; Say "1-$f" $t } else { "RESULT 1-$f absent" } }
|
||||
if (Test-Path -LiteralPath $sweep) { foreach ($i in (Get-ChildItem -LiteralPath $sweep -Force)) { "RESULT 1-dir $($i.Name) bytes=$($i.Length) mtime=$($i.LastWriteTimeUtc.ToString('yyyy-MM-ddTHH:mm:ssZ'))" } } else { 'RESULT 1-dir sweep folder absent' }
|
||||
Say '2-schtasks' ((& schtasks /query /tn "Igneum Power Helper" /v /fo list 2>&1 | Out-String))
|
||||
$taskExe = $null; $tr = (& schtasks /query /tn "Igneum Power Helper" /v /fo list 2>&1 | Out-String) -split "`r?`n" | Where-Object { $_ -match '^Task To Run:' } | Select-Object -First 1
|
||||
if ($tr -and $tr -match '^Task To Run:\s*(.+)$') { $cmd = $Matches[1].Trim(); if ($cmd -match '^"([^"]+)"') { $taskExe = $Matches[1] } elseif ($cmd -match '^(\S+\.exe)') { $taskExe = $Matches[1] } }
|
||||
foreach ($p in (Get-CimInstance Win32_Process -ErrorAction SilentlyContinue | Where-Object { $_.CommandLine -like '*igneum*' })) { $c = ''; try { $c = $p.ConvertToDateTime($p.CreationDate).ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ssZ') } catch { }; "RESULT 3-proc pid=$($p.ProcessId) parent=$($p.ParentProcessId) name=$($p.Name) path=$($p.ExecutablePath) created=$c cmd=$($p.CommandLine)" }
|
||||
try { $ev = @(Get-WinEvent -FilterHashtable @{ LogName = 'Application'; Id = 1000, 1001, 1026 } -MaxEvents 40 -ErrorAction Stop | Where-Object { $_.Message -like '*igneum*' }); if ($ev.Count -eq 0) { 'RESULT 4-apperr none of 1000/1001/1026 name igneum in the last 40' }; foreach ($e in $ev) { "RESULT 4-apperr $($e.TimeCreated.ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ssZ')) id=$($e.Id) $(($e.Message -replace '\s+', ' '))" } } catch { "RESULT 4-apperr unreadable: $(($_ | Out-String).Trim() -replace '\s+', ' ')" }
|
||||
try { $ev = @(Get-WinEvent -LogName 'Microsoft-Windows-TaskScheduler/Operational' -MaxEvents 200 -ErrorAction Stop | Where-Object { $_.Message -like '*Igneum Power Helper*' }); if ($ev.Count -eq 0) { 'RESULT 5-sched no events naming the task in the last 200 (the operational log may be off)' }; foreach ($e in ($ev | Sort-Object TimeCreated)) { "RESULT 5-sched $($e.TimeCreated.ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ssZ')) id=$($e.Id) $(($e.Message -replace '\s+', ' '))" } } catch { "RESULT 5-sched unreadable: $(($_ | Out-String).Trim() -replace '\s+', ' ')" }
|
||||
if ($taskExe -and (Test-Path -LiteralPath $taskExe)) { $f = Get-Item -LiteralPath $taskExe; "RESULT 6-taskexe path=$taskExe mtime=$($f.LastWriteTimeUtc.ToString('yyyy-MM-ddTHH:mm:ssZ')) bytes=$($f.Length) sha256=$((Get-FileHash -Algorithm SHA256 -LiteralPath $taskExe).Hash.ToLower())"; try { "RESULT 6-taskexe --version: $((& $taskExe --version 2>&1 | Out-String).Trim() -replace '\s+', ' ')" } catch { "RESULT 6-taskexe --version failed: $_" } } else { "RESULT 6-taskexe not found (Task To Run: $tr)" }
|
||||
$inst = Join-Path $env:LOCALAPPDATA 'Programs\Igneum Miner\igneum-app.exe'
|
||||
if (Test-Path -LiteralPath $inst) { $f = Get-Item -LiteralPath $inst; "RESULT 6-installexe path=$inst mtime=$($f.LastWriteTimeUtc.ToString('yyyy-MM-ddTHH:mm:ssZ')) bytes=$($f.Length) sha256=$((Get-FileHash -Algorithm SHA256 -LiteralPath $inst).Hash.ToLower())"; try { "RESULT 6-installexe --version: $((& $inst --version 2>&1 | Out-String).Trim() -replace '\s+', ' ')" } catch { "RESULT 6-installexe --version failed: $_" } } else { "RESULT 6-installexe absent at $inst" }
|
||||
$logs = Join-Path $env:LOCALAPPDATA 'igneum\app\logs'
|
||||
$since = (Get-Date).ToUniversalTime().Date.AddHours(21)
|
||||
foreach ($lf in (Get-ChildItem -Path (Join-Path $env:LOCALAPPDATA 'igneum\app') -Recurse -Filter '*.log' -ErrorAction SilentlyContinue | Where-Object { $_.LastWriteTimeUtc -ge $since.AddMinutes(-5) -and $_.Name -notmatch '^helper' } | Sort-Object LastWriteTime -Descending | Select-Object -First 3)) {
|
||||
$hits = @(Select-String -LiteralPath $lf.FullName -Pattern 'helper|power cap|power control|tune|Power Helper' -ErrorAction SilentlyContinue | Select-Object -Last 60)
|
||||
"RESULT 7-applog file=$($lf.FullName) hits=$($hits.Count)"
|
||||
foreach ($h in $hits) { "RESULT 7-applog $(($h.Line -replace '\s+', ' ').Substring(0, [math]::Min(260, ($h.Line -replace '\s+', ' ').Length)))" }
|
||||
}
|
||||
'SUMMARY {"job":"pc1-helper-read","status":"done"}'
|
||||
exit 0
|
||||
47
tools/ca3-v4-amend/pc1-publish-20261007.sh
Executable file
47
tools/ca3-v4-amend/pc1-publish-20261007.sh
Executable file
|
|
@ -0,0 +1,47 @@
|
|||
#!/usr/bin/env bash
|
||||
# The PC 1 queue of 7 October 2026 (Counter ASIC 3.0, the hash lane; main's orders through the coordinator): the exact publish
|
||||
# lines, one job at a time under the PC 1 rule (each job's closing report read before the next; nothing published before the
|
||||
# coordinator relays the shipper's "PC 1 released" line). PC 1 = machine ae432dc7 (RTX 5090, RTX 5080 on the dock, RTX 4070,
|
||||
# RX 9070 XT amd:gfx1201). Every measured card alone through the RUNNER's --cards-off (the app's own card path, restored on any
|
||||
# exit); the other cards mining on; the installed app never quit, paused, resumed or updated; the desk usable.
|
||||
# tools/ca3-v4-amend/pc1-publish-20261007.sh <step> [--deploy] steps: kit family-kit eff-5090 eff-5090-floor eff-5090-floor2 eff-5080 ca4-exe ca4-sparse ca4-mb-exe ca4-mb ca4-packs-kit ca4-packs helper-diag2 v5-kit v5-amd v5-intel amd-g1 family tune-5080 tune-9070
|
||||
# tools/ca3-v4-amend/pc1-publish-20261007.sh read <job id> the closing report (node tools/jobs.mjs <id> --all)
|
||||
# Kits (built 7 October 2026, 17:5x UTC; the exes cross-compiled on build-1 with mingw from this tree):
|
||||
# igneum-ca3-v4-sub3-pc1-kit.zip sha256 1d441f5a075d72b1b902d23941e57b0fa4bee08f91a14b10a7a545aaac58c413 (896,071 bytes, 167 files)
|
||||
# igneum-ca3-pc1-amd-family-kit-20261007.zip sha256 9dd82b11ed08ff9270ac7d59f634eaf99267b1bb48a9ae9fdfc0ce7fe41e63ef (39,399 bytes, 3 files)
|
||||
# The efficiency passes are NOT elevated (the 18:31Z run exit 251: the UAC prompt went unanswered); the clock lock goes through the
|
||||
# installed app's registered Igneum Power Helper task (no prompt); ids -b because a machine runs an id once.
|
||||
# Order (main, 7 October 2026 evening): eff-5090, eff-5080 (the class v4 efficiency pass; its clock grid carries the four Ember
|
||||
# caps, so the owed "5090 clock rows" fall out of it), amd-g1 (G1 + the AMD ladder on the sub-version 3 kit), family (item 6
|
||||
# on AMD), tune-5080 (the full Ember Tune on the 5080), tune-9070 (the 9070 XT tune pass). The dr736 derive job is NOT in this
|
||||
# queue: its packs are not in this tree (the 6 October kit carried them from branch ca3-pc1-amd).
|
||||
set -euo pipefail
|
||||
ROOT=$(cd "$(dirname "$0")/../.." && pwd); cd "$ROOT"
|
||||
KITS="${IGNEUM_PC1_KITS:-${TMPDIR:-/tmp}}"
|
||||
P=packaging/ota/publish-jobs.sh
|
||||
STEP="${1:-}"; shift || true
|
||||
DEPLOY=""; for a in "$@"; do [ "$a" = "--deploy" ] && DEPLOY="--deploy"; done
|
||||
case "$STEP" in
|
||||
kit) $P add --kind fetch --target ae432dc7 --id fetch-ca3-v4-sub3-pc1-20261007 --file "$KITS/igneum-ca3-v4-sub3-pc1-kit.zip" --dir jobs --extract --title "CA3 PC 1: the sub-version 3 kit (worker, 13 packs)" --expires-hours 36 $DEPLOY ;;
|
||||
family-kit) $P add --kind fetch --target ae432dc7 --id fetch-ca3-pc1-amd-family-20261007 --file "$KITS/igneum-ca3-pc1-amd-family-kit-20261007.zip" --dir jobs --extract --title "CA3 PC 1: the family probe kit (7 Oct)" --expires-hours 36 $DEPLOY ;;
|
||||
eff-5090) $P add --kind run --target ae432dc7 --id run-ca3-pc1-v4-eff-5090-20261007-b --cards-off "nvidia:NVIDIA GeForce RTX 5090" --script tools/ca3-v4-amend/pc1-v4-efficiency.ps1 --timeout-minutes 75 --title "CA3 PC 1: class v4 efficiency pass on the RTX 5090 (clock locks, v4 against v3, the card alone)" --expires-hours 36 $DEPLOY ;;
|
||||
eff-5090-floor) $P add --kind run --target ae432dc7 --id run-ca3-pc1-v4-eff-5090-floor-20261007 --cards-off "nvidia:NVIDIA GeForce RTX 5090" --script tools/ca3-v4-amend/pc1-v4-efficiency.ps1 --timeout-minutes 60 --title "CA3 PC 1: class v4 efficiency pass 2 on the RTX 5090 (1,400 MHz down to the driver floor, v4 against v3, the card alone)" --expires-hours 36 $DEPLOY ;;
|
||||
ca4-exe) $P add --kind fetch --target ae432dc7 --id fetch-ca4-sparse-exe-20261007 --file "$KITS/ca4/igneum-worker-cuda-ca4sparse.exe" --dir jobs --title "CA4 research: the SM-sparse worker exe (scratch build, counter-asic-4)" --expires-hours 36 $DEPLOY ;;
|
||||
ca4-sparse) $P add --kind run --target ae432dc7 --id run-ca4-pc1-ca4sparse-5090-20261007 --cards-off "nvidia:NVIDIA GeForce RTX 5090" --script tools/ca3-v4-amend/pc1-v4-efficiency.ps1 --timeout-minutes 70 --title "CA4 research: SM-sparse worker variants on the RTX 5090, unlocked and at the 1,300 MHz knee, v4 and v3 (the card alone)" --expires-hours 36 $DEPLOY ;;
|
||||
eff-5090-floor2) $P add --kind run --target ae432dc7 --id run-ca3-pc1-v4-eff-5090-floor2-20261007 --cards-off "nvidia:NVIDIA GeForce RTX 5090" --script tools/ca3-v4-amend/pc1-v4-efficiency.ps1 --timeout-minutes 45 --title "CA3 PC 1: class v4 efficiency pass 3 on the RTX 5090 (1,100 MHz down to the driver floor, v4 against v3, the card alone)" --expires-hours 36 $DEPLOY ;;
|
||||
eff-5080) $P add --kind run --target ae432dc7 --id run-ca3-pc1-v4-eff-5080-20261007-c --cards-off "nvidia:NVIDIA GeForce RTX 5080" --script tools/ca3-v4-amend/pc1-v4-efficiency.ps1 --timeout-minutes 75 --title "CA3 PC 1: class v4 efficiency pass on the RTX 5080 (clock locks, v4 against v3, the card alone)" --expires-hours 36 $DEPLOY ;;
|
||||
helper-diag2) $P add --kind run --target ae432dc7 --id run-ca3-pc1-helper-diag2-20261007 --script tools/ca3-v4-amend/pc1-helper-diag2.ps1 --timeout-minutes 5 --title "PC 1: Power Helper diagnostic 2 (the task exe against the running app, scheduler history, crash log, a 20 s unelevated helper probe)" --expires-hours 12 $DEPLOY ;;
|
||||
ca4-mb-exe) $P add --kind fetch --target ae432dc7 --id fetch-ca4-microbench-exe-20261007 --file "$KITS/ca4/igneum-worker-cuda-ca4mb.exe" --dir jobs --title "CA4 research: the microbench worker exe (scratch build)" --expires-hours 36 $DEPLOY ;;
|
||||
ca4-mb) $P add --kind run --target ae432dc7 --id run-ca4-pc1-microbench-5090-20261007 --cards-off "nvidia:NVIDIA GeForce RTX 5090" --script tools/ca3-v4-amend/pc1-ca4-microbench.ps1 --timeout-minutes 60 --title "CA4 research: per-block micro-benchmark on the RTX 5090, unlocked and at the 1,300 MHz knee (the card alone)" --expires-hours 36 $DEPLOY ;;
|
||||
ca4-packs-kit) $P add --kind fetch --target ae432dc7 --id fetch-ca4-packs-20261007 --file "$KITS/igneum-ca4-packs-20261007.zip" --dir jobs --extract --title "CA4 research: the two prototype classes' packs (both per-load exports) plus the mx8 control\" --expires-hours 36 $DEPLOY ;;
|
||||
ca4-packs) $P add --kind run --target ae432dc7 --id run-ca4-pc1-packs-5090-20261007 --cards-off "nvidia:NVIDIA GeForce RTX 5090" --script tools/ca3-v4-amend/pc1-ca4-packs.ps1 --timeout-minutes 60 --title "CA4 research: the per-load shadow and the int8 tile prototypes against mx8 and sh256x27 on the RTX 5090 (the card alone)" --expires-hours 36 $DEPLOY ;;
|
||||
v5-kit) $P add --kind fetch --target ae432dc7,1ccfe586 --id fetch-ca3-v5-kit-20261007 --file "$KITS/v5kit/packs-ca3-v5-20261007T221001Z.zip" --dir jobs --extract --title "Class v5 kit (the v5 lane, 7f58af97): packs, workers, SHA256SUMS" --expires-hours 36 $DEPLOY ;;
|
||||
v5-amd) $P add --kind run --target ae432dc7 --id run-ca3-pc1-v5-amd-bench-20261007 --script tools/ca3-v4-amend/pc1-v5-amd-bench-20261007.ps1 --timeout-minutes 12 --title "Class v5 kit fingerprint on the RX 9070 XT (beside the miners; the v5 lane's script)" --expires-hours 36 $DEPLOY ;;
|
||||
v5-intel) $P add --kind run --target 1ccfe586 --id run-ca3-pc2-v5-intel-bench-20261007 --script tools/ca3-v4-amend/pc1-v5-intel-bench-20261007.ps1 --timeout-minutes 12 --title "Class v5 kit fingerprint on PC 2's Arc B580 eGPU (the v5 lane's script, class-v5 a4b08245; beside the miners; only on the shipper's PC 2 clear)" --expires-hours 36 $DEPLOY ;;
|
||||
amd-g1) $P add --kind run --target ae432dc7 --id run-ca3-pc1-v4-sub3-amd-g1-20261007 --cards-off amd:gfx1201 --script tools/ca3-v4-amend/pc1-v4-sub3-amd-g1.ps1 --timeout-minutes 45 --title "CA3 PC 1: G1 on the sub-version 3 kit and the AMD ladder on the 9070 XT (the card alone)" --expires-hours 36 $DEPLOY ;;
|
||||
family) $P add --kind run --target ae432dc7 --id run-ca3-pc1-amd-family-20261007-e --cards-off amd:gfx1201 --script tools/ca3-v4-amend/pc1-amd-family-20261007.ps1 --timeout-minutes 20 --title "CA3 PC 1: item 6 family step costs on the 9070 XT (run e, the card alone)" --expires-hours 36 $DEPLOY ;;
|
||||
tune-5080) $P add --kind run --target ae432dc7 --id run-ca3-pc1-ember-5080-20261007 --script tools/ca3-v4-amend/pc1-ember-card.ps1 --timeout-minutes 35 --title "CA3 PC 1: Ember Tune on the RTX 5080 (the installed app tunes the one card)" --expires-hours 36 $DEPLOY ;;
|
||||
tune-9070) $P add --kind run --target ae432dc7 --id run-ca3-pc1-ember-9070-20261007 --script tools/ca3-v4-amend/pc1-ember-card.ps1 --timeout-minutes 35 --title "CA3 PC 1: Ember Tune pass on the RX 9070 XT (set or measure-only by the probe)" --expires-hours 36 $DEPLOY ;;
|
||||
read) node tools/jobs.mjs "$1" --all ;;
|
||||
*) sed -n 2,12p "$0"; exit 2 ;;
|
||||
esac
|
||||
319
tools/ca3-v4-amend/pc1-v4-efficiency.ps1
Normal file
319
tools/ca3-v4-amend/pc1-v4-efficiency.ps1
Normal file
|
|
@ -0,0 +1,319 @@
|
|||
# Counter ASIC 3.0, PC 1: the class v4 efficiency pass (7 October 2026, main's question "profitable mining is all about
|
||||
# efficiency, is there a solution?"). Thesis under test: the latency-shadow work hides in the memory wait, so the core clock
|
||||
# (and with it the voltage along the driver's V/F curve) can drop until the work just fits the wait with no rate loss, and the
|
||||
# 5090's +80 W for class v4 at the unlocked 2,850 MHz mostly comes back. One job per card (the job id names the card: an id
|
||||
# carrying "5080" measures the RTX 5080, any other the RTX 5090), the card ALONE under the RUNNER's --cards-off (the app's own
|
||||
# card path, restored on any exit; the other cards keep mining), ELEVATED so nvidia-smi -lgc needs no prompt, no restart, the
|
||||
# desk usable. Per step: nvidia-smi -lgc <c>,<c> on the card, then the INSTALLED igneum-worker-cuda.exe --bench on the class v4
|
||||
# pack (v4-devnet-epoch0, the sub-version 3 kit) and on the class v3 control (mx8-devnet-epoch0), about 60 s each, the 2^24
|
||||
# fingerprint checked against the Mac's on every step (bit-exactness under the lock), while nvidia-smi samples power.draw,
|
||||
# clocks.sm, clocks.mem and the throttle reasons at 1 Hz (a child by pid, ended in finally). Steps: unlocked, then 2,850,
|
||||
# 2,781, 2,700, 2,550, 2,472, 2,400, 2,250, 2,163, 2,100, 1,950, 1,854, 1,800, 1,650, 1,500, 1,400 MHz (the four Ember caps
|
||||
# 2,781 / 2,472 / 2,163 / 1,854 are on the grid, so the owed "5090 clock rows" of counter-asic-3-status.md section 7 fall out of
|
||||
# this pass), then unlocked again as the drift check. The voltage lever: nvidia-smi exposes NO voltage offset (that is NVAPI,
|
||||
# Afterburner's lever); the clock lock moves the card down the driver's V/F curve, so the voltage falls with the lock and that
|
||||
# is the lever this pass has; stated on a RESULT line. Lines: `RESULT STEP card= pack= lock= sm_mhz= watts= mhs= mh_per_w=
|
||||
# fingerprint= match=`, one per step and pack; `RESULT TABLE` lines at the end (v3 best against v4 best, the recovered watts,
|
||||
# the rate lost); a `SUMMARY {json}` line. Never quits, pauses, resumes or updates the installed app; never posts to
|
||||
# /api/cards; never writes settings.json; -rgc in finally. Not elevated: the lock is refused, the unlocked rows are
|
||||
# measured and every lock row says why. The lock path when NOT elevated (the 18:31Z run: the UAC prompt went unanswered, exit 251):
|
||||
# the installed app's registered `Igneum Power Helper` task (app/igneum-app/src/powertask.rs, one approval ever): the script
|
||||
# starts it (Start-ScheduledTask by the owning user, no prompt) and talks to it through %LOCALAPPDATA%\igneum\app\sweep\cmd.txt
|
||||
# with the fixed verbs `<seq> dev <idx>`, `<seq> lgc <MHz>` (the helper runs nvidia-smi -lgc 0,<MHz>: a cap, which under load
|
||||
# is the lock), `<seq> rgc`, and reads the result from helper.log beside it; the same channel the app's own Ember tune uses,
|
||||
# so no new rights and no prompt. Kit: fetch job fetch-ca3-v4-sub3-pc1-20261007 (tools/ca3-v4-amend/pc1-publish-20261007.sh).
|
||||
$ErrorActionPreference = 'Continue'
|
||||
$jobName = 'pc1-v4-efficiency'
|
||||
$kitId = 'fetch-ca3-v4-sub3-pc1-20261007'
|
||||
$started = Get-Date
|
||||
$deadline = $started.AddMinutes(66)
|
||||
function Stamp { (Get-Date).ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ssZ') }
|
||||
function Summary([string] $status, [hashtable] $extra) {
|
||||
$o = [ordered]@{ job = $jobName; status = $status; duration_s = [int]((Get-Date) - $started).TotalSeconds; finished_at = (Stamp) }
|
||||
foreach ($k in $extra.Keys) { $o[$k] = $extra[$k] }
|
||||
'SUMMARY ' + ($o | ConvertTo-Json -Compress -Depth 4)
|
||||
}
|
||||
$jobDir = $env:IGNEUM_JOB_DIR; if (-not $jobDir) { $jobDir = Join-Path $env:TEMP 'igneum-ca3-pc1-eff' }
|
||||
New-Item -ItemType Directory -Force -Path $jobDir | Out-Null
|
||||
$jobId = Split-Path -Leaf $jobDir
|
||||
$cardName = if ($jobId -match '5080') { 'NVIDIA GeForce RTX 5080' } else { 'NVIDIA GeForce RTX 5090' }
|
||||
$expected = @{ 'mx8-devnet-epoch0' = '90f794dd556f7a3b'; 'v4-devnet-epoch0' = 'e370fb2080b7dbb1' }
|
||||
$packOrder = @('v4-devnet-epoch0', 'mx8-devnet-epoch0')
|
||||
# the grid by the job id: an id carrying "floor" runs the second pass, 1,400 MHz down to the driver's floor in 100 MHz steps (the
|
||||
# first pass found the best MH/W at its lowest lock, 1,400, so the knee sits below it); a lock the driver clamps shows in sm_mhz
|
||||
# the counter-asic-4 research mode (an id carrying "ca4sparse"): the research lane's SM-sparse worker variants (proto-cuda/nvrtc/worker.cpp on
|
||||
# branch counter-asic-4: `--variant sp<N>-w<W>` runs the pack's bound kernel as a persistent wrapper of N blocks of W warps, about N SMs busy,
|
||||
# the rest idle) on both packs at the unlocked clock and at the 1,300 MHz knee, 250 batches each, beside the base shape; the exe comes from
|
||||
# its own fetch job (fetch-ca4-sparse-exe-20261007, the scratch exe igneum-worker-cuda-ca4sparse.exe, sha256 ee8d0e70...); the rows carry
|
||||
# label=ca4-research and the worker's race line verbatim; nothing in any pack changes, the fingerprint is checked on every row
|
||||
$ca4 = $jobId -match 'ca4sparse'
|
||||
$variants = if ($ca4) { @('base', 'sp170-w32', 'sp85-w32', 'sp43-w32', 'sp21-w32', 'sp11-w32') } else { @('base') }
|
||||
$locks = if ($ca4) { @(1300) } elseif ($jobId -match '5080') { @(2850, 2781, 2700, 2550, 2472, 2400, 2250, 2163, 2100, 1950, 1854, 1800, 1650, 1500, 1400, 1300, 1200, 1100, 1000, 900, 800, 700, 600, 500, 400, 300) } elseif ($jobId -match 'floor2') { @(1100, 1000, 900, 800, 700, 600, 500, 400, 300) } elseif ($jobId -match 'floor') { @(1400, 1300, 1200, 1100, 1000, 900, 800, 700, 600, 500, 400, 300) } else { @(2850, 2781, 2700, 2550, 2472, 2400, 2250, 2163, 2100, 1950, 1854, 1800, 1650, 1500, 1400) }
|
||||
$stepSeconds = 60
|
||||
"RESULT start $(Stamp) job=$jobName job_id=$jobId card=$cardName grid=[$($locks -join ' ')] machine=$env:COMPUTERNAME app_version=$env:IGNEUM_APP_VERSION"
|
||||
'RESULT lever voltage: nvidia-smi exposes no voltage offset (NVAPI only); the clock lock (-lgc) moves the card along the driver V/F curve, so the voltage falls with the lock; the power cap (-pl) is the other driver lever and is not moved here'
|
||||
$elevated = $false
|
||||
try { $elevated = ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) } catch { }
|
||||
"RESULT elevated=$elevated"
|
||||
|
||||
# ---- the kit (the wiped-jobs-folder rule: tested before use) ----
|
||||
$jobs = Split-Path $jobDir
|
||||
$kit = Join-Path $jobs $kitId
|
||||
$packs = Join-Path $kit 'packs'
|
||||
if (-not (Test-Path $packs)) { "RESULT error packs missing at $packs (the fetch job $kitId runs first; republish it after any app update)"; Summary 'failed' @{ error = 'kit missing' }; exit 2 }
|
||||
foreach ($pk in $packOrder) { $d = Join-Path $packs $pk; if (-not (Test-Path $d)) { "RESULT error pack missing at $d"; Summary 'failed' @{ error = "pack $pk missing" }; exit 2 }; foreach ($f in (Get-ChildItem $d -File | Sort-Object Name)) { "RESULT kitfile $pk/$($f.Name) sha256 $((Get-FileHash -Algorithm SHA256 $f.FullName).Hash.ToLower())" } }
|
||||
|
||||
# ---- the installed worker (NVRTC, the pack's own kernel text) ----
|
||||
$inst = @("$env:LOCALAPPDATA\Programs\Igneum Miner", "$env:ProgramFiles\Igneum Miner") | Where-Object { Test-Path (Join-Path $_ 'igneum-worker-cuda.exe') } | Select-Object -First 1
|
||||
if (-not $inst) { 'RESULT error no installed igneum-worker-cuda.exe'; Summary 'failed' @{ error = 'no worker' }; exit 2 }
|
||||
$exe = Join-Path $inst 'igneum-worker-cuda.exe'
|
||||
if ($ca4) {
|
||||
$exe = Join-Path (Join-Path $jobs 'fetch-ca4-sparse-exe-20261007') 'igneum-worker-cuda-ca4sparse.exe'
|
||||
if (-not (Test-Path $exe)) { "RESULT error the research worker is missing at $exe (the fetch job fetch-ca4-sparse-exe-20261007 runs first)"; Summary 'failed' @{ error = 'research worker missing' }; exit 2 }
|
||||
}
|
||||
"RESULT worker $exe sha256 $((Get-FileHash -Algorithm SHA256 $exe).Hash.ToLower()) bytes $((Get-Item $exe).Length)"
|
||||
$appExe = Join-Path $inst 'igneum-app.exe'
|
||||
if (Test-Path $appExe) { try { "RESULT app-version $((& $appExe --version 2>&1 | Out-String).Trim())" } catch { } }
|
||||
|
||||
# ---- nvidia-smi and the card ----
|
||||
$smi = $null
|
||||
$c = Get-Command nvidia-smi -ErrorAction SilentlyContinue; if ($c) { $smi = $c.Source }
|
||||
if (-not $smi) { foreach ($p in @((Join-Path $env:ProgramFiles 'NVIDIA Corporation\NVSMI\nvidia-smi.exe'), (Join-Path $env:SystemRoot 'System32\nvidia-smi.exe'))) { if (Test-Path $p) { $smi = $p; break } } }
|
||||
if (-not $smi) { 'RESULT error nvidia-smi not found'; Summary 'failed' @{ error = 'no nvidia-smi' }; exit 2 }
|
||||
$gpus = @(& $smi --query-gpu=index,name,uuid,pci.bus_id,power.limit,power.max_limit,clocks.max.sm,driver_version --format=csv,noheader 2>&1 | ForEach-Object { "$_" })
|
||||
$gpus | ForEach-Object { "RESULT gpu $_" }
|
||||
$row = $gpus | Where-Object { $_ -match [regex]::Escape($cardName) } | Select-Object -First 1
|
||||
if (-not $row) { "RESULT error card '$cardName' is not in nvidia-smi's list"; Summary 'failed' @{ error = 'card missing' }; exit 2 }
|
||||
$fld = $row -split ',\s*'
|
||||
$idx = $fld[0].Trim(); $uuid = $fld[2].Trim(); $bus = $fld[3].Trim(); $maxSm = $fld[6].Trim()
|
||||
"RESULT card index=$idx uuid=$uuid bus=$bus max_sm_mhz=$maxSm"
|
||||
|
||||
# ---- the card quiet (the RUNNER's --cards-off took): no igneum compute app on this bus id ----
|
||||
function BusApps { @(& $smi --query-compute-apps=gpu_bus_id,pid,process_name --format=csv,noheader 2>$null | ForEach-Object { "$_" } | Where-Object { $_ -match [regex]::Escape($bus) -and $_ -match 'igneum' }) }
|
||||
$t = 0; $apps = @(BusApps)
|
||||
while ($t -lt 150 -and $apps.Count -gt 0) { Start-Sleep -Seconds 5; $t += 5; $apps = @(BusApps) }
|
||||
$alone = ($apps.Count -eq 0)
|
||||
if ($alone) { "RESULT card $(Stamp) quiet after $t s: no igneum compute app on $bus (the runner's --cards-off took)" }
|
||||
else { "RESULT card $(Stamp) UNCONFIRMED after $t s: $($apps -join '; ') (was the job published with --cards-off?); the rows below are LOADED-card figures" }
|
||||
|
||||
# ---- the sampler: nvidia-smi at 1 Hz on this card, a child by pid, its file read per step ----
|
||||
$samples = Join-Path $jobDir 'nv-samples.csv'
|
||||
if (Test-Path $samples) { Remove-Item -LiteralPath $samples -Force }
|
||||
$sampler = $null
|
||||
$rows = @()
|
||||
$lockState = 'unlocked'
|
||||
function ReadWindow([datetime] $t0, [datetime] $t1) {
|
||||
$sel = @()
|
||||
if (Test-Path $samples) {
|
||||
foreach ($l in (Get-Content -LiteralPath $samples -ErrorAction SilentlyContinue)) {
|
||||
$p = $l -split ',\s*'
|
||||
if ($p.Count -lt 5) { continue }
|
||||
$ts = $null
|
||||
try { $ts = [datetime]::ParseExact($p[0].Trim(), 'yyyy/MM/dd HH:mm:ss.fff', [cultureinfo]::InvariantCulture) } catch { continue }
|
||||
if ($ts -ge $t0 -and $ts -le $t1) { $sel += ,$p }
|
||||
}
|
||||
}
|
||||
return ,$sel
|
||||
}
|
||||
function Mean($vals) { $v = @($vals | ForEach-Object { [double]$_ }); if ($v.Count -eq 0) { return $null }; return [math]::Round((($v | Measure-Object -Sum).Sum / $v.Count), 1) }
|
||||
# ---- the Power Helper task (the lock path without a prompt) ----
|
||||
$helperDir = Join-Path $env:LOCALAPPDATA 'igneum\app\sweep'
|
||||
$cmdFile = Join-Path $helperDir 'cmd.txt'
|
||||
$helperLog = Join-Path $helperDir 'helper.log'
|
||||
# the 0.3.20 helper also skips the first N lines of cmd.txt, N = the line count when it started (fixed in 0.3.21, a64c193f): four
|
||||
# leading `dev` padding lines with climbing numbers absorb that skip (the engine writes 4-line files, so N is at most 4), the real
|
||||
# dev and the command stand after them; never pad after the command (update-return lane, 7 October 2026, 22:00Z)
|
||||
# the helper skips any sequence number at or under the last one it ran (powertask.rs: `seq <= last_seq`), and the installed app's
|
||||
# own Ember tune writes the same file with its own rising numbers while it tunes another card (the floor pass of 20:21Z: the app
|
||||
# tuned the 5080 at seqs 423596 to 423598 while this script wrote 419500 to 419527, so every lock from 1,000 MHz down and the
|
||||
# closing reset were skipped as stale and the card stayed locked); the base is therefore the maximum over cmd.txt AND helper.log,
|
||||
# re-read after every timeout
|
||||
function HelperSeqBase() {
|
||||
$m = 0
|
||||
foreach ($f in @($cmdFile, $helperLog)) { if (Test-Path $f) { foreach ($l in (Get-Content -LiteralPath $f -ErrorAction SilentlyContinue)) { if ($l -match '(?:^|\s)(\d{1,6}) (?:dev|pl|lgc|rgc|lmc|rmc|nvidia-smi|reregister)\b') { $v = [int]$Matches[1]; if ($v -gt $m) { $m = $v } } } } }
|
||||
return $m + 100
|
||||
}
|
||||
$viaHelper = $false
|
||||
$seq = 0
|
||||
if (-not $elevated) {
|
||||
$task = Get-ScheduledTask -TaskName 'Igneum Power Helper' -ErrorAction SilentlyContinue
|
||||
if ($task) {
|
||||
try {
|
||||
$seq = HelperSeqBase
|
||||
Start-ScheduledTask -TaskName 'Igneum Power Helper' -ErrorAction Stop
|
||||
Start-Sleep -Seconds 3
|
||||
$viaHelper = $true
|
||||
"RESULT helper task 'Igneum Power Helper' state=$((Get-ScheduledTask -TaskName 'Igneum Power Helper').State) started; commands through $cmdFile from seq $seq; results from $helperLog"
|
||||
} catch { "RESULT helper task present but Start-ScheduledTask failed: $_" }
|
||||
} else { 'RESULT helper task NOT REGISTERED (Power control off in the app): no lock path without a prompt' }
|
||||
}
|
||||
function HelperSend([string] $verb) {
|
||||
$script:seq += 2
|
||||
$s0 = $script:seq; $s1 = $script:seq + 1; $script:seq = $s1
|
||||
$mark = 0; if (Test-Path $helperLog) { $mark = @(Get-Content -LiteralPath $helperLog -ErrorAction SilentlyContinue).Count }
|
||||
$pad = ((0..3) | ForEach-Object { "$($s0 - 8 + 2 * $_) dev $idx" }) -join "`n"; Set-Content -LiteralPath $cmdFile -Value "$pad`n$s0 dev $idx`n$s1 $verb`n" -NoNewline -Encoding ascii
|
||||
$t = 0
|
||||
while ($t -lt 30) {
|
||||
Start-Sleep -Seconds 1; $t++
|
||||
$new = @(Get-Content -LiteralPath $helperLog -ErrorAction SilentlyContinue)
|
||||
if ($new.Count -gt $mark) {
|
||||
$hit = $new[$mark..($new.Count - 1)] | Where-Object { $_ -match "\b$s1 nvidia-smi " } | Select-Object -First 1
|
||||
if ($hit) { return ($hit -replace '^\d+\s+', '') }
|
||||
}
|
||||
}
|
||||
# a timeout: another writer may have raised the helper's sequence past ours; re-base once and send again
|
||||
$base = HelperSeqBase
|
||||
if ($base -gt $script:seq) {
|
||||
"RESULT helper seq re-based from $script:seq to $base (another writer, the app's own tune, raised the helper's sequence)"
|
||||
$script:seq = $base
|
||||
$s0 = $script:seq; $s1 = $script:seq + 1; $script:seq = $s1
|
||||
$mark = 0; if (Test-Path $helperLog) { $mark = @(Get-Content -LiteralPath $helperLog -ErrorAction SilentlyContinue).Count }
|
||||
$pad = ((0..3) | ForEach-Object { "$($s0 - 8 + 2 * $_) dev $idx" }) -join "`n"; Set-Content -LiteralPath $cmdFile -Value "$pad`n$s0 dev $idx`n$s1 $verb`n" -NoNewline -Encoding ascii
|
||||
$t = 0
|
||||
while ($t -lt 30) {
|
||||
Start-Sleep -Seconds 1; $t++
|
||||
$new = @(Get-Content -LiteralPath $helperLog -ErrorAction SilentlyContinue)
|
||||
if ($new.Count -gt $mark) { $hit = $new[$mark..($new.Count - 1)] | Where-Object { $_ -match "\b$s1 nvidia-smi " } | Select-Object -First 1; if ($hit) { return ($hit -replace '^\d+\s+', '') } }
|
||||
}
|
||||
}
|
||||
return "no helper.log line for seq $s1 within 30 s (is the task running?)"
|
||||
}
|
||||
function SetLock([int] $mhz) {
|
||||
if ($elevated) {
|
||||
$out = (& $smi -i $idx -lgc "$mhz,$mhz" 2>&1 | Out-String).Trim() -replace '\s+', ' '
|
||||
if ($out -match 'not have permission|No permission|Insufficient|not supported|Unable|ERROR') { return "refused: $out" }
|
||||
return "ok (direct): $out"
|
||||
}
|
||||
if ($viaHelper) {
|
||||
$out = HelperSend "lgc $mhz"
|
||||
if ($out -match 'not have permission|No permission|Insufficient|not supported|Unable|ERROR|no helper.log') { return "refused: $out" }
|
||||
return "ok (helper, -lgc 0,$mhz): $out"
|
||||
}
|
||||
return 'refused: not elevated and no helper task'
|
||||
}
|
||||
function RestartHelper() {
|
||||
# the floor pass of 20:21Z lost the helper at its 1,000 MHz step (no helper.log answer for that and every later command, the
|
||||
# lock most likely hanging inside the helper's nvidia-smi call) and its closing reset never ran: before a reset the task is
|
||||
# stopped and started again (the owning user may do both without a prompt), so a stuck helper never holds the card locked
|
||||
try { Stop-ScheduledTask -TaskName 'Igneum Power Helper' -ErrorAction SilentlyContinue } catch { }
|
||||
Start-Sleep -Seconds 3
|
||||
try { Start-ScheduledTask -TaskName 'Igneum Power Helper' -ErrorAction Stop; Start-Sleep -Seconds 3; return $true } catch { "RESULT helper restart failed: $_"; return $false }
|
||||
}
|
||||
function AppClock() { $a = @(& $smi -i $idx --query-gpu=clocks.applications.graphics --format=csv,noheader 2>&1 | ForEach-Object { "$_" }) | Select-Object -First 1; return "$a" }
|
||||
function ResetClocks() {
|
||||
if ($elevated) { return ((& $smi -i $idx -rgc 2>&1 | Out-String).Trim() -replace '\s+', ' ') }
|
||||
if ($viaHelper) {
|
||||
$out = HelperSend 'rgc'
|
||||
if ($out -match 'no helper.log') { if (RestartHelper) { $out = "after a task restart: $(HelperSend 'rgc')" } }
|
||||
return "$out ; applications clock now: $(AppClock)"
|
||||
}
|
||||
return 'not attempted (not elevated, no helper)'
|
||||
}
|
||||
function Bench([string] $pk, [int] $n, [string] $variant = 'base') {
|
||||
$d = Join-Path $packs $pk
|
||||
$env:CUDA_VISIBLE_DEVICES = $uuid
|
||||
$t0 = Get-Date
|
||||
$extra = @(); if ($variant -ne 'base') { $extra = @('--block-warps', '32', '--variant', $variant) } else { $extra = @('--block-warps', '1') }
|
||||
$lines = @(& $exe --bench --pack $d --batches $n --batch-log2 24 @extra --device 0 2>&1 | ForEach-Object { "$_" })
|
||||
$race = (($lines | Where-Object { $_ -match 'race' }) -join ' / ') -replace '\s+', ' '
|
||||
$rc = $LASTEXITCODE
|
||||
$t1 = Get-Date
|
||||
$res = $lines | Where-Object { $_ -match '^RESULT pack=' } | Select-Object -First 1
|
||||
$fp = ''; $mhs = $null; $check = ''; $dev = ''
|
||||
if ($res -and $res -match 'device=(.*?) arch=') { $dev = $Matches[1] }
|
||||
if ($res -and $res -match 'fingerprint=([0-9a-f]{16})') { $fp = $Matches[1] }
|
||||
if ($res -and $res -match 'mhs=([\d.]+)') { $mhs = [double]$Matches[1] }
|
||||
if ($res -and $res -match 'check=(\S+)') { $check = $Matches[1] }
|
||||
$err = ''
|
||||
if (-not $res -or $rc -ne 0) { $e = ($lines | Where-Object { $_ -match 'FAIL|error|MISMATCH' } | Select-Object -First 1); $err = if ($e) { ($e -replace '\s+', ' ') } else { "exit $rc, no RESULT line" } }
|
||||
return [pscustomobject]@{ pack = $pk; t0 = $t0; t1 = $t1; rc = $rc; fp = $fp; mhs = $mhs; check = $check; err = $err; res = $res; batches = $n; dev = $dev; variant = $variant; race = $race }
|
||||
}
|
||||
function Step([string] $label, [string] $lockNote) {
|
||||
foreach ($pk in $packOrder) { foreach ($variant in $variants) {
|
||||
if ((Get-Date) -gt $deadline) { "RESULT STEP card=$cardName pack=$pk lock=$label variant=$variant error=budget spent"; continue }
|
||||
$n = if ($ca4) { 250 } else { $nb[$pk] }
|
||||
$b = Bench $pk $n $variant
|
||||
if ($b.err) { "RESULT STEP card=$cardName pack=$pk lock=$label variant=$variant error=$($b.err) lock_note=$lockNote race=[$($b.race)]"; continue }
|
||||
$w = ReadWindow $b.t0.AddSeconds(8) $b.t1
|
||||
$watts = Mean ($w | ForEach-Object { $_[1] })
|
||||
$sm = Mean ($w | ForEach-Object { $_[2] })
|
||||
$mem = Mean ($w | ForEach-Object { $_[3] })
|
||||
$tmax = ($w | ForEach-Object { [int]$_[4] } | Measure-Object -Maximum).Maximum
|
||||
$thr = ($w | ForEach-Object { $_[5].Trim() } | Group-Object | Sort-Object Count -Descending | Select-Object -First 1).Name
|
||||
$wInst = Mean ($w | Where-Object { $_.Count -ge 8 -and $_[6] -match '^\s*[\d.]+\s*$' } | ForEach-Object { $_[6] })
|
||||
$wAvg = Mean ($w | Where-Object { $_.Count -ge 8 -and $_[7] -match '^\s*[\d.]+\s*$' } | ForEach-Object { $_[7] })
|
||||
$eff = if ($watts -and $watts -gt 0) { [math]::Round($b.mhs / $watts, 4) } else { $null }
|
||||
$want = $expected[$pk]
|
||||
$match = if ($want -and $b.fp -eq $want) { 'yes' } elseif ($want) { 'no' } else { 'unknown' }
|
||||
$wall = [int]($b.t1 - $b.t0).TotalSeconds
|
||||
$lab = if ($ca4) { 'ca4-research' } else { 'efficiency' }
|
||||
"RESULT STEP card=$cardName pack=$pk lock=$label variant=$variant label=$lab sm_mhz=$sm mem_mhz=$mem watts=$watts watts_instant=$wInst watts_average=$wAvg temp_max=$tmax throttle=$thr mhs=$($b.mhs) mh_per_w=$eff fingerprint=$($b.fp) match=$match expected=$want check=$($b.check) batches=$n wall_s=$wall samples=$($w.Count) device=$($b.dev) lock_note=$lockNote alone=$alone race=[$($b.race)]"
|
||||
if ($variant -eq 'base') { $script:rows += [pscustomobject]@{ pack = $pk; lock = $label; sm = $sm; watts = $watts; mhs = $b.mhs; eff = $eff; match = $match } }
|
||||
} }
|
||||
}
|
||||
try {
|
||||
$sampler = Start-Process -FilePath $smi -ArgumentList @('-i', $idx, '--query-gpu=timestamp,power.draw,clocks.sm,clocks.mem,temperature.gpu,clocks_throttle_reasons.active,power.draw.instant,power.draw.average', '--format=csv,noheader,nounits', '-lms', '1000') -RedirectStandardOutput $samples -WindowStyle Hidden -PassThru
|
||||
Start-Sleep -Seconds 6
|
||||
$first = @(Get-Content -LiteralPath $samples -ErrorAction SilentlyContinue | Select-Object -First 2)
|
||||
"RESULT sampler pid=$($sampler.Id) first=[$($first -join ' | ')]"
|
||||
if ($first.Count -eq 0) { 'RESULT sampler error=no samples in 6 s; watts will read empty on every row' }
|
||||
# the unlocked start: -rgc so the baseline is the driver's own choice, then the batch counts from a short probe per pack
|
||||
$r0 = ResetClocks
|
||||
"RESULT unlock start $r0"
|
||||
$nb = @{}
|
||||
foreach ($pk in $packOrder) {
|
||||
$p = Bench $pk 5
|
||||
if ($p.err) { "RESULT probe pack=$pk error=$($p.err)"; $nb[$pk] = 40 } else {
|
||||
$n = [int][math]::Ceiling($stepSeconds * $p.mhs * 1e6 / 16777216.0)
|
||||
$nb[$pk] = [math]::Max(20, [math]::Min(2000, $n))
|
||||
"RESULT probe pack=$pk mhs=$($p.mhs) fingerprint=$($p.fp) batches_for_${stepSeconds}s=$($nb[$pk])"
|
||||
}
|
||||
}
|
||||
Step 'unlocked' 'driver default (-rgc)'
|
||||
if (-not $elevated -and -not $viaHelper) {
|
||||
'RESULT locks skipped: the job is not elevated and the Power Helper task is not registered; the unlocked rows above are the measurement'
|
||||
foreach ($c in $locks) { foreach ($pk in $packOrder) { "RESULT STEP card=$cardName pack=$pk lock=$c error=no lock path (not elevated, no helper task)" } }
|
||||
} else {
|
||||
foreach ($c in $locks) {
|
||||
if ((Get-Date) -gt $deadline) { "RESULT STEP card=$cardName pack=* lock=$c error=budget spent"; continue }
|
||||
$note = SetLock $c
|
||||
$lockState = "locked $c"
|
||||
if ($note -match 'no helper.log') { foreach ($pk in $packOrder) { "RESULT STEP card=$cardName pack=$pk lock=$c error=lock $note" }; "RESULT locks stopped at $c MHz: the helper gave no answer (a lock under the driver's floor hangs its nvidia-smi call); the remaining locks are skipped and the task is restarted for the reset"; RestartHelper | Out-Null; break }
|
||||
if ($note -match '^refused') { foreach ($pk in $packOrder) { "RESULT STEP card=$cardName pack=$pk lock=$c error=lock $note" }; if ($c -eq $locks[0]) { 'RESULT locks refused on the first step: the remaining locks are skipped'; break } else { continue } }
|
||||
Start-Sleep -Seconds 3
|
||||
Step "$c" $note
|
||||
}
|
||||
$r1 = ResetClocks
|
||||
$lockState = 'unlocked'
|
||||
"RESULT unlock end $r1"
|
||||
Start-Sleep -Seconds 3
|
||||
Step 'unlocked-end' 'driver default (-rgc), the drift check'
|
||||
}
|
||||
# ---- the table ----
|
||||
$tab = @{}
|
||||
foreach ($pk in $packOrder) {
|
||||
$mine = @($rows | Where-Object { $_.pack -eq $pk -and $_.mhs })
|
||||
$un = $mine | Where-Object { $_.lock -eq 'unlocked' } | Select-Object -First 1
|
||||
$withW = @($mine | Where-Object { $_.eff })
|
||||
$best = $withW | Sort-Object eff -Descending | Select-Object -First 1
|
||||
$tab[$pk] = @{ un = $un; best = $best }
|
||||
if ($un -and $best) {
|
||||
$rec = [math]::Round($un.watts - $best.watts, 1)
|
||||
$lost = if ($un.mhs -gt 0) { [math]::Round(100.0 * ($un.mhs - $best.mhs) / $un.mhs, 2) } else { $null }
|
||||
"RESULT TABLE card=$cardName pack=$pk unlocked_sm_mhz=$($un.sm) unlocked_mhs=$($un.mhs) unlocked_w=$($un.watts) unlocked_mh_per_w=$($un.eff) best_lock=$($best.lock) best_sm_mhz=$($best.sm) best_mhs=$($best.mhs) best_w=$($best.watts) best_mh_per_w=$($best.eff) recovered_w=$rec rate_lost_pct=$lost fingerprints_all_match=$((@($mine | Where-Object { $_.match -ne 'yes' })).Count -eq 0)"
|
||||
} else { "RESULT TABLE card=$cardName pack=$pk error=no unlocked row or no row with watts" }
|
||||
}
|
||||
$u4 = $tab['v4-devnet-epoch0'].un; $u3 = $tab['mx8-devnet-epoch0'].un; $b4 = $tab['v4-devnet-epoch0'].best; $b3 = $tab['mx8-devnet-epoch0'].best
|
||||
if ($u4 -and $u3 -and $b4 -and $b3) {
|
||||
"RESULT TABLE card=$cardName v3_vs_v4 v4_extra_w_unlocked=$([math]::Round($u4.watts - $u3.watts, 1)) v4_extra_w_at_best=$([math]::Round($b4.watts - $b3.watts, 1)) v4_rate_vs_v3_unlocked_pct=$([math]::Round(100.0 * ($u4.mhs - $u3.mhs) / $u3.mhs, 2)) v4_rate_vs_v3_at_best_pct=$([math]::Round(100.0 * ($b4.mhs - $b3.mhs) / $b3.mhs, 2)) v4_best_lock=$($b4.lock) v3_best_lock=$($b3.lock)"
|
||||
}
|
||||
Summary 'done' @{ card = $cardName; elevated = $elevated; via_helper = $viaHelper; alone = $alone; steps = $rows.Count; mismatches = (@($rows | Where-Object { $_.match -eq 'no' })).Count }
|
||||
exit 0
|
||||
} finally {
|
||||
try {
|
||||
$r = ResetClocks; "RESULT restore clocks reset: $r (was $lockState)"
|
||||
if ($viaHelper -and (AppClock) -match '^\d+ MHz' -and (AppClock) -notmatch '^(0|\[N/A\]|N/A)') { if (RestartHelper) { $r2 = HelperSend 'rgc'; "RESULT restore second reset after a task restart: $r2 ; applications clock now: $(AppClock)" } }
|
||||
} catch { "RESULT restore clocks reset failed: $_" }
|
||||
if ($sampler -and -not $sampler.HasExited) { try { Stop-Process -Id $sampler.Id -Force -ErrorAction SilentlyContinue; "RESULT restore sampler pid $($sampler.Id) stopped" } catch { } }
|
||||
"RESULT gpus_after $(Stamp) $((& $smi --query-gpu=index,name,power.draw,clocks.sm,clocks.applications.graphics --format=csv,noheader 2>&1 | Out-String).Trim() -replace "`r?`n", ' | ')"
|
||||
}
|
||||
260
tools/ca3-v4-amend/pc1-v4-sub3-amd-g1.ps1
Normal file
260
tools/ca3-v4-amend/pc1-v4-sub3-amd-g1.ps1
Normal file
|
|
@ -0,0 +1,260 @@
|
|||
# Counter ASIC 3.0, PC 1 queue job (7 October 2026): gate G1 on the sub-version 3 kit and item 8's AMD ladder rows on PC 1's
|
||||
# RX 9070 XT (machine ae432dc7, gfx1201 on the eGPU). The shape is tools/ca3-pc1-amd/pc1-amd-g1-shadow.ps1 (6 October 2026)
|
||||
# with the kit of 7 October: fetch job fetch-ca3-v4-sub3-pc1-20261007 (bin/igneum-worker-opencl.exe from this tree's
|
||||
# proto-opencl/host.c, cross-compiled on build-1 with mingw as proto-cuda/nvrtc/build-windows.sh builds the shipped worker;
|
||||
# packs/: the class v3 control mx8-devnet-epoch0, the seven sub-version 3 class v4 packs v4-devnet-epoch0, v4-era-0 .. v4-era-5
|
||||
# (program id a785001687d8688a), and the ladder packs sh256x13, sh256x27, sh256x53, sh256x88, sh64x52 re-exported under the
|
||||
# amended stream; src/; SHA256SUMS). Published with the RUNNER's --cards-off amd:gfx1201 (the index-free key form; the runner
|
||||
# matches the live card with or without the device index and restores it on ANY exit); the 5090, 5080 and 4070 keep mining.
|
||||
# The script never touches /api/cards, never quits, pauses, resumes or updates the installed app, never writes settings.json;
|
||||
# it confirms the card quiet by the process list, then runs the kit worker --bench-pack on every pack while the AMD helper
|
||||
# samples board watts (the sampler by its own pid). Per pack: the self-test, the 2^24 fingerprint at base nonce 0 against the
|
||||
# Mac's (G1: Metal = Apple OpenCL on 7 October 2026, 17:5x UTC), and a timed rate for the ladder row (MH/s, board watts,
|
||||
# microjoules per hash). Lines: `RESULT G1 pack= fingerprint= match=`, `RESULT LADDER pack= ops= mhs= watts= uj=`, a
|
||||
# `SUMMARY {json}` line. Read back with `node tools/jobs.mjs <job id>`.
|
||||
$ErrorActionPreference = 'Continue'
|
||||
$jobName = 'pc1-v4-sub3-amd-g1'
|
||||
$kitId = 'fetch-ca3-v4-sub3-pc1-20261007'
|
||||
$started = Get-Date
|
||||
function Stamp { (Get-Date).ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ssZ') }
|
||||
function Summary([string] $status, [hashtable] $extra) {
|
||||
$o = [ordered]@{ job = $jobName; status = $status; duration_s = [int]((Get-Date) - $started).TotalSeconds; finished_at = (Stamp) }
|
||||
foreach ($k in $extra.Keys) { $o[$k] = $extra[$k] }
|
||||
'SUMMARY ' + ($o | ConvertTo-Json -Compress -Depth 4)
|
||||
}
|
||||
# the expected 2^24 fingerprints at base nonce 0 (the Mac on Metal and Apple OpenCL, the 5090 on CUDA: status file
|
||||
# section 3, docs/analysis/latency-shadow-2026-10-06.md sections 2 and 5, counter-asic-2-status.md 22:16 for the
|
||||
# control; re-checked on Apple OpenCL through this kit's host.c on 6 October 2026, 15:2x UTC)
|
||||
$expected = @{
|
||||
'mx8-devnet-epoch0' = '90f794dd556f7a3b'
|
||||
'v4-devnet-epoch0' = 'e370fb2080b7dbb1'
|
||||
'v4-era-0' = 'b7237555d31fc3cf'
|
||||
'v4-era-1' = 'b6b167fa15dfe2c9'
|
||||
'v4-era-2' = '28bdf65eff33f2c4'
|
||||
'v4-era-3' = 'e26d38c46f3f1b16'
|
||||
'v4-era-4' = 'dd8fdf6ff4f59eed'
|
||||
'v4-era-5' = '8bf40f5cb858d835'
|
||||
'sh256x13' = 'ff8f707210659eb1'
|
||||
'sh256x27' = '892b6d55a7ddcfcb'
|
||||
'sh256x53' = '663c73c61f62cc54'
|
||||
'sh256x88' = 'ec7ca430a061fa59'
|
||||
'sh64x52' = '9dd010f79d8ca9f4'
|
||||
}
|
||||
# ops per hash as the status file counts them (x1.83 counted ops per shadow instruction + 930 for the base program);
|
||||
# the shadow instruction count is read from each pack's program.json at run time and printed beside it
|
||||
$opsPerHash = @{ 'mx8-devnet-epoch0' = 930; 'v4-devnet-epoch0' = 102100; 'v4-era-0' = 102100; 'v4-era-1' = 102100; 'v4-era-2' = 102100; 'v4-era-3' = 102100; 'v4-era-4' = 102100; 'v4-era-5' = 102100; 'sh256x13' = 49700; 'sh256x27' = 102100; 'sh256x53' = 199600; 'sh256x88' = 330700; 'sh64x52' = 49700 }
|
||||
# timed dispatches of 2^24 nonces per pack: about 0.9 s each at 19 MH/s, longer where the shadow binds the card
|
||||
$batches = @{ 'mx8-devnet-epoch0' = 90; 'v4-devnet-epoch0' = 90; 'v4-era-0' = 40; 'v4-era-1' = 40; 'v4-era-2' = 40; 'v4-era-3' = 40; 'v4-era-4' = 40; 'v4-era-5' = 40; 'sh256x13' = 90; 'sh256x27' = 90; 'sh256x53' = 80; 'sh256x88' = 60; 'sh64x52' = 90 }
|
||||
$order = @('mx8-devnet-epoch0', 'v4-devnet-epoch0', 'v4-era-0', 'v4-era-1', 'v4-era-2', 'v4-era-3', 'v4-era-4', 'v4-era-5', 'sh256x13', 'sh256x27', 'sh256x53', 'sh256x88', 'sh64x52', 'mx8-devnet-epoch0')
|
||||
|
||||
"RESULT start $(Stamp) job=$jobName machine=$env:COMPUTERNAME app_version=$env:IGNEUM_APP_VERSION"
|
||||
# ---- the kit (the wiped-jobs-folder rule: tested before use; republish the fetch after any app update) ----
|
||||
$jobs = Split-Path $env:IGNEUM_JOB_DIR
|
||||
$kit = Join-Path $jobs $kitId
|
||||
if (-not (Test-Path $kit)) { "RESULT error kit missing at $kit (the fetch job $kitId runs first; republish it after any app update)"; Summary 'failed' @{ error = 'kit missing' }; exit 2 }
|
||||
$exe = Join-Path $kit 'bin\igneum-worker-opencl.exe'
|
||||
$packs = Join-Path $kit 'packs'
|
||||
if (-not (Test-Path $exe)) { "RESULT error worker missing at $exe"; Summary 'failed' @{ error = 'worker missing' }; exit 2 }
|
||||
if (-not (Test-Path $packs)) { "RESULT error packs missing at $packs"; Summary 'failed' @{ error = 'packs missing' }; exit 2 }
|
||||
"RESULT worker kit $exe sha256 $((Get-FileHash -Algorithm SHA256 $exe).Hash.ToLower()) bytes $((Get-Item $exe).Length)"
|
||||
foreach ($f in (Get-ChildItem $kit -Recurse -File | Where-Object { $_.Name -match '\.(exe|c|json)$|SHA256SUMS' } | Sort-Object FullName)) { "RESULT kitfile $($f.FullName.Substring($kit.Length + 1).Replace('\', '/')) sha256 $((Get-FileHash -Algorithm SHA256 $f.FullName).Hash.ToLower()) bytes $($f.Length)" }
|
||||
$job = $env:IGNEUM_JOB_DIR; if (-not $job) { $job = Join-Path $env:TEMP 'igneum-ca3-pc1-amd' }; New-Item -ItemType Directory -Force -Path $job | Out-Null
|
||||
|
||||
# ---- the installed app: install folder (the installed worker for the cross-check and the AMD helper), app dir, URL ----
|
||||
$inst = @("$env:LOCALAPPDATA\Programs\Igneum Miner", "$env:ProgramFiles\Igneum Miner") | Where-Object { Test-Path (Join-Path $_ 'igneum-app.exe') } | Select-Object -First 1
|
||||
$appDir = if ($env:IGNEUM_APP_DIR) { $env:IGNEUM_APP_DIR } else { Join-Path $env:LOCALAPPDATA 'igneum\app' }
|
||||
$urlFile = Join-Path $appDir 'app.url'
|
||||
$base = $null
|
||||
if (Test-Path $urlFile) { $base = (Get-Content -LiteralPath $urlFile -Raw).Trim().TrimEnd('/') }
|
||||
"RESULT app install=$inst app_dir=$appDir url_present=$([bool]$base)"
|
||||
|
||||
# ---- the OpenCL device index of the 9070 XT, from the worker's own list (the current platform; the older platform's duplicate is marked dup) ----
|
||||
$listExe = $exe
|
||||
if ($inst -and (Test-Path (Join-Path $inst 'igneum-worker-opencl.exe'))) { $listExe = Join-Path $inst 'igneum-worker-opencl.exe' }
|
||||
$list = @(& $listExe --list 2>&1 | ForEach-Object { "$_" })
|
||||
$list | ForEach-Object { "RESULT list $_" }
|
||||
$dev = $null
|
||||
foreach ($l in $list) { if ($l -match '^\s*\[(\d+)\].*gfx1201' -and $l -notmatch 'dup') { $dev = [int]$Matches[1]; break } }
|
||||
if ($null -eq $dev) { "RESULT error no gfx1201 device in --list (the eGPU is off the bus: every row OWED)"; Summary 'failed' @{ error = 'no gfx1201' }; exit 2 }
|
||||
"RESULT device $dev gfx1201 (list from $listExe)"
|
||||
|
||||
# ---- the card is switched off by the RUNNER (`--cards-off amd:gfx1201` at publish), never by this script (rule of
|
||||
# 6 October 2026, tools/ci/playbook-quit-check.sh rule 2); here only the read-only confirmation that its worker is gone ----
|
||||
function Workers {
|
||||
@(Get-CimInstance Win32_Process -Filter "Name = 'igneum-worker-opencl.exe' OR Name = 'igneum-worker-cuda.exe' OR Name = 'igneum-worker-metal.exe'" -ErrorAction SilentlyContinue | ForEach-Object { "$($_.Name):$($_.ProcessId):[$($_.CommandLine -replace '\s+', ' ')]" })
|
||||
}
|
||||
function CardWorkers { @(Get-CimInstance Win32_Process -Filter "Name = 'igneum-worker-opencl.exe'" -ErrorAction SilentlyContinue | Where-Object { $_.CommandLine -match "--device\s+$dev(\s|$)" } | ForEach-Object { $_.ProcessId }) }
|
||||
"RESULT workers_before $(Stamp) $((Workers) -join ' ')"
|
||||
|
||||
# ---- the AMD helper: the installed app's igneum-gpu-telemetry.exe (0.3.10+), else the newest one under an amd-kit job folder ----
|
||||
$tele = $null
|
||||
if ($inst -and (Test-Path (Join-Path $inst 'igneum-gpu-telemetry.exe'))) { $tele = Join-Path $inst 'igneum-gpu-telemetry.exe' }
|
||||
if (-not $tele) { $found = Get-ChildItem -Path (Join-Path $appDir 'jobs') -Recurse -Filter 'igneum-gpu-telemetry.exe' -ErrorAction SilentlyContinue | Where-Object { $_.FullName -match 'amd-kit' } | Sort-Object LastWriteTime -Descending | Select-Object -First 1; if ($found) { $tele = $found.FullName } }
|
||||
$wattsSource = 'owed'
|
||||
if ($tele) {
|
||||
$one = @(& $tele 2>&1 | ForEach-Object { "$_" })
|
||||
$one | ForEach-Object { "RESULT tele_once $_" }
|
||||
$card9070 = $one | Where-Object { $_ -match '^amd \d+ .*name "[^"]*9070[^"]*" watts (\S+) .* source (\S+)' } | Select-Object -First 1
|
||||
if ($card9070 -and $card9070 -match ' watts (\S+) .* source (\S+)$') { if ($Matches[1] -ne '-') { $wattsSource = $Matches[2] } }
|
||||
"RESULT tele helper=$tele sha256=$((Get-FileHash -Algorithm SHA256 $tele).Hash.ToLower()) watts_source=$wattsSource (board watts through ADLX GPUPower, GPUTotalBoardPower as the fallback; 'owed' = the helper gives no watts for the 9070 XT)"
|
||||
} else { "RESULT tele none (no igneum-gpu-telemetry.exe in the install folder or under an amd-kit job folder): watts OWED" }
|
||||
|
||||
$samples = Join-Path $job 'amd-samples.log'
|
||||
$sampler = $null
|
||||
$windows = @()
|
||||
$results = @()
|
||||
$cardAlone = $false
|
||||
try {
|
||||
# ---- the card quiet? read-only: the runner switched it off before this script started; its worker must be gone ----
|
||||
$before = @(CardWorkers)
|
||||
"RESULT card_workers_before pids=[$($before -join ' ')]"
|
||||
if ($before.Count -eq 0) { "RESULT card already quiet: no opencl worker with --device $dev (the runner's --cards-off took, or --stop-miners)"; $cardAlone = $true }
|
||||
else {
|
||||
$t = 0
|
||||
while ($t -lt 150) { Start-Sleep -Seconds 5; $t += 5; if ((@(CardWorkers)).Count -eq 0) { break } }
|
||||
$left = @(CardWorkers)
|
||||
if ($left.Count -eq 0) { "RESULT card $(Stamp) quiet after $t s: no igneum-worker-opencl process with --device $dev (the card to itself)"; $cardAlone = $true }
|
||||
else { "RESULT card $(Stamp) UNCONFIRMED after $t s: opencl worker pids [$($left -join ' ')] still carry --device $dev (was the job published with --cards-off amd:gfx1201?); the rows below are LOADED-card figures" }
|
||||
Start-Sleep -Seconds 5
|
||||
}
|
||||
"RESULT workers_during $(Stamp) $((Workers) -join ' ')"
|
||||
|
||||
# ---- the sampler: the helper at 1 Hz through a wrapper; its paths through the ENVIRONMENT (inherited by the child,
|
||||
# never on a command line: Start-Process -ArgumentList in Windows PowerShell 5.1 joins arguments WITHOUT quoting,
|
||||
# and "Igneum Miner" holds a space, which is why the 15:46Z run wrote no samples), the -File path quoted; ended
|
||||
# by its own pid in finally. The sampler PROVES itself in the 12 idle seconds: the first three raw lines are
|
||||
# printed, and without a 9070 XT line with watts the ladder is NOT run blind (the rates are on record from 15:46Z). ----
|
||||
$samplerOk = $false
|
||||
if ($tele -and $wattsSource -ne 'owed') {
|
||||
$wrapper = Join-Path $job 'sampler.ps1'
|
||||
$wrapperText = @'
|
||||
$exe = $env:IGNEUM_SAMPLER_EXE
|
||||
$out = $env:IGNEUM_SAMPLER_OUT
|
||||
Add-Content -LiteralPath $out -Value ('wrapper start ' + (Get-Date).ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ss.fff') + ' exe=' + $exe + ' exists=' + (Test-Path -LiteralPath $exe))
|
||||
& $exe -l 1 2>&1 | ForEach-Object { Add-Content -LiteralPath $out -Value ((Get-Date).ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ss.fff') + ' ' + $_) }
|
||||
Add-Content -LiteralPath $out -Value ('wrapper end ' + (Get-Date).ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ss.fff') + ' exit=' + $LASTEXITCODE)
|
||||
'@
|
||||
[IO.File]::WriteAllText($wrapper, $wrapperText, (New-Object System.Text.UTF8Encoding $false))
|
||||
Remove-Item -LiteralPath $samples -Force -ErrorAction SilentlyContinue
|
||||
$env:IGNEUM_SAMPLER_EXE = $tele
|
||||
$env:IGNEUM_SAMPLER_OUT = $samples
|
||||
$sampler = Start-Process -FilePath 'powershell.exe' -ArgumentList @('-NoProfile', '-ExecutionPolicy', 'Bypass', '-File', ('"' + $wrapper + '"')) -WindowStyle Hidden -PassThru
|
||||
"RESULT sampler pid=$($sampler.Id) wrapper=$wrapper file=$samples exe_via_env=$tele"
|
||||
Start-Sleep -Seconds 12
|
||||
$raw = @(); if (Test-Path -LiteralPath $samples) { $raw = @(Get-Content -LiteralPath $samples) }
|
||||
$raw | Select-Object -First 3 | ForEach-Object { "RESULT sampler_raw $_" }
|
||||
$n9070 = @($raw | Where-Object { $_ -match '^\S+ amd \d+ .*name "[^"]*9070[^"]*" watts [\d.]+ ' }).Count
|
||||
"RESULT sampler_idle lines=$($raw.Count) lines_9070_with_watts=$n9070 sampler_alive=$(-not $sampler.HasExited)"
|
||||
if ($n9070 -eq 0) {
|
||||
$why = if ($raw.Count -eq 0) { 'the sampler wrote nothing in 12 s (file absent or empty)' } else { 'the sampler wrote ' + $raw.Count + ' line(s) but none is a 9070 XT line with a watts number (first lines above)' }
|
||||
"RESULT watts error=$why; the ladder is NOT run blind"
|
||||
foreach ($pk in $order) { "RESULT LADDER pack=$pk error=$why" }
|
||||
} else { $samplerOk = $true; "RESULT idle $(Stamp) sampler proven on the idle window" }
|
||||
} else { "RESULT watts error=no helper or no watts in its one-shot sample: the ladder runs with watts owed"; $samplerOk = $true }
|
||||
|
||||
# ---- the ladder ----
|
||||
$i = 0
|
||||
foreach ($pk in $order) {
|
||||
if (-not $samplerOk) { break }
|
||||
$i++
|
||||
$d = Join-Path $packs $pk
|
||||
if (-not (Test-Path $d)) { "RESULT G1 pack=$pk error=pack missing at $d"; "RESULT LADDER pack=$pk error=pack missing"; continue }
|
||||
$instrs = 0; $cls = ''
|
||||
try { $pj = Get-Content -LiteralPath (Join-Path $d 'program.json') -Raw | ConvertFrom-Json; $cls = [string] $pj.load_class; if ($pj.shadow) { $instrs = [int] $pj.shadow.instrs_per_hash } } catch { "RESULT note program.json of $pk unreadable: $($_.Exception.Message)" }
|
||||
$n = $batches[$pk]
|
||||
$tag = "$pk#$i"
|
||||
$t0 = (Get-Date).ToUniversalTime()
|
||||
"RESULT bench $tag start $(Stamp) batches=$n class=$cls shadow_instrs_per_hash=$instrs"
|
||||
$lines = @(& $exe --bench-pack --pack $d --batches $n --batch-log2 24 --device $dev 2>&1 | ForEach-Object { "$_" })
|
||||
$rc = $LASTEXITCODE
|
||||
$t1 = (Get-Date).ToUniversalTime()
|
||||
$lines | ForEach-Object { "RESULT bench $tag $_" }
|
||||
"RESULT bench $tag end $(Stamp) exit=$rc wall_s=$([int]($t1 - $t0).TotalSeconds)"
|
||||
$res = $lines | Where-Object { $_ -match '^RESULT pack=' } | Select-Object -First 1
|
||||
$fp = ''; $mhs = ''; $check = ''
|
||||
if ($res -and $res -match 'fingerprint=([0-9a-f]{16})') { $fp = $Matches[1] }
|
||||
if ($res -and $res -match 'mhs=([\d.]+)') { $mhs = $Matches[1] }
|
||||
if ($res -and $res -match 'check=(\S+)') { $check = $Matches[1] }
|
||||
$buildMs = ''; if (($lines | Where-Object { $_ -match '^build ([\d.]+) ms clBuildProgram' } | Select-Object -First 1) -match '^build ([\d.]+) ms') { $buildMs = $Matches[1] }
|
||||
$dsMs = ''; if (($lines | Where-Object { $_ -match '^pack .*: cache \d+ dataset (\d+) ' } | Select-Object -First 1) -match ' dataset (\d+) ') { $dsMs = $Matches[1] }
|
||||
if (-not $res -or $rc -ne 0) {
|
||||
$err = ($lines | Where-Object { $_ -match 'FAIL|error|MISMATCH' } | Select-Object -First 1); if (-not $err) { $err = "exit $rc, no RESULT line" }
|
||||
"RESULT G1 pack=$pk error=$($err -replace '\s+', ' ')"
|
||||
"RESULT LADDER pack=$pk error=$($err -replace '\s+', ' ')"
|
||||
continue
|
||||
}
|
||||
$want = $expected[$pk]
|
||||
$match = if ($want -and $fp -eq $want) { 'yes' } elseif ($want) { 'no' } else { 'unknown' }
|
||||
"RESULT G1 pack=$pk fingerprint=$fp match=$match expected=$want check=$check card=gfx1201 harness=opencl-kit"
|
||||
$results += [pscustomobject]@{ tag = $tag; pack = $pk; instrs = $instrs; ops = $opsPerHash[$pk]; mhs = $mhs; fp = $fp; match = $match; check = $check; build_ms = $buildMs; dataset_ms = $dsMs; t0 = $t0; t1 = $t1 }
|
||||
Start-Sleep -Seconds 3
|
||||
}
|
||||
|
||||
# ---- the cross-check: the INSTALLED worker on the control pack, if it carries --bench-pack ----
|
||||
if ($inst -and (Test-Path (Join-Path $inst 'igneum-worker-opencl.exe'))) {
|
||||
$iexe = Join-Path $inst 'igneum-worker-opencl.exe'
|
||||
$help = (& $iexe --help 2>&1 | Out-String)
|
||||
if ($help -match '--bench-pack') {
|
||||
$d = Join-Path $packs 'mx8-devnet-epoch0'
|
||||
$t0 = (Get-Date).ToUniversalTime()
|
||||
"RESULT xcheck start $(Stamp) worker=installed sha256=$((Get-FileHash -Algorithm SHA256 $iexe).Hash.ToLower())"
|
||||
$lines = @(& $iexe --bench-pack --pack $d --batches 40 --batch-log2 24 --device $dev 2>&1 | ForEach-Object { "$_" })
|
||||
$rc = $LASTEXITCODE
|
||||
$t1 = (Get-Date).ToUniversalTime()
|
||||
$lines | Where-Object { $_ -match '^RESULT|^pack |^warm-up|FAIL|error' } | ForEach-Object { "RESULT xcheck $_" }
|
||||
$res = $lines | Where-Object { $_ -match '^RESULT pack=' } | Select-Object -First 1
|
||||
$fp = ''; $mhs = ''
|
||||
if ($res -and $res -match 'fingerprint=([0-9a-f]{16})') { $fp = $Matches[1] }
|
||||
if ($res -and $res -match 'mhs=([\d.]+)') { $mhs = $Matches[1] }
|
||||
if ($res) { "RESULT XCHECK worker=installed pack=mx8-devnet-epoch0 fingerprint=$fp match=$(if ($fp -eq $expected['mx8-devnet-epoch0']) { 'yes' } else { 'no' }) mhs=$mhs exit=$rc (the kit worker's control rows are the LADDER lines)"; $windows += [pscustomobject]@{ tag = 'xcheck-installed'; t0 = $t0; t1 = $t1 } }
|
||||
else { "RESULT XCHECK worker=installed error=exit $rc, no RESULT line" }
|
||||
} else { "RESULT XCHECK worker=installed skipped: the installed igneum-worker-opencl.exe has no --bench-pack" }
|
||||
}
|
||||
} finally {
|
||||
# ---- whatever happened: the sampler ended by its own pid (never by name: the app runs its own copy of the helper), the card back ----
|
||||
if ($sampler) { try { & taskkill /T /F /PID $sampler.Id 2>&1 | Out-Null; "RESULT sampler ended pid=$($sampler.Id)" } catch { "RESULT sampler end error=$($_.Exception.Message)" } }
|
||||
# the card comes back through the RUNNER (its restore on any exit), not here
|
||||
"RESULT workers_after $(Stamp) $((Workers) -join ' ')"
|
||||
}
|
||||
|
||||
# ---- the samples: board watts and clocks of the 9070 XT inside each bench window (after its first 12 s, before its last 2 s) ----
|
||||
$rows = @()
|
||||
if (Test-Path $samples) {
|
||||
foreach ($l in (Get-Content -LiteralPath $samples)) {
|
||||
if ($l -match '^(\S+) amd (\d+) bus (\S+) kind (\S+) name "([^"]*)" watts (\S+) temp_c (\S+) fan_rpm (\S+) fan_pct (\S+) mclk_mhz (\S+) gclk_mhz (\S+) util_pct (\S+) source (\S+)') {
|
||||
if ($Matches[5] -notmatch '9070') { continue }
|
||||
try { $ts = [datetime]::ParseExact($Matches[1], 'yyyy-MM-ddTHH:mm:ss.fff', [Globalization.CultureInfo]::InvariantCulture) } catch { continue }
|
||||
$w = if ($Matches[6] -eq '-') { $null } else { [double] $Matches[6] }
|
||||
$rows += [pscustomobject]@{ ts = $ts; w = $w; temp = $Matches[7]; fan = $Matches[9]; mclk = $Matches[10]; gclk = $Matches[11]; util = $Matches[12]; src = $Matches[13] }
|
||||
}
|
||||
}
|
||||
}
|
||||
"RESULT samples total $($rows.Count) file $samples (9070 XT lines)"
|
||||
function Window([datetime] $t0, [datetime] $t1) { @($rows | Where-Object { $_.ts -gt $t0.AddSeconds(12) -and $_.ts -lt $t1.AddSeconds(-2) -and $null -ne $_.w }) }
|
||||
$g1Match = 0; $ladderRows = 0
|
||||
foreach ($r in $results) {
|
||||
$in = @(Window $r.t0 $r.t1)
|
||||
$watts = 'owed'; $uj = 'owed'; $gclk = 'owed'; $mclk = 'owed'; $tmax = 'owed'; $wmin = ''; $wmax = ''
|
||||
if ($in.Count -gt 0) {
|
||||
$m = $in | Measure-Object -Property w -Average -Minimum -Maximum
|
||||
$watts = [math]::Round($m.Average, 1); $wmin = $m.Minimum; $wmax = $m.Maximum
|
||||
$gclk = [math]::Round((($in | ForEach-Object { [double] $_.gclk }) | Measure-Object -Average).Average)
|
||||
$mclk = [math]::Round((($in | ForEach-Object { [double] $_.mclk }) | Measure-Object -Average).Average)
|
||||
$tmax = (($in | ForEach-Object { [double] $_.temp }) | Measure-Object -Maximum).Maximum
|
||||
if ($r.mhs -and [double] $r.mhs -gt 0) { $uj = [math]::Round($m.Average / [double] $r.mhs, 3) }
|
||||
}
|
||||
if ($r.match -eq 'yes') { $g1Match++ }
|
||||
$ladderRows++
|
||||
"RESULT LADDER pack=$($r.pack) ops=$($r.ops) shadow_instrs=$($r.instrs) mhs=$($r.mhs) watts=$watts uj=$uj watts_min=$wmin watts_max=$wmax gclk_mhz=$gclk mclk_mhz=$mclk temp_max=$tmax samples=$($in.Count) window_s=$([int]($r.t1 - $r.t0).TotalSeconds) build_ms=$($r.build_ms) dataset_ms=$($r.dataset_ms) fingerprint=$($r.fp) match=$($r.match) card_alone=$cardAlone tag=$($r.tag)"
|
||||
}
|
||||
foreach ($win in $windows) { $in = @(Window $win.t0 $win.t1); if ($in.Count -gt 0) { $m = $in | Measure-Object -Property w -Average; "RESULT power $($win.tag) samples=$($in.Count) watts_mean=$([math]::Round($m.Average, 1))" } }
|
||||
if ($rows.Count -gt 0) {
|
||||
$idle = @($rows | Where-Object { $results.Count -gt 0 -and $_.ts -lt $results[0].t0 -and $null -ne $_.w })
|
||||
if ($idle.Count -gt 0) { "RESULT idle_power samples=$($idle.Count) watts_mean=$([math]::Round(($idle | Measure-Object -Property w -Average).Average, 1)) (the card switched off in the app, before the first bench)" }
|
||||
foreach ($r in $rows) { "RESULT sample $($r.ts.ToString('HH:mm:ss')) $($r.w) $($r.temp) $($r.fan) $($r.mclk) $($r.gclk) $($r.util) $($r.src)" }
|
||||
}
|
||||
$status = if (-not $samplerOk) { 'failed' } elseif ($results.Count -eq $order.Count) { 'done' } elseif ($results.Count -gt 0) { 'partial' } else { 'failed' }
|
||||
"RESULT end $(Stamp)"
|
||||
Summary $status @{ packs_run = $results.Count; packs_wanted = $order.Count; g1_match = $g1Match; ladder_rows = $ladderRows; watts = $wattsSource; sampler_proven = $samplerOk; card_alone = $cardAlone; device = $dev }
|
||||
if ($status -eq 'failed') { exit 1 }
|
||||
exit 0
|
||||
80
tools/ca3-v4-amend/pc1-v5-amd-bench-20261007.ps1
Normal file
80
tools/ca3-v4-amend/pc1-v5-amd-bench-20261007.ps1
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
# Class v5 kit bench on PC 1's RX 9070 XT (7 October 2026, docs/design/class-v5-stored-state.md section 13, "the kit:
|
||||
# OpenCL (AMD)"): the kit's igneum-worker-opencl.exe (THIS tree's proto-opencl/host.c with the class v5 leaf upload:
|
||||
# igneum_build(ds, cache, leaves, nLeaves, nItems), the leaves of leaves.bin checked against the pack's FNV-1a 64 first)
|
||||
# runs --bench-pack on the first class v5 pack, proto-cuda/packs-ca3-v5/v5-dn3-epoch0 (program id e5a4ac5978462156, 11
|
||||
# leaves under state root 7e37a9fb..., cache FNV 7334fa46e5d972eb), on the gfx1201 device. Expected: the self-test
|
||||
# PASS line (cache head, last line and FNV; dataset head, word [268435455] and 64 samples; 96 of 96 vector lanes) and the
|
||||
# fingerprint of the 2^24 outputs at base nonce 0 equal to the Metal reading, 82b19cbde8557ea5 (the M5 Max, 7 October
|
||||
# 2026, 18:07:09Z). The v4-genesis control pack runs after it so the run has a known class v4 row beside the v5 row.
|
||||
# Beside the miners (a correctness and fingerprint gate; the rate row is labelled loaded when the card mines): no card is
|
||||
# switched, nothing is posted to the installed app, nothing is built on the PC. Published by the Counter ASIC coordinator
|
||||
# only (the PC 1 queue is its); the kit is fetch job $kitId. Every result line starts with RESULT; SUMMARY {json} ends it.
|
||||
$ErrorActionPreference = 'Continue'
|
||||
$jobName = 'pc1-amd-v5-bench'
|
||||
$kitId = $env:IGNEUM_V5_KIT_ID; if (-not $kitId) { $kitId = 'fetch-ca3-v5-kit-20261007' }
|
||||
$expected = '82b19cbde8557ea5'
|
||||
$started = Get-Date
|
||||
function Stamp { (Get-Date).ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ssZ') }
|
||||
function Summary([string] $status, [hashtable] $extra) {
|
||||
$o = [ordered]@{ job = $jobName; status = $status; duration_s = [int]((Get-Date) - $started).TotalSeconds; finished_at = (Stamp) }
|
||||
foreach ($k in $extra.Keys) { $o[$k] = $extra[$k] }
|
||||
'SUMMARY ' + ($o | ConvertTo-Json -Compress -Depth 4)
|
||||
}
|
||||
"RESULT start $(Stamp) job=$jobName machine=$env:COMPUTERNAME app_version=$env:IGNEUM_APP_VERSION expected_fingerprint=$expected"
|
||||
$jobs = Split-Path $env:IGNEUM_JOB_DIR
|
||||
$kit = Join-Path $jobs $kitId
|
||||
if (-not (Test-Path $kit)) { "RESULT error kit missing at $kit (the fetch job $kitId runs first; republish it after any app update)"; Summary 'failed' @{ error = 'kit missing' }; exit 2 }
|
||||
$exe = Join-Path $kit 'bin\windows\igneum-worker-opencl.exe'
|
||||
$packs = Join-Path $kit 'packs'
|
||||
if (-not (Test-Path $exe)) { "RESULT error worker missing at $exe"; Summary 'failed' @{ error = 'worker missing' }; exit 2 }
|
||||
"RESULT worker kit $exe sha256 $((Get-FileHash -Algorithm SHA256 $exe).Hash.ToLower()) bytes $((Get-Item $exe).Length)"
|
||||
foreach ($pk in @('v5-dn3-epoch0', 'v4-genesis')) {
|
||||
$d = Join-Path $packs $pk
|
||||
if (-not (Test-Path (Join-Path $d 'kernel_bound.cl'))) { "RESULT error pack $pk missing at $d"; Summary 'failed' @{ error = "pack $pk missing" }; exit 2 }
|
||||
"RESULT pack $pk kernel_bound.cl sha256 $((Get-FileHash -Algorithm SHA256 (Join-Path $d 'kernel_bound.cl')).Hash.ToLower()) program.h sha256 $((Get-FileHash -Algorithm SHA256 (Join-Path $d 'program.h')).Hash.ToLower())"
|
||||
}
|
||||
$leaves = Join-Path $packs 'v5-dn3-epoch0\leaves.bin'
|
||||
if (-not (Test-Path $leaves)) { "RESULT error leaves.bin missing at $leaves (a class v5 pack without its leaves builds nothing)"; Summary 'failed' @{ error = 'leaves missing' }; exit 2 }
|
||||
"RESULT leaves $leaves bytes $((Get-Item $leaves).Length) sha256 $((Get-FileHash -Algorithm SHA256 $leaves).Hash.ToLower())"
|
||||
# the OpenCL device index of the 9070 XT (the installed worker's list when present: the app's own indices)
|
||||
$inst = @("$env:LOCALAPPDATA\Programs\Igneum Miner", "$env:ProgramFiles\Igneum Miner") | Where-Object { Test-Path (Join-Path $_ 'igneum-app.exe') } | Select-Object -First 1
|
||||
$listExe = $exe
|
||||
if ($inst -and (Test-Path (Join-Path $inst 'igneum-worker-opencl.exe'))) { $listExe = Join-Path $inst 'igneum-worker-opencl.exe' }
|
||||
$list = @(& $listExe --list 2>&1 | ForEach-Object { "$_" })
|
||||
$list | ForEach-Object { "RESULT list $_" }
|
||||
$dev = $null
|
||||
foreach ($l in $list) { if ($l -match '^\s*\[(\d+)\].*gfx1201' -and $l -notmatch 'dup') { $dev = [int]$Matches[1]; break } }
|
||||
if ($null -eq $dev) { "RESULT error no gfx1201 device in --list (the eGPU is off the bus: the AMD v5 row stays OWED)"; Summary 'failed' @{ error = 'no gfx1201' }; exit 2 }
|
||||
"RESULT device $dev gfx1201 (list from $listExe)"
|
||||
function Workers { @(Get-CimInstance Win32_Process -Filter "Name = 'igneum-worker-opencl.exe' OR Name = 'igneum-worker-cuda.exe'" -ErrorAction SilentlyContinue | ForEach-Object { "$($_.Name):$($_.ProcessId):[$($_.CommandLine -replace '\s+', ' ')]" }) }
|
||||
$w = @(Workers)
|
||||
$loaded = ($w | Where-Object { $_ -match "igneum-worker-opencl.*--device\s+$dev(\s|$)" }).Count -gt 0
|
||||
"RESULT workers_before $(Stamp) $($w -join ' ')"
|
||||
$state = if ($loaded) { 'loaded' } else { 'quiet' }
|
||||
"RESULT context card_state=$state (a fingerprint gate: the load changes the rate row, never the bytes)"
|
||||
$rows = @{}
|
||||
$fpOk = $false
|
||||
foreach ($pk in @('v5-dn3-epoch0', 'v4-genesis')) {
|
||||
$d = Join-Path $packs $pk
|
||||
$t0 = Get-Date
|
||||
"RESULT bench $pk start $(Stamp) cmd=igneum-worker-opencl.exe --bench-pack --pack $d --device $dev --batch-log2 24 --batches 5"
|
||||
$out = @(& $exe --bench-pack --pack $d --device $dev --batch-log2 24 --batches 5 2>&1 | ForEach-Object { "$_" })
|
||||
$code = $LASTEXITCODE
|
||||
$secs = [int]((Get-Date) - $t0).TotalSeconds
|
||||
foreach ($l in $out) { if ($l -match '^(pack |class v5|RESULT |FAIL|error|warm-up)') { "RESULT bench $pk out $l" } }
|
||||
$res = $out | Where-Object { $_ -match '^RESULT ' } | Select-Object -Last 1
|
||||
$fp = ''; $mhs = ''; $check = ''
|
||||
if ($res -match 'fingerprint=([0-9a-f]{16})') { $fp = $Matches[1] }
|
||||
if ($res -match 'mhs=([0-9.]+)') { $mhs = $Matches[1] }
|
||||
if ($res -match 'check=(\w+)') { $check = $Matches[1] }
|
||||
$rows[$pk] = [ordered]@{ exit = $code; seconds = $secs; fingerprint = $fp; mhs = $mhs; check = $check; card_state = $state }
|
||||
if ($pk -eq 'v5-dn3-epoch0') {
|
||||
$fpOk = ($fp -eq $expected -and $check -eq 'PASS')
|
||||
"RESULT v5 fingerprint=$fp expected=$expected match=$fpOk check=$check mhs=$mhs card_state=$state exit=$code seconds=$secs $(Stamp)"
|
||||
} else {
|
||||
"RESULT v4-control fingerprint=$fp check=$check mhs=$mhs card_state=$state exit=$code seconds=$secs $(Stamp)"
|
||||
}
|
||||
}
|
||||
"RESULT workers_after $(Stamp) $((@(Workers)) -join ' ')"
|
||||
Summary $(if ($fpOk) { 'done' } else { 'failed' }) @{ expected = $expected; v5 = $rows['v5-dn3-epoch0']; v4_control = $rows['v4-genesis']; device = $dev; kit = $kitId }
|
||||
exit $(if ($fpOk) { 0 } else { 1 })
|
||||
84
tools/ca3-v4-amend/pc1-v5-intel-bench-20261007.ps1
Normal file
84
tools/ca3-v4-amend/pc1-v5-intel-bench-20261007.ps1
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
# Class v5 kit bench on PC 1's Intel Arc B580 (7 October 2026, 23:4x UK, main's re-route: the Intel fingerprint runs on PC 1 as
|
||||
# the same lock-free, non-elevated job class as the AMD one, after it): tools/class-v5/pc1-amd-v5-bench.ps1 with the device
|
||||
# match on the Intel OpenCL platform (Arc / B580) instead of gfx1201; everything else identical (the v5-dn3-epoch0 pack,
|
||||
# the v4-genesis control, RESULT lines, SUMMARY {json}). Expected fingerprint 82b19cbde8557ea5.
|
||||
# Class v5 kit bench on PC 1's RX 9070 XT (7 October 2026, docs/design/class-v5-stored-state.md section 13, "the kit:
|
||||
# OpenCL (AMD)"): the kit's igneum-worker-opencl.exe (THIS tree's proto-opencl/host.c with the class v5 leaf upload:
|
||||
# igneum_build(ds, cache, leaves, nLeaves, nItems), the leaves of leaves.bin checked against the pack's FNV-1a 64 first)
|
||||
# runs --bench-pack on the first class v5 pack, proto-cuda/packs-ca3-v5/v5-dn3-epoch0 (program id e5a4ac5978462156, 11
|
||||
# leaves under state root 7e37a9fb..., cache FNV 7334fa46e5d972eb), on the gfx1201 device. Expected: the self-test
|
||||
# PASS line (cache head, last line and FNV; dataset head, word [268435455] and 64 samples; 96 of 96 vector lanes) and the
|
||||
# fingerprint of the 2^24 outputs at base nonce 0 equal to the Metal reading, 82b19cbde8557ea5 (the M5 Max, 7 October
|
||||
# 2026, 18:07:09Z). The v4-genesis control pack runs after it so the run has a known class v4 row beside the v5 row.
|
||||
# Beside the miners (a correctness and fingerprint gate; the rate row is labelled loaded when the card mines): no card is
|
||||
# switched, nothing is posted to the installed app, nothing is built on the PC. Published by the Counter ASIC coordinator
|
||||
# only (the PC 1 queue is its); the kit is fetch job $kitId. Every result line starts with RESULT; SUMMARY {json} ends it.
|
||||
$ErrorActionPreference = 'Continue'
|
||||
$jobName = 'pc1-intel-v5-bench'
|
||||
$kitId = $env:IGNEUM_V5_KIT_ID; if (-not $kitId) { $kitId = 'fetch-ca3-v5-kit-20261007' }
|
||||
$expected = '82b19cbde8557ea5'
|
||||
$started = Get-Date
|
||||
function Stamp { (Get-Date).ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ssZ') }
|
||||
function Summary([string] $status, [hashtable] $extra) {
|
||||
$o = [ordered]@{ job = $jobName; status = $status; duration_s = [int]((Get-Date) - $started).TotalSeconds; finished_at = (Stamp) }
|
||||
foreach ($k in $extra.Keys) { $o[$k] = $extra[$k] }
|
||||
'SUMMARY ' + ($o | ConvertTo-Json -Compress -Depth 4)
|
||||
}
|
||||
"RESULT start $(Stamp) job=$jobName machine=$env:COMPUTERNAME app_version=$env:IGNEUM_APP_VERSION expected_fingerprint=$expected"
|
||||
$jobs = Split-Path $env:IGNEUM_JOB_DIR
|
||||
$kit = Join-Path $jobs $kitId
|
||||
if (-not (Test-Path $kit)) { "RESULT error kit missing at $kit (the fetch job $kitId runs first; republish it after any app update)"; Summary 'failed' @{ error = 'kit missing' }; exit 2 }
|
||||
$exe = Join-Path $kit 'bin\windows\igneum-worker-opencl.exe'
|
||||
$packs = Join-Path $kit 'packs'
|
||||
if (-not (Test-Path $exe)) { "RESULT error worker missing at $exe"; Summary 'failed' @{ error = 'worker missing' }; exit 2 }
|
||||
"RESULT worker kit $exe sha256 $((Get-FileHash -Algorithm SHA256 $exe).Hash.ToLower()) bytes $((Get-Item $exe).Length)"
|
||||
foreach ($pk in @('v5-dn3-epoch0', 'v4-genesis')) {
|
||||
$d = Join-Path $packs $pk
|
||||
if (-not (Test-Path (Join-Path $d 'kernel_bound.cl'))) { "RESULT error pack $pk missing at $d"; Summary 'failed' @{ error = "pack $pk missing" }; exit 2 }
|
||||
"RESULT pack $pk kernel_bound.cl sha256 $((Get-FileHash -Algorithm SHA256 (Join-Path $d 'kernel_bound.cl')).Hash.ToLower()) program.h sha256 $((Get-FileHash -Algorithm SHA256 (Join-Path $d 'program.h')).Hash.ToLower())"
|
||||
}
|
||||
$leaves = Join-Path $packs 'v5-dn3-epoch0\leaves.bin'
|
||||
if (-not (Test-Path $leaves)) { "RESULT error leaves.bin missing at $leaves (a class v5 pack without its leaves builds nothing)"; Summary 'failed' @{ error = 'leaves missing' }; exit 2 }
|
||||
"RESULT leaves $leaves bytes $((Get-Item $leaves).Length) sha256 $((Get-FileHash -Algorithm SHA256 $leaves).Hash.ToLower())"
|
||||
# the OpenCL device index of the 9070 XT (the installed worker's list when present: the app's own indices)
|
||||
$inst = @("$env:LOCALAPPDATA\Programs\Igneum Miner", "$env:ProgramFiles\Igneum Miner") | Where-Object { Test-Path (Join-Path $_ 'igneum-app.exe') } | Select-Object -First 1
|
||||
$listExe = $exe
|
||||
if ($inst -and (Test-Path (Join-Path $inst 'igneum-worker-opencl.exe'))) { $listExe = Join-Path $inst 'igneum-worker-opencl.exe' }
|
||||
$list = @(& $listExe --list 2>&1 | ForEach-Object { "$_" })
|
||||
$list | ForEach-Object { "RESULT list $_" }
|
||||
$dev = $null
|
||||
foreach ($l in $list) { if ($l -match '^\s*\[(\d+)\].*(Intel|Arc|B580)' -and $l -notmatch 'dup') { $dev = [int]$Matches[1]; break } }
|
||||
if ($null -eq $dev) { "RESULT error no Intel Arc device in --list (the B580 is off the bus or has no OpenCL runtime: the Intel v5 row stays OWED)"; Summary 'failed' @{ error = 'no intel arc' }; exit 2 }
|
||||
"RESULT device $dev Intel Arc (list from $listExe)"
|
||||
function Workers { @(Get-CimInstance Win32_Process -Filter "Name = 'igneum-worker-opencl.exe' OR Name = 'igneum-worker-cuda.exe'" -ErrorAction SilentlyContinue | ForEach-Object { "$($_.Name):$($_.ProcessId):[$($_.CommandLine -replace '\s+', ' ')]" }) }
|
||||
$w = @(Workers)
|
||||
$loaded = ($w | Where-Object { $_ -match "igneum-worker-opencl.*--device\s+$dev(\s|$)" }).Count -gt 0
|
||||
"RESULT workers_before $(Stamp) $($w -join ' ')"
|
||||
$state = if ($loaded) { 'loaded' } else { 'quiet' }
|
||||
"RESULT context card_state=$state (a fingerprint gate: the load changes the rate row, never the bytes)"
|
||||
$rows = @{}
|
||||
$fpOk = $false
|
||||
foreach ($pk in @('v5-dn3-epoch0', 'v4-genesis')) {
|
||||
$d = Join-Path $packs $pk
|
||||
$t0 = Get-Date
|
||||
"RESULT bench $pk start $(Stamp) cmd=igneum-worker-opencl.exe --bench-pack --pack $d --device $dev --batch-log2 24 --batches 5"
|
||||
$out = @(& $exe --bench-pack --pack $d --device $dev --batch-log2 24 --batches 5 2>&1 | ForEach-Object { "$_" })
|
||||
$code = $LASTEXITCODE
|
||||
$secs = [int]((Get-Date) - $t0).TotalSeconds
|
||||
foreach ($l in $out) { if ($l -match '^(pack |class v5|RESULT |FAIL|error|warm-up)') { "RESULT bench $pk out $l" } }
|
||||
$res = $out | Where-Object { $_ -match '^RESULT ' } | Select-Object -Last 1
|
||||
$fp = ''; $mhs = ''; $check = ''
|
||||
if ($res -match 'fingerprint=([0-9a-f]{16})') { $fp = $Matches[1] }
|
||||
if ($res -match 'mhs=([0-9.]+)') { $mhs = $Matches[1] }
|
||||
if ($res -match 'check=(\w+)') { $check = $Matches[1] }
|
||||
$rows[$pk] = [ordered]@{ exit = $code; seconds = $secs; fingerprint = $fp; mhs = $mhs; check = $check; card_state = $state }
|
||||
if ($pk -eq 'v5-dn3-epoch0') {
|
||||
$fpOk = ($fp -eq $expected -and $check -eq 'PASS')
|
||||
"RESULT v5 fingerprint=$fp expected=$expected match=$fpOk check=$check mhs=$mhs card_state=$state exit=$code seconds=$secs $(Stamp)"
|
||||
} else {
|
||||
"RESULT v4-control fingerprint=$fp check=$check mhs=$mhs card_state=$state exit=$code seconds=$secs $(Stamp)"
|
||||
}
|
||||
}
|
||||
"RESULT workers_after $(Stamp) $((@(Workers)) -join ' ')"
|
||||
Summary $(if ($fpOk) { 'done' } else { 'failed' }) @{ expected = $expected; v5 = $rows['v5-dn3-epoch0']; v4_control = $rows['v4-genesis']; device = $dev; kit = $kitId }
|
||||
exit $(if ($fpOk) { 0 } else { 1 })
|
||||
|
|
@ -22,6 +22,14 @@ check_file() {
|
|||
if grep -vE '^\s*#' "$f" | grep -qE "api/cards"; then
|
||||
echo "playbook-quit: $f sends a request to the installed app's api/cards (a script never switches cards; ask the runner: publish-jobs.sh add --kind run --cards-off <key,key>)"; bad=1
|
||||
fi
|
||||
# rule 3 (STANDING RULE, the founder through main, 7 October 2026, 18:5x UTC): no PC job raises a UAC prompt or needs a click, ever.
|
||||
# A script that launches anything with -Verb RunAs (or runas.exe) raises one; the rights path is the installed app's Igneum
|
||||
# Power Helper task (Start-ScheduledTask by the owning user, commands through its cmd.txt: app/igneum-app/src/powertask.rs).
|
||||
# publish-jobs.sh refuses --elevated for the same reason (the 18:27Z job run-ca3-pc1-v4-eff-5090-20261007: exit 251 after
|
||||
# two minutes waiting for a click).
|
||||
if grep -vE '^\s*#' "$f" | grep -qiE -e "(-Verb +['\"]?RunAs)|(\brunas(\.exe)? +/user)"; then
|
||||
echo "playbook-quit: $f raises an administrator prompt (-Verb RunAs or runas): no PC job prompts; use the Igneum Power Helper task (app/igneum-app/src/powertask.rs)"; bad=1
|
||||
fi
|
||||
grep -qE "api/(quit|pause|resume)" "$f" || return $bad
|
||||
if grep -vE '^\s*#' "$f" | grep -qE "igneum\\\\app\\\\app\.url|igneum/app/app\.url|Application Support/Igneum/app/app\.url|IGNEUM_APP_DIR[^\n]*app\.url|\\\$appDir[^\n]*'app\.url'"; then
|
||||
echo "playbook-quit: $f reads the installed app's URL file and sends quit, pause or resume to it (a job may only quit an engine it started: its own scratch URL file)"; bad=1
|
||||
|
|
@ -38,7 +46,12 @@ if [ "${1:-}" = "--self-test" ]; then
|
|||
if ! check_file "$t/good.ps1"; then echo "self-test FAILED: the good playbook failed"; exit 1; fi
|
||||
if check_file "$t/cards.ps1" >/dev/null; then echo "self-test FAILED: the api/cards switch passed"; exit 1; fi
|
||||
if ! check_file "$t/state.ps1"; then echo "self-test FAILED: a read of api/state (api/cards only in a comment) failed"; exit 1; fi
|
||||
rm -rf "$t"; echo "self-test passed: the installed app's URL file with a quit fails, a scratch URL file passes, a request to api/cards fails, a read of api/state passes"; exit 0
|
||||
printf '%s\n' '$p = Start-Process powershell.exe -Verb RunAs -ArgumentList @("-File", $s) -Wait -PassThru' > "$t/runas.ps1"
|
||||
printf '%s\n' '# the old shape ran Start-Process -Verb RunAs; now the Power Helper task carries the rights' 'Start-ScheduledTask -TaskName "Igneum Power Helper"' > "$t/helper.ps1"
|
||||
if check_file "$t/runas.ps1" >/dev/null; then echo "self-test FAILED: the RunAs launch passed"; exit 1; fi
|
||||
if ! check_file "$t/helper.ps1"; then echo "self-test FAILED: the Power Helper task start (RunAs only in a comment) failed"; exit 1; fi
|
||||
if ! bash packaging/ota/publish-jobs.sh add --kind run --target ae432dc7 --id self-test-elevated --elevated --script "$t/helper.ps1" --title "self-test" --dest "$t/dest" >/dev/null 2>&1; then :; else echo "self-test FAILED: publish-jobs.sh accepted --elevated (the 18:27Z job as published)"; exit 1; fi
|
||||
rm -rf "$t"; echo "self-test passed: the installed app's URL file with a quit fails, a scratch URL file passes, a request to api/cards fails, a read of api/state passes, a -Verb RunAs launch fails, a Power Helper task start passes, publish-jobs.sh refuses --elevated"; exit 0
|
||||
fi
|
||||
# allowed senders: the installer's own stop step (the update-now path the rule names), and, pending the rule owner's
|
||||
# word (6 October 2026, 15:10 UTC): tools/proving-v1/pc2-agg-cost.ps1 and its restore step pc2-agg-cost-restore.ps1, which switch the 5090 off through /api/cards
|
||||
|
|
|
|||
Loading…
Reference in a new issue