153 lines
14 KiB
PowerShell
153 lines
14 KiB
PowerShell
# amd-card-test.ps1: one AMD card through the whole test on a Windows PC running Igneum Miner (5 October 2026).
|
|
# Published as a run job (packaging/ota/publish-jobs.sh add --kind run ... --script this file) after a fetch job has put
|
|
# the kit zip (igneum-worker-opencl.exe, igneum-gpu-telemetry.exe, kernel_bound.cl, pack-a\) under jobs\<KitJob>\kit.
|
|
# Parameters at the top (a job script takes no arguments; edit the three lines or sed them before publishing):
|
|
# $Gfx the card's OpenCL name as the worker lists it (gfx1201 = RX 9070 XT, gfx1100 = RX 7900 XTX)
|
|
# $KitJob the fetch job id whose kit folder holds the exes (an app update clears the jobs folder: fetch again, new id, after one)
|
|
# $PricePounds the card's price for the MH per pound line (0 = not stated)
|
|
# What it does, with the card under test switched OFF in the app (POST /api/cards, that key only; the other cards keep
|
|
# mining) and switched back on at the end: the --list fold check (one row for the card, no dup row), --memprobe at
|
|
# 4, 64, 96 and 1024 MiB, a 120 s hash run (the worker's own serve mode on pack-a, 2^21-nonce jobs: within 1% of the
|
|
# app's STATUS rate on the 9070 XT, bench-log 5 October 2026), the telemetry window during it (watts, temperature,
|
|
# fan, memory clock), then the core-clock sweep (stock, 2800, 2400, 2000, 1600, 1200 MHz) and the power-limit sweep
|
|
# (100, 80, 65, 50% as ADLX offsets 0, -20, -35, -50, clamped to the card's range) at 90 s per step with the hash
|
|
# run as the load, every setting reset to factory at the end and read back. Output: RESULT lines, one bench-log table,
|
|
# and a RESULT line with the MH/W at stock and the MH per pound.
|
|
$Gfx = 'gfx1201'
|
|
$KitJob = 'amd-kit-1'
|
|
$PricePounds = 0
|
|
$ErrorActionPreference = 'Continue'
|
|
$kit = Join-Path $env:IGNEUM_APP_DIR ("jobs\" + $KitJob + "\kit")
|
|
$worker = Join-Path $kit 'igneum-worker-opencl.exe'
|
|
$tele = Join-Path $kit 'igneum-gpu-telemetry.exe'
|
|
$pack = Join-Path $kit 'pack-a'
|
|
$deadline = (Get-Date).AddMinutes(4)
|
|
while (-not ((Test-Path $worker) -and (Test-Path $tele)) -and (Get-Date) -lt $deadline) { Start-Sleep -Seconds 5 }
|
|
if (-not (Test-Path $worker)) { "RESULT no worker at $worker"; exit 1 }
|
|
if (-not (Test-Path $tele)) { "RESULT no telemetry helper at $tele"; exit 1 }
|
|
"RESULT kit worker sha256 " + (Get-FileHash $worker -Algorithm SHA256).Hash.ToLower() + " telemetry " + (Get-FileHash $tele -Algorithm SHA256).Hash.ToLower()
|
|
Set-Location $kit
|
|
# --- the card in the app: off for the test, on again at the end (the other cards are never touched)
|
|
$base = ""; try { $base = (Get-Content (Join-Path $env:LOCALAPPDATA "igneum\app\app.url") -Raw).Trim().TrimEnd("/") } catch {}
|
|
$amdKeys = @()
|
|
# the precondition (5 October 2026, after a probe read "off" while the log showed mining): two live reads 10 s apart;
|
|
# the card under test is switched off only when it is listed AND mining (state mining, hash_now above 0) on both reads;
|
|
# listed but not mining: nothing to switch off, the test still runs; not listed: the test still runs on the OpenCL index
|
|
$reads = @()
|
|
if ($base) { for ($i = 0; $i -lt 2; $i++) { try { $s = Invoke-RestMethod -Uri "$base/api/state" -TimeoutSec 20; $reads += ,@($s.mining.cards | ForEach-Object { [pscustomobject]@{ key = $_.key; state = $_.state; hash = [double]$_.hash_now; enabled = $_.enabled; identities = $_.identities } }) } catch { "RESULT the app is not answering: " + $_.Exception.Message }; if ($i -eq 0) { Start-Sleep -Seconds 10 } } }
|
|
if ($reads.Count -eq 2) { foreach ($c in $reads[1]) { $a = $reads[0] | Where-Object { $_.key -eq $c.key } | Select-Object -First 1; $mining = ($c.state -eq 'mining' -and $c.hash -gt 0 -and $a -and $a.state -eq 'mining' -and $a.hash -gt 0); "RESULT card before: " + $c.key + " | " + $a.state + " " + $a.hash + " then " + $c.state + " " + $c.hash + " MH/s | enabled " + $c.enabled + " | " + $(if ($mining) { "MINING" } else { "not mining" }); if ($c.key -like ("amd:*" + $Gfx + "*") -and $c.enabled -and $mining) { $amdKeys += @{ key = $c.key; enabled = $false; identities = $c.identities } } } }
|
|
"RESULT precondition: " + $(if ($amdKeys.Count -gt 0) { "$Gfx listed and mining: switching it off in the app for the test" } else { "$Gfx not mining in the app: nothing to switch off" })
|
|
function Set-Cards($list) { if ($list.Count -eq 0) { return }; $body = @{ cards = $list } | ConvertTo-Json -Depth 4; try { $r = Invoke-RestMethod -Method Post -Uri "$base/api/cards" -ContentType 'application/json' -Body $body -TimeoutSec 30; "RESULT api/cards: " + ($r | ConvertTo-Json -Compress) } catch { "RESULT api/cards failed: " + $_.Exception.Message } }
|
|
Set-Cards $amdKeys
|
|
if ($amdKeys.Count -gt 0) { Start-Sleep -Seconds 15 }
|
|
# an OpenCL worker may drive ANOTHER AMD card (the iGPU); a worker on THIS card's OpenCL index means the card is held: abort
|
|
function Held($index) { @(Get-CimInstance Win32_Process | Where-Object { $_.Name -eq 'igneum-worker-opencl.exe' -and $_.CommandLine -match ('--device ' + $index + '( |$)') -and $_.ExecutablePath -notlike "$kit*" }).Count -gt 0 }
|
|
# --- 1. the list fold check
|
|
"=== STAGE list fold check ($Gfx)"
|
|
$list = & $worker --list 2>&1
|
|
$list | ForEach-Object { $_ }
|
|
$rows = @($list | Where-Object { $_ -match ('^\s*\*?\[(\d+)\] ' + [regex]::Escape($Gfx)) })
|
|
$dups = @($list | Where-Object { $_ -match ('^\s*dup \[(\d+)\] ' + [regex]::Escape($Gfx)) })
|
|
$idx = if ($rows.Count -gt 0) { ($rows[0] -replace '^\s*\*?\[(\d+)\].*','$1') } else { '' }
|
|
"RESULT list: $($rows.Count) row(s) for $Gfx (index $idx), $($dups.Count) hidden duplicate(s)" + $(if ($rows.Count -eq 1) { " OK" } else { " NOT ONE ROW" })
|
|
if (-not $idx) { Set-Cards @($amdKeys | ForEach-Object { @{ key = $_.key; enabled = $true; identities = $_.identities } }); exit 1 }
|
|
if (Held $idx) { "RESULT a worker still holds OpenCL device $idx (the app did not release it): aborting, no contended number"; Set-Cards @($amdKeys | ForEach-Object { @{ key = $_.key; enabled = $true; identities = $_.identities } }); exit 1 }
|
|
# the telemetry ordinal of the card: the discrete AMD card whose name is not the integrated one
|
|
$t0 = & $tele 2>&1
|
|
$t0 | ForEach-Object { "TELE0 " + $_ }
|
|
$ord = ''; $cardName = ''
|
|
foreach ($l in $t0) { if ($l -match '^amd (\d+) bus (\S+) kind discrete name "([^"]*)"') { $ord = $matches[1]; $cardName = $matches[3]; break } }
|
|
"RESULT telemetry ordinal $ord name `"$cardName`""
|
|
# --- 2. memprobe
|
|
"=== STAGE memprobe $Gfx [$idx] at 4, 64, 96, 1024 MiB"
|
|
& $worker --device $idx --memprobe --probe-mib 4,64,96,1024 2>&1 | Where-Object { $_ -match '^\| (chase|indep|line|stream|alu)|^memprobe kernel' }
|
|
# --- the load: serve mode on pack-a, jobs of 2^21 nonces, as long as the script needs it (a background process fed from a file)
|
|
$seeds = Get-Content (Join-Path $pack 'seeds.txt')
|
|
$epoch = ($seeds | Where-Object { $_ -match '^epoch_seed_hex\s+(\S+)' } | ForEach-Object { $matches[1] } | Select-Object -First 1)
|
|
$day = ($seeds | Where-Object { $_ -match '^day_seed_hex\s+(\S+)' } | ForEach-Object { $matches[1] } | Select-Object -First 1)
|
|
$jobFile = Join-Path $env:IGNEUM_JOB_DIR 'jobs.txt'
|
|
$prehash = '7a1c0f3e5b9d2468ace0f1b2c3d4e5f60718293a4b5c6d7e8f9a0b1c2d3e4f50'
|
|
$lines = New-Object System.Collections.Generic.List[string]
|
|
for ($j = 1; $j -le 40000; $j++) { $start = [uint64]($j * 4294967296 + 1048576); $lines.Add("job $j $prehash 0000100000000000 $start 2097152 $epoch $day") }
|
|
$lines.Add('quit')
|
|
[IO.File]::WriteAllLines($jobFile, $lines)
|
|
$serveOut = Join-Path $env:IGNEUM_JOB_DIR 'serve.log'
|
|
$serve = Start-Process -FilePath $worker -ArgumentList '--serve','--pack',"`"$pack`"",'--device',$idx -RedirectStandardInput $jobFile -RedirectStandardOutput $serveOut -NoNewWindow -PassThru
|
|
$teleOut = Join-Path $env:IGNEUM_JOB_DIR 'tele.log'
|
|
$teleP = Start-Process -FilePath $tele -ArgumentList '-l','5' -RedirectStandardOutput $teleOut -NoNewWindow -PassThru
|
|
Start-Sleep -Seconds 20
|
|
$ready = Get-Content $serveOut | Where-Object { $_ -match '^ready|^error' } | Select-Object -First 1
|
|
"RESULT serve: $ready"
|
|
if (-not ($ready -match '^ready')) { Stop-Process -Id $serve.Id -Force -ErrorAction SilentlyContinue; Stop-Process -Id $teleP.Id -Force -ErrorAction SilentlyContinue; Set-Cards @($amdKeys | ForEach-Object { @{ key = $_.key; enabled = $true; identities = $_.identities } }); exit 1 }
|
|
# a window: mean MH/s from the done lines and the telemetry means over the last $sec seconds
|
|
function Window($sec, $label) {
|
|
$from = (Get-Date)
|
|
$n0 = @(Get-Content $serveOut | Where-Object { $_ -match '^done ' }).Count
|
|
Start-Sleep -Seconds $sec
|
|
$done = @(Get-Content $serveOut | Where-Object { $_ -match '^done \d+ \d+ ([\d.]+)$' } | Select-Object -Skip $n0 | ForEach-Object { [double]($_ -replace '^done \d+ \d+ ([\d.]+)$','$1') })
|
|
$tl = @(Get-Content $teleOut | Where-Object { $_ -match ('^amd ' + $ord + ' ') } | Select-Object -Last ([int]($sec / 5)))
|
|
$num = { param($k) @($tl | ForEach-Object { if ($_ -match (' ' + $k + ' (\S+)')) { $matches[1] } } | Where-Object { $_ -ne '-' } | ForEach-Object { [double]$_ }) }
|
|
$avg = { param($a, $d) if ($a.Count -gt 0) { [math]::Round(($a | Measure-Object -Average).Average, $d) } else { -1 } }
|
|
$mhs = if ($done.Count -gt 0) { [math]::Round(2097152.0 / (($done | Measure-Object -Average).Average) / 1000.0, 2) } else { -1 }
|
|
$w = & $avg (& $num 'watts') 1; $t = & $avg (& $num 'temp_c') 0; $f = & $avg (& $num 'fan_rpm') 0; $m = & $avg (& $num 'mclk_mhz') 0; $g = & $avg (& $num 'gclk_mhz') 0; $pl = & $avg (& $num 'plimit_pct') 0; $gm = & $avg (& $num 'gmax_mhz') 0
|
|
$eff = if ($mhs -gt 0 -and $w -gt 0) { [math]::Round($mhs / $w, 4) } else { -1 }
|
|
"RESULT window $label : jobs $($done.Count) mhs $mhs watts $w temp_c $t fan_rpm $f mclk_mhz $m gclk_mhz $g plimit_pct $pl gmax_mhz $gm eff $eff"
|
|
return [pscustomobject]@{ label = $label; mhs = $mhs; watts = $w; temp = $t; fan = $f; mclk = $m; gclk = $g; plimit = $pl; gmax = $gm; eff = $eff }
|
|
}
|
|
"=== STAGE 120 s hash and telemetry at stock"
|
|
$stock = Window 120 'stock'
|
|
# --- 3. the tuning ranges, then the sweeps
|
|
"=== STAGE tuning ranges"
|
|
$tune = & $tele --tune 2>&1
|
|
$tune | ForEach-Object { "TUNE " + $_ }
|
|
$tl = $tune | Where-Object { $_ -match ('^tune ' + $ord + ' ') } | Select-Object -First 1
|
|
$gmaxStock = -1; $gRange = @(-1, -1); $pRange = @(0, 0)
|
|
if ($tl -match 'gmax (\S+) gmax_range (\S+) (\S+) plimit (\S+) plimit_range (\S+) (\S+)') { $gmaxStock = [int]$matches[1]; $gRange = @([int]$matches[2], [int]$matches[3]); $pRange = @([int]$matches[5], [int]$matches[6]) }
|
|
"RESULT tune: stock gmax $gmaxStock MHz, gmax range $($gRange[0])..$($gRange[1]), power offset range $($pRange[0])..$($pRange[1]) %"
|
|
$table = @()
|
|
$table += $stock
|
|
if ($gmaxStock -gt 0) {
|
|
foreach ($mhz in @($gmaxStock, 2800, 2400, 2000, 1600, 1200)) {
|
|
$v = [math]::Max($gRange[0], [math]::Min($gRange[1], $mhz))
|
|
$r = & $tele --card $ord --set-gmax $v 2>&1
|
|
$verdict = ($r | Where-Object { $_ -match '^tune ' } | Select-Object -First 1)
|
|
"RESULT set gmax $v (asked $mhz): $verdict"
|
|
if ($verdict -match ' error ') { continue }
|
|
$row = Window 90 ("gmax " + $v)
|
|
$table += $row
|
|
}
|
|
$r = & $tele --card $ord --reset 2>&1; "RESULT reset after the clock sweep: " + ($r | Where-Object { $_ -match '^tune ' } | Select-Object -First 1)
|
|
Start-Sleep -Seconds 10
|
|
}
|
|
foreach ($pct in @(100, 80, 65, 50)) {
|
|
$off = $pct - 100
|
|
$v = [math]::Max($pRange[0], [math]::Min($pRange[1], $off))
|
|
$r = & $tele --card $ord --set-plimit $v 2>&1
|
|
$verdict = ($r | Where-Object { $_ -match '^tune ' } | Select-Object -First 1)
|
|
"RESULT set plimit $v % (asked $off %): $verdict"
|
|
if ($verdict -match ' error ') { continue }
|
|
$row = Window 90 ("plimit " + (100 + $v) + "%")
|
|
$table += $row
|
|
}
|
|
# --- restore and verify
|
|
"=== STAGE restore"
|
|
$r = & $tele --card $ord --reset 2>&1; "RESULT reset at the end: " + ($r | Where-Object { $_ -match '^tune ' } | Select-Object -First 1)
|
|
Start-Sleep -Seconds 10
|
|
$after = & $tele --tune 2>&1 | Where-Object { $_ -match ('^tune ' + $ord + ' ') } | Select-Object -First 1
|
|
"RESULT tune after restore: $after" + $(if ($after -match ' factory 1 ') { ' RESTORED (factory 1)' } elseif ($after -match ('gmax ' + $gmaxStock + ' ') -and $after -match ' plimit 0 ') { ' RESTORED (stock values)' } else { ' NOT VERIFIED' })
|
|
$verify = Window 30 'after restore'
|
|
Stop-Process -Id $serve.Id -Force -ErrorAction SilentlyContinue; Stop-Process -Id $teleP.Id -Force -ErrorAction SilentlyContinue
|
|
# --- the table
|
|
"=== STAGE table"
|
|
"| Setting | MH/s | Watts | MH/W | Temp C | Fan rpm | Memory MHz | Shader MHz | Power limit % | Max clock MHz |"
|
|
"|---|---|---|---|---|---|---|---|---|---|"
|
|
foreach ($row in $table + @($verify)) { "| " + $row.label + " | " + $row.mhs + " | " + $row.watts + " | " + $row.eff + " | " + $row.temp + " | " + $row.fan + " | " + $row.mclk + " | " + $row.gclk + " | " + $row.plimit + " | " + $row.gmax + " |" }
|
|
$best = $table | Where-Object { $_.eff -gt 0 } | Sort-Object eff -Descending | Select-Object -First 1
|
|
if ($best) { "RESULT best MH/W: " + $best.label + " at " + $best.eff + " MH/W (" + $best.mhs + " MH/s, " + $best.watts + " W); stock " + $stock.eff + " MH/W (" + $stock.mhs + " MH/s, " + $stock.watts + " W); hash cost of the best point " + [math]::Round(100 * (1 - $best.mhs / $stock.mhs), 1) + " %" }
|
|
$mclkDrop = @($table | Where-Object { $_.mclk -gt 0 -and $stock.mclk -gt 0 -and $_.mclk -lt 0.98 * $stock.mclk })
|
|
"RESULT memory clock: stock " + $stock.mclk + " MHz; " + $(if ($mclkDrop.Count -gt 0) { "DROPPED at " + (($mclkDrop | ForEach-Object { $_.label + " (" + $_.mclk + ")" }) -join ', ') } else { "held at every step" })
|
|
"RESULT $Gfx `"$cardName`" stock: " + $stock.mhs + " MH/s at " + $stock.watts + " W = " + $stock.eff + " MH/W" + $(if ($PricePounds -gt 0) { "; " + [math]::Round($stock.mhs / $PricePounds, 4) + " MH per pound at " + $PricePounds + " pounds" } else { "; MH per pound: price not stated" })
|
|
# --- the card back on in the app
|
|
Set-Cards @($amdKeys | ForEach-Object { @{ key = $_.key; enabled = $true; identities = $_.identities } })
|
|
if ($amdKeys.Count -gt 0) { Start-Sleep -Seconds 60; try { $s = Invoke-RestMethod -Uri "$base/api/state" -TimeoutSec 20; foreach ($c in $s.mining.cards) { "RESULT card after: " + $c.key + " | " + $c.state + " | " + $c.hash_now + " MH/s | enabled " + $c.enabled } } catch {} }
|
|
exit 0
|