CI builds this project but has no GPU. These checks are the evidence that the hardware layer works. Run them on the real machine, record the date and driver version below, and fill in the Result column for each row.
Checks 3-6 write to the GPU (clock offsets) through NVAPI, which
refuses those calls without administrator rights. Run gao.exe from an
elevated PowerShell or Command Prompt (Start menu -> right-click
PowerShell/Terminal -> "Run as administrator") for any command marked
"yes" in the Elevated column below; an unelevated shell is expected to fail
those specific calls with an NVAPI error, not a crash. Checks 1-2 only read
telemetry through NVML and do not need elevation. Checks 10-12 need no
elevation either: the stress load only computes, it writes no settings.
Checks 13-16 run --optimize, which writes clocks and the power limit: elevated.
Checks 46-52 run --optimize as well and need an elevated shell; check 45 compares --probe with another tool.
Checks 54-56 exercise the window after its display device went away; 54 and 55 change no GPU setting, 56 does run a search (from the window, elevated), which writes clocks and the power limit.
Checks 62-72 write the voltage/frequency curve (all but 61, which only reads it) and need an elevated shell; 68 forces two driver resets, 69 to 71 run the undervolt search from the window, and 72 compares two shapes of the flat top under load. Checks 73 to 76 are about an overclock and an undervolt together: 75 and 76 run All in one, about 35 minutes of full load each.
Checks 17-22 cover persistence; 19 and 20 need a log-off and log-on.
Commands assume a Release build at .\build\Release\gao.exe from the repo
root; adjust the path for a Debug build.
- Date run: 2026-09-26
- Driver version (
nvidia-smi --query-gpu=driver_version --format=csv,noheader): 610.74 - GPU: NVIDIA GeForce RTX 4070
- The RTX 5070 results (rows 45, 48, 51) were measured on driver 616.56; the driver is now 617.14. The
--probepart of check 45 and the card's edge (a probe passed at core +420 MHz and failed at +480 MHz on 616.56) must be established again on 617.14 before they are relied on. The RTX 4070 results on 610.74 are unaffected.
| # | Check | Command | Elevated | Expected | Result |
|---|---|---|---|---|---|
| 1 | Telemetry matches nvidia-smi | .\build\Release\gao.exe --probe |
no | Clocks, temp and power agree with nvidia-smi, and none of them print n/a (a failed reading now shows n/a, not a 0 that would look like a real one) |
Pass. 5 back-to-back samples at idle identical to nvidia-smi (core 210, mem 405, temp 34, power 14-15 W); no n/a. |
| 2 | Fan percentage is real | .\build\Release\gao.exe --probe |
no | fan= is not n/a (i.e. NVML reported a real value, not the -1 sentinel) |
Pass. fan=0% (zero-RPM at idle; nvidia-smi also 0 %), not n/a. |
| 3 | Core offset applies | In a second window run .\build\Release\gao.exe --stress 120 first; then .\build\Release\gao.exe --set-core 25 and compare the core= of its lines before and after |
yes | Read-back 25, OK; a follow-up --probe under the same load shows a core clock roughly 25 MHz above its boost figure from before the offset was applied. This second observation matters because the read-back reads the same byte offset the write just wrote: if kOffCoreDelta or the version word were wrong, the write would land in the wrong field and the read-back would confirm it anyway. The boost-clock rise is the only independent cross-check that the offset actually reached the clock, not just the buffer. |
Pass (2026-09-26). Read-back 25, OK. Cross-check under --stress: core 2790 -> 2805 MHz (8 probes each, all identical), back to 2790 after --reset. The rise is one 15 MHz clock bin, not 25: the V/F curve moves in 15 MHz steps. |
| 4 | Core offset reverts | .\build\Release\gao.exe --reset |
yes | Read-back core 0, mem 0, OK |
Pass. Read-back core 0, mem 0, OK. |
| 5 | Memory offset applies | In a second window run .\build\Release\gao.exe --stress 120 first; then .\build\Release\gao.exe --set-mem 100 and compare the mem= of its lines before and after |
yes | Read-back 100, OK; a follow-up --probe under the same load shows a memory clock roughly 100 MHz above its figure from before the offset was applied -- the same independent cross-check as check 3, for kOffMemDelta. |
Pass (2026-09-26). Read-back 100, OK. Cross-check under --stress: mem 10501 -> 10601 MHz, exactly +100. |
| 6 | A refused write is reported | .\build\Release\gao.exe --set-core 5000 |
yes | MISMATCH, not OK |
Pass. Driver rejected the call itself (NvAPI_GPU_SetPstates20 failed), read-back 0, MISMATCH, exit 1. --reset afterwards: OK. |
| 7 | Fan control applies | - | - | - | Retired: fan control was removed (the search never used it; NVIDIA's legacy fan API does not exist on RTX 20-series and newer). |
| 8 | Fan returns to automatic | - | - | - | Retired: fan control was removed (the search never used it; NVIDIA's legacy fan API does not exist on RTX 20-series and newer). |
| 9 | Unsupported fan API is reported | - | - | - | Retired: fan control was removed (the search never used it; NVIDIA's legacy fan API does not exist on RTX 20-series and newer). |
| 10 | Stress load is stable on stock and loads the card | .\build\Release\gao.exe --stress 60 (after --reset) |
no | VERDICT: STABLE, exit 0, steady-state power (the per-second lines after warm-up) ≥ 95 % of the power limit |
Pass (2026-09-26). STABLE, exit 0, 0 errors, peak 65 C, score ~5560 it/s. Steady power 184-195 W (avg 193 W = 96.5 % of 200 W); the whole-run average is 189 W (94.5 %) because the ramp-up at start is included. |
| 11 | A wrong result is detected | .\build\Release\gao.exe --stress 10 --stress-selftest wrong |
no | VERDICT: WRONG RESULT, exit 2, within the first second |
Pass (2026-09-26). WRONG RESULT on the first batch, exit 2. |
| 12 | A TDR is detected and survived | Start .\build\Release\gao.exe --stress 30; after ~5 s run dxcap -forcetdr in an elevated shell (DXCap.exe ships with Windows' Graphics Tools feature); then .\build\Release\gao.exe --stress 5 |
dxcap only | Screen goes black for 1-2 s while Windows resets the driver; the stress run prints VERDICT: DEVICE LOST (exit 2); the second prints VERDICT: STABLE. A long dispatch does not work as a trigger: the GPU preempts compute work instead of hanging, so no TDR fires (tried: a ~6 s dispatch completed as STABLE). |
Pass (2026-09-26). DEVICE LOST a moment after dxcap -forcetdr; the next --stress 5 printed STABLE. (The exit code was not captured in this run: Start-Process swallowed it; the verdict line comes from the same code path that returns 2.) Superseded note (2026-09-27): the exit code for DEVICE LOST is 2 by construction (stress() maps WrongResult and DeviceLost to 2); re-run with echo $LASTEXITCODE to record it. |
| 13 | Optimize end to end | .\build\Release\gao.exe --optimize best, then .\build\Release\gao.exe --stress 60 |
yes / no | RESULT: line with the applied values, soak STABLE; the follow-up stress run STABLE |
Pass (2026-09-26, driver 610.74). RESULT: power 105 %, core +135 MHz (max stable +195), mem +1050 MHz (max stable +1500); soak STABLE; score 5539 -> 5909 it/s (+6.7 %), core 2752 -> 2910 MHz, mem 10295 -> 11550 MHz, peak 65 -> 67 C, ~11 min. Follow-up --stress 60: STABLE, 0 errors, 5906 it/s. The core edge moves between runs (+210 was STABLE in one run, WRONG RESULT in the next; +225 held at 80 % power), which is what the margin is for. Memory reached the top of the search range (+1500) every run. |
| 14 | A recorded freeze becomes a ceiling | Append {"id":999,"core":150,"state":"begin"} to %ProgramData%\GpuAutoOptimizer\journal.jsonl, run --optimize best |
yes | Warning "froze the machine at core +150"; no logged core candidate ≥ +150. Remove the line afterwards. | Pass (2026-09-26). Printed warning: a previous run froze the machine at core +150; staying below it from now on; the highest core candidate tried was +135 (max stable +135, applied +90). Line removed afterwards. |
| 15 | Ctrl+C restores stock | Press Ctrl+C during the core search of --optimize best |
yes | "abort requested", then RESULT: not applied -- aborted (card at stock); --reset read-back 0/0 |
Pass (2026-09-26). Ctrl+C during the core search printed abort requested, finished the running +180 probe, then stopped: aborted -- card restored to stock / RESULT: not applied -- aborted (card at stock). --reset afterwards: read-back 0/0, power default, OK. |
| 16 | Power limit applies | --optimize quiet log shows power <N> % lines; --probe afterwards |
yes | --probe's /<limit> W equals N % of the default limit (±1 %) |
Pass (2026-09-26). --optimize quiet chose 80 %; --probe afterwards showed /160 W, 80 % of the 200 W default. That run also exposed a bug: the efficiency reference was a single noisy probe, so quiet cost 4 % score instead of < 2 % (5718 -> 5486 it/s); fixed in ed289e1 (reference = best stable score at or below the cap). |
| 17 | Optimize saves a profile | --optimize best (elevated), then --status |
yes / no | Saved: line; --status shows the profile and driver: ... (matches) |
Pass (2026-09-26, driver 610.74). --optimize best ended with Saved: ...; --status: profile: best: power 105 %, core +135 MHz, mem +1050 MHz (driver 610.74, saved 2026-09-26 23:51), driver: 610.74 (matches). |
| 18 | Apply re-applies it | --reset, then --apply, then --probe |
yes | applied best: ..., OK; --probe limit equals the profile's power % of default |
Pass (2026-09-26). --reset, then --apply: applied best: ..., OK; --probe showed /210 W = 105 % of the 200 W default. |
| 19 | Boot-apply at logon | --boot on, log off and on, wait 2 min, --status |
yes | boot.log last line applied ...; --status shows boot-apply: on, strikes: 0 of 3 |
Pass (2026-09-26). After log off/on: boot.log applied best: ... strike 1 clears in 2 minutes at 23:53; the boot-apply process exited after 2 minutes and gao.json showed boot_strikes: 0. The task action is quoted correctly for this repo's path with spaces (schtasks /Query /V: "C:\Users\...\Git Projects\...\gao.exe" --boot-apply). |
| 20 | Three strikes stop it | set "boot_strikes": 3 in gao.json, log off and on, --status; then --boot on |
yes | last boot line not applied: disabled after 3 crashes ...; after --boot on, strikes: 0 of 3 |
Pass (2026-09-26). With boot_strikes: 3, log off/on: last boot: ... not applied: disabled after 3 crashes; run `gao --boot on` to retry; --boot on then showed strikes: 0 of 3. Note: the card still carried the profile from the previous logon, because logging off does not reset the driver; in the real case (three crashes) each crash reboots the machine, which does. |
| 21 | A driver change blocks it | set "driver": "000.00" in gao.json, run --apply; restore the value |
yes | not applied: driver changed (000.00 -> ...) |
Pass (2026-09-26). With "driver": "000.00": --apply printed not applied: driver changed (000.00 -> 610.74); run `gao --optimize` again, exit 1. Value restored afterwards. |
| 22 | Boot-apply off | --boot off, then schtasks /Query /TN GpuAutoOptimizer |
yes | boot-apply off: task removed; schtasks reports the task does not exist |
Pass (2026-09-26). --boot off: boot-apply off: task removed; schtasks /Query /TN GpuAutoOptimizer: ERROR: The system cannot find the file specified.; --status: boot-apply: off. |
| 23 | Memory search stops at the bandwidth peak | --optimize best (elevated) |
yes | log shows mem +N: bandwidth X GB/s per step and mem: bandwidth peak +N; the peak is where bandwidth stops rising, not the top of the range |
Pass (2026-09-27, driver 610.74): one bandwidth line per 50 MHz step. On this card bandwidth kept rising to the top of the range (454.3 GB/s at +0, 513.0 at +1500), so there was no peak inside it. The 1 % tie band picked +1400 (509.3 GB/s), the lowest offset within 1 % of the best. The earlier guess that +1050 was already past the peak was wrong. |
| 24 | Edges are confirmed | same run as 23 | yes | confirm core +N and confirm ... mem +N lines with 30 s probes; RESULT: reports the confirmed values the margin was applied to |
Pass (2026-09-27): confirm core +195 / mem +0 STABLE, confirm core +135 / mem +1400 STABLE. RESULT: power 105 %, core +135 MHz (confirmed +195), mem +950 MHz (confirmed +1400). Soak STABLE, score 5764 -> 6008 it/s (+4.2 %). |
| 25 | Boot-apply runs the installed copy | --boot on, schtasks /Query /TN \GpuAutoOptimizer\BootApply /XML, log off and on, --status |
yes | exe in %ProgramFiles%\GpuAutoOptimizer\; XML shows DisallowStartIfOnBatteries false, ExecutionTimeLimit PT0S, Priority 5, --tray, IgnoreNew, the Program Files path; last boot: ... applied; boot copy: up to date |
Failed (2026-09-27): could not create the task (schtasks exit 1). The old task \GpuAutoOptimizer is a file in the task root, and it blocks creating the \GpuAutoOptimizer\ folder. Fixed: --boot on now removes the old task first. Both executables were copied to Program Files correctly. Re-run pass (2026-09-27, after 96e61bc): boot-apply on; the old task is gone; the XML has PT0S, Priority 5, IgnoreNew, no battery limits, and GpuAutoOptimizer.exe --tray from Program Files; boot copy: up to date; boot.log applied from the tray; strikes 0 of 3 after the grace period. |
| 26 | State folder is admin-only | icacls "%ProgramData%\GpuAutoOptimizer"; --status unelevated; a user-created folder is refused |
no / yes | SYSTEM and Administrators full, Users read & execute, no inherited entries; --status works; a folder created by the user before the first elevated run makes --optimize refuse |
Pass (2026-09-27): a folder the user created (owner Gebruiker) is refused (not owned by Administrators or SYSTEM); a junction is refused (is a link or not a folder) and its target's ACL is unchanged. The folder gao created: SYSTEM and Administrators (OI)(CI)(F), Users (OI)(CI)(RX), no inherited entries. |
| 27 | No DLL planting | copy C:\Windows\System32\version.dll next to the exe as d3d11.dll, dxgi.dll, d3dcompiler_47.dll, msvcp140.dll, vcruntime140.dll and vcruntime140_1.dll; run --stress 5, --bandwidth, --status; delete the copies |
no | all three work normally (a loaded fake would break the import) | Pass (2026-09-27, driver 610.74). --stress 5 STABLE (5768 it/s), --bandwidth 491.8 GB/s, --status normal. dumpbin /dependents: static imports only KERNEL32, ADVAPI32, SHELL32, ole32 (KnownDLLs; the CRT is linked statically); d3d11, dxgi, D3DCOMPILER_47 delay-loaded after SetDefaultDllDirectories. |
| 28 | Old journal is not imported | first elevated run after the move | yes | the new journal starts empty; nothing under %LOCALAPPDATA% is read by the elevated process |
Dropped as a migration on 2026-09-27: reading a user-writable file from the elevated process could be redirected to disclose other files (P4b review). The old journal on the reference machine had no open entries (136 lines, 0 freezes), so no ceiling was lost. |
| 29 | The window renders and idles | start GpuAutoOptimizer.exe unelevated |
no | GPU name, driver, telemetry, saved/applied tune and presets shown; under 1 % of one CPU core while idle | Pass (2026-09-27): all shown (screenshot); 0.05 CPU-seconds over 5 s idle (~1 %); 0.02 after the P5 fix pass. |
| 30 | Optimize from the window | Restart as administrator, pick Best, Optimize | yes | run screen with live temperature/power and the probe log; results screen with before/after | |
| 31 | Abort from the window | Abort during the core search | yes | the run ends with aborted, card at stock |
|
| 32 | Tray app at logon | apply at logon on, log off and on | yes (task) | tray icon present, tune applied, boot.log applied, strike cleared after 2 min |
Pass for the tray start (2026-09-27): the task-style start --tray applied the tune (boot.log applied ... strike 1 clears in 2 minutes) and the strike was cleared (0 of 3) after 2 minutes. A real logoff and logon with the new task is still to be checked. |
| 33 | Watchdog re-applies after a TDR | tray running, dxcap -forcetdr, wait 45 s, gao --status |
yes | applied: equals the saved tune again; boot.log has watchdog: ... re-applied; a balloon said so |
Failed (2026-09-27): the tray crashed on the forced TDR. It hit an access violation inside nvml.dll, called from the 1 s telemetry tick (crash dump in the state folder), so nothing re-applied the tune. Fixed: the tray's periodic hardware work is now guarded. After a reset (UI device removed, a failed read-back, or a fault) NVML and NVAPI are re-created 5 s later. A build with an injected access violation recovered and kept running. Re-run pass (2026-09-27, after 96e61bc): the tray was still running after dxcap -forcetdr; boot.log watchdog: the tune had been reset (driver reset or TDR); re-applied; applied: equals the saved tune; no new crash dump. |
| 34 | Watchdog backs off from another tool | tray running, set an offset in MSI Afterburner and Apply | no | a balloon says another program changed the settings; the watchdog does not fight it | |
| 35 | One instance | start the app twice | no | the second start brings the first window forward and exits | Pass (2026-09-27, after the P5 fix pass): the second start exits with code 0, the first keeps running. |
| 36 | CLI and tray side by side | tray running with the tune kept applied; gao --reset, wait 45 s, gao --status; then gao --optimize from a shell while the window runs an optimize |
yes | after --reset the card stays at stock (the tray notes it and does not re-apply); after gao --apply the tray keeps the tune applied again; the second optimize refuses with another optimize is already running |
Pass for reset and apply (2026-09-27): after gao --reset the card stayed at stock for 45 s with the tray running (no watchdog line); gao --apply applied the tune again. Two optimizes at once not yet tried. |
| 37 | Closing the window | close the window, then choose Exit in the tray menu | no | closing hides the window to the tray (a balloon says so the first time); Exit quits | |
| 38 | Fan curve follows the temperature | Fan page: fan control on; gao --stress 120 in a shell |
yes (tray) | the fan tile shows (curve) above the stop threshold and the speed rises with the temperature; gao --status shows fans: manual N % |
|
| 39 | Fan-stop zone | idle below the stop threshold | yes (tray) | gao --status shows fans: driver control; the fans stand still |
|
| 40 | Fans back on exit | Exit in the tray menu while the curve runs | yes (tray) | gao --status shows fans: driver control |
|
| 41 | Fans back on sleep and after a TDR | sleep and wake; then dxcap -forcetdr |
yes (tray) | on sleep: driver control; after wake and after the TDR the curve takes over again within a few seconds | |
| 42 | Another tool's fan speed | curve running, set a fixed fan speed in MSI Afterburner and Apply | yes (tray) | a balloon says another program set the fans; the app does not overwrite them | |
| 43 | gao --fan auto |
curve running, gao --fan auto in an elevated shell |
yes | fans: driver control -- OK; then the tray re-applies the curve on its next tick |
|
| 44 | Fans during an optimize run | gao --optimize best, Ctrl+C during the core search |
yes | during the run the fans follow the Best curve; after Ctrl+C gao --status shows fans: driver control |
|
| 45 | Offset ranges match another tool | gao --probe; in MSI Afterburner, read the lowest and highest value of the Core Clock and Memory Clock sliders; where a minimum differs, gao --set-core <min> / --set-mem <min> and one step below it |
no / yes | the maxima --probe prints equal the slider maxima; each minimum either equals the slider minimum or is confirmed by the driver accepting it and refusing one step lower |
Pass (2026-10-02, RTX 5070, driver 616.56, MSI Afterburner 4.6.6; not the reference RTX 4070). --probe: offset ranges as the driver reports them: core -1000 to +1000 MHz, mem -1000 to +3000 MHz. Afterburner sliders: Core Clock -502 to +1000 MHz, Memory Clock -502 to +3000 MHz. Both maxima match; both minima do not (-502 vs -1000), so they were measured against the driver (elevated): --set-core -1000 read back -1000, OK; --set-core -1015 read back -1000, NvAPI_GPU_SetPstates20 failed, MISMATCH; --set-mem -1000 read back -1000, OK; --set-mem -1050 read back -1000, NvAPI_GPU_SetPstates20 failed, MISMATCH. The driver accepts exactly down to -1000 on both, so the read-out minima are right and -502 is Afterburner's own slider limit. One card, one driver. Both maxima are below kCoreRangeSanityMhz (2000) and kMemRangeSanityMhz (6000); no constant needs raising. The --probe part again on the reference RTX 4070 (2026-10-04, driver 617.14): offset ranges as the driver reports them: core -1000 to +1000 MHz, mem -1000 to +3000 MHz, the same range as the RTX 5070; not compared with MSI Afterburner on this card. |
| 46 | Ctrl+C stops a running soak | gao --optimize best (elevated); press Ctrl+C about one minute into the soak: 300 s line; then gao --status and the last lines of %ProgramData%\GpuAutoOptimizer\journal.jsonl |
yes | RESULT: not applied -- aborted (card at stock) within about a second of the key press; --status shows stock offsets and 100 % power; the last journal line is "verdict":"ABORTED" |
Pass for the abort, earlier in the soak than the Command column asks (2026-10-04, reference RTX 4070, driver 617.14, build 2d9d115). The run of check 50 was stopped with Ctrl+C a few seconds after its soak had started, not one minute in: the log ended soak: 300 s at power 105 %, core +90, mem +200, abort requested -- stopping the probe and restoring stock, stopped: aborted -- card restored to stock, RESULT: not applied -- aborted (card at stock); gao.exe exited within about a second, exit code 1; the last journal line was "verdict":"ABORTED" for the entry "core":90,"mem":200; gao --status: applied: power 100 %, core +0 MHz, mem +0 MHz. The run started for this check itself was not aborted: with a memory offset of 0 the soak's journal entry names only the core, the script did not recognise it (fixed in fc851ab), and the run ended by itself with RESULT: power 105 %, core +135 MHz (confirmed +210), mem +0 MHz (confirmed +0). During that run another program had taken almost all of the machine's memory (0.4 of 31.9 GB free): core +225 ended WRONG RESULT, the confirm probe at +210 was STABLE at 3207 it/s and the soak STABLE at 2898 it/s, against a baseline of 5685 it/s. Not repeated with the abort one minute into the soak. Repeated as the Command column asks: Pass (2026-10-08, reference RTX 4070, driver 617.14, build 9ac1474 (the undervolt build, whose reset to stock reads the voltage/frequency curve), elevated, tray app exited, driven by tools/hwchecks.ps1). The soak was recognised 281 s into the run and Ctrl+C sent one minute later: the log ended soak: 300 s at power 105 %, core +135, mem +400, abort requested -- stopping the probe and restoring stock, stopped: aborted -- card restored to stock, RESULT: not applied -- aborted (card at stock); gao.exe exited within 0.8 s (which includes the 0.8 s the sender waits), exit code 1; the last journal line was "verdict":"ABORTED" for the entry "core":135,"mem":400; gao --status: applied: power 100 %, core +0 MHz, mem +0 MHz. In this run the memory scan ended early, at mem +750: the bandwidth readings did not settle; the memory scan ends here (peak +600, applying +400); the core search then ran to its own edge (+210 WRONG RESULT, confirmed +195). |
| 47 | The Abort button stops a running soak | Restart as administrator, Optimize, press Abort about one minute into the soak | yes | the run screen shows aborted within about a second; the card is at stock; the last journal line is ABORTED |
|
| 48 | Abort during a clock probe and during the baseline | gao --optimize best, Ctrl+C during the first clock candidates (the first core +N / mem +N lines); again during the 30 s baseline |
yes | both end with aborted (card at stock); the next gao --optimize best prints no a previous run froze the machine warning |
Partly checked; not a pass for the whole row. Baseline half, the abort itself: Pass (2026-10-02, RTX 5070, driver 616.56, build cb6c728; not the reference RTX 4070): Ctrl+C 12 s into the baseline; gao.exe exited within about a second, log abort requested -- stopping the probe and restoring stock, stopped: aborted -- card restored to stock, RESULT: not applied -- aborted (card at stock); no clock candidate had been journaled. Clock-probe half, the abort itself: Pass (2026-10-02, same card and driver, build 3080023): Ctrl+C during the first clock probe (core +60, the first clock candidate in that build); gao.exe exited within about a second, the journal entry was closed as ABORTED, log stopped: aborted -- card restored to stock; the journal had no begin without a complete. Not checked: the second part of the Expected column, that the next gao --optimize best prints no a previous run froze the machine warning. It was not checked after the baseline abort and is not recorded for the clock-probe abort. Clock-probe half on the reference RTX 4070: Pass (2026-10-04, driver 617.14, build 8c837ef, tools\hwchecks.ps1 -Checks 48): Ctrl+C during the first clock candidate (core +0 / mem +0, the first of the memory search); gao.exe exited within about a second, exit code 1, log abort requested -- stopping the probe and restoring stock, stopped: aborted -- card restored to stock, RESULT: not applied -- aborted (card at stock); the journal entry was closed as ABORTED, no begin without a complete; gao --status: applied: power 100 %, core +0 MHz, mem +0 MHz. The next gao --optimize best (check 51 below) printed no a previous run froze the machine warning. The baseline half was not run on this card. Clock half repeated: Pass (2026-10-08, reference RTX 4070, driver 617.14, build 9ac1474 (the undervolt build, whose reset to stock reads the voltage/frequency curve), elevated, tray app exited, driven by tools/hwchecks.ps1). Ctrl+C during the first clock candidate, right after the power: 105 % line: abort requested -- stopping the probe and restoring stock, stopped: aborted -- card restored to stock, RESULT: not applied -- aborted (card at stock); gao.exe exited within 0.8 s (including the sender's 0.8 s), exit code 1; the candidate's journal entry was closed as ABORTED; gao --status: applied: power 100 %, core +0 MHz, mem +0 MHz. The next gao --optimize best printed no a previous run froze the machine warning. |
| 49 | The core search climbs | gao --optimize best and gao --optimize max (elevated), each to the end |
yes | the log starts with core range: up to +N MHz (...) and mem range: up to +N MHz (...); the memory candidates (core +0 / mem +N) come before the core candidates; memory ascends by 50 from +0, one step at a time, until a candidate is not STABLE or the bandwidth falls more than 1 % below the best so far (a drop is measured twice, so one memory value can appear twice); the core ascends by 15 from +15, one step at a time, with the chosen memory offset applied, and the first core candidate that is not STABLE is the last one of the climb; in neither search is a higher value tried afterwards (the confirm lines that follow are at or below the value the search ended on); soak: 300 s; note the total run time of each |
best: Pass (2026-10-04, reference RTX 4070, driver 617.14, build 2d9d115; the second run of check 52, which has the log). The log started with core range: up to +1000 MHz (reported by the card) and mem range: up to +3000 MHz (reported by the card); the memory candidates came before the core candidates and ascended by 50 from +0; the core ascended by 15 from +15 to +225 with the chosen memory offset (+0) applied, and the first candidate that was not STABLE (+225, WRONG RESULT) ended the climb; confirmation went one step down at a time (+210 failed, +195 held). In the two runs of check 51 the same order showed with a memory offset applied during the core climb (core +15 / mem +200, core +15 / mem +450), and a bandwidth reading more than 1 % below the best was measured twice (mem +550 and mem +800 each appear twice). max: not run. best repeated: Pass (2026-10-08, reference RTX 4070, driver 617.14, build 9ac1474 (the undervolt build, whose reset to stock reads the voltage/frequency curve), elevated, tray app exited, driven by tools/hwchecks.ps1; 720 s in all). Memory ascended by 50 from +0 to core +0 / mem +2550: WRONG RESULT; confirm core +0 / mem +2200: STABLE; mem: bandwidth peak +2200, confirmed +2200, applying +1500. Core then ascended by 15 to core +225 / mem +1500: WRONG RESULT; confirm core +210 / mem +1500: WRONG RESULT; confirm core +195 / mem +1500: STABLE; core: highest stable +210, confirmed +195, applying +135; soak: STABLE score=5942 it/s peak=70 C power=201 W against a baseline of 5681 it/s; RESULT: power 105 %, core +135 MHz (confirmed +195), mem +1500 MHz (confirmed +2200), exit 0. The journal had no begin without a complete; no nvlddmkm event in the Windows System log during the run. max: still not run. |
| 50 | A freeze during the climb becomes a ceiling | after a run in which a core candidate froze the machine (or after adding a line {"core":N,"id":9999,"state":"begin"} to the journal by hand), gao --optimize best |
yes | the run warns a previous run froze the machine at core +N; staying below it from now on; the core search ascends by 15 from stock and its highest candidate is below +N; core +N or higher is never applied. (N must be above 0: an entry at stock clocks is reported as a freeze but never becomes a ceiling.) |
Pass (2026-10-04, reference RTX 4070, driver 617.14, build 2d9d115, tools\hwchecks.ps1 -Checks 50, which adds the line {"core":150,"id":9999,"state":"begin"} to the journal and removes it afterwards). The run started with warning: a previous run froze the machine at core +150; staying below it from now on. Memory first: mem: bandwidth peak +350, confirmed +350, applying +200. The core ascended by 15 from +15 to +135, all STABLE, and no further: confirm core +135 / mem +200: STABLE, core: highest stable +135, confirmed +135, applying +90, then the soak at core +90. The core values journaled were 15 to 135 and 135 again (the confirm probe); +150 or higher was never applied. The run was stopped with Ctrl+C during the soak (see check 46); the journal had no begin without a complete, and the injected line was removed (337 to 336 lines). Repeated: Pass (2026-10-08, reference RTX 4070, driver 617.14, build 9ac1474 (the undervolt build, whose reset to stock reads the voltage/frequency curve), elevated, tray app exited, driven by tools/hwchecks.ps1). With {"core":150,"id":9999,"state":"begin"} added to the journal the run warned a previous run froze the machine at core +150; staying below it from now on; the core values journaled were 15 to 135 and 135 again (the confirm probe), +150 or higher was never applied; core: highest stable +135, confirmed +135, applying +90. Stopped with Ctrl+C 40 s into the soak; no begin without a complete; the injected line was removed (1031 to 1030 lines). This run also met a real driver reset in its memory scan: at +2550 the bandwidth measurement failed (mem +2550: the bandwidth measurement failed; treated as a driver reset), and the Windows System log holds nvlddmkm 153 (Restarting TDR occurred on GPUID:600) and one nvlddmkm 14 for that second. The run went on as check 52 describes: mem: the driver reset at +2550; using +2350, the best of the values that passed, the driver was reset; reconnecting, resting 20 s before the card is loaded again, preparing the stress load, health: STABLE score=5450 it/s, confirm core +0 / mem +2150: STABLE, mem: bandwidth peak +2350, confirmed +2150, applying +1500, and the core climb after it had its own reset budget; the log ended driver resets during this run: 1. After this run, the last of the four started that day (checks 48, 49, 46 and 50), gao --curve read 0 of 127 raw offsets other than 0. |
| 51 | A driver reset during an optimize run is survived | gao --optimize best (elevated), with a few programs open on the desktop; once the memory search has ended (the mem: ... applying +N line) and the core climb runs (core +15 / mem +N, core +30 / mem +N, ...), run dxcap -forcetdr in a second elevated shell; let the run end by itself; then gao --status, the last lines of %ProgramData%\GpuAutoOptimizer\journal.jsonl, the nvlddmkm events of the Windows System log for the time of the run, and the desktop |
yes | All four criteria in the Notes ("Checks that involve a driver reset") hold. (1) gao: the core candidate that was running logs DEVICE LOST; then, in this order and before any further candidate, the driver was reset; reconnecting, resting 20 s before the card is loaded again, preparing the stress load, checking that the card is back: 5 s at stock and a health: line (the rest and the check up to three times, with preparing the stress load once, again only after a failed rebuild, until a health: STABLE line at 90 % of the baseline score or better); then core: the driver reset at +N; using +M, the last value that passed, with M one step below N; no core value above M is tried again; a confirm line, if any, is at M minus 60 or lower; the run ends with a RESULT: line with the applied values, or with RESULT: not applied -- <reason> and one of the reasons listed in the Notes; gao.exe does not crash; the card is at the result or at stock and gao --status agrees; the journal has no begin without a complete. (2) Windows System log: one driver reset (nvlddmkm) during the run, the forced one, unless the run ended with one of the three reasons the Notes name for a second reset. (3) The desktop, within one minute after the run ends: the windows that were open are visible and usable; a new program starts and shows its window; no sign-out or restart was needed; programs that crashed are noted. (4) The owner's judgement: the machine feels normal afterwards. |
First round: Failed (2026-10-02, RTX 5070, driver 616.56, build cb6c728; not the reference RTX 4070). Two gao --optimize best runs. The test script was meant to force the reset with dxcap -forcetdr early in the run, but a bug in the script meant that never happened, so the reset was not forced as the Command column describes: both runs climbed until the driver itself gave up. Run A: baseline STABLE 5869 it/s, power step ended at 100 %; core +60 to +420 all STABLE, score rising from 5994 to 6593 it/s; core +480 was reported STABLE with score 167 it/s, 85 W, peak 56 C, the log showed fans: a write did not verify; the driver has them again, the screen went black for a moment, no DEVICE LOST; the next candidate, +540, ended with the line stopped: setting power 100 % failed -- reset FAILED, run `gao --reset`, journal entry closed as SET FAILED, exit code 1; gao --status afterwards: applied: power 100 %, core +0 MHz, mem +0 MHz (the driver had reset itself to stock). Run B: core +60 to +420 STABLE, +480 DEVICE LOST; the next candidate, +450, was journaled (begin), then gao.exe died (Windows Application Error event: faulting module nvml.dll, exception code 0xc0000005). The journal was left with a begin for core +450 and no complete, which a later run would treat as a ceiling although the machine never froze; the machine did not reboot and the card was at stock afterwards. After both resets the desktop stayed usable; in both the load stopped within seconds of the reset. Fixed after that round (in build 3080023; not run on hardware before round A below): a probe judged STABLE that scores below a quarter of the baseline is now STALLED and counts as a failed candidate (01f0d80); after a probe that ends DEVICE LOST, STALLED or NO TELEMETRY the search reconnects to the driver before it opens the next journal entry or writes to the card (log line the driver was reset; reconnecting), a failed write is closed as SET FAILED and the candidate gets one new entry and one more try, and a driver that does not come back ends the run without an open journal entry (5176e1b, ddebf4d); the optimize run owns its NVML and NVAPI connections, calls them under a guard that turns an access violation in a driver DLL into a failed call, and re-creates both libraries on reconnect (up to 15 attempts, 2 s apart), an access violation that still escapes ends the run with the journal entry closed as CRASHED and the card reset to stock, and a fan hand-back that could not be delivered is retried and, if still not delivered, reported (ea2045c, 919a1e5, 7c22d35). Round A (2026-10-02, same card and driver, build 3080023: the run survives a reset and reconnects, but takes no rest; started by a test script whose output is kept locally; the open begin for core +450 that the first round left had been removed from the journal beforehand). The reset was forced with dxcap -forcetdr during the first core candidate, which in that build was core +60. What the tool did: baseline STABLE 5871 it/s; power step ended at 100 %; core +60 ended DEVICE LOST (score 1162 it/s); the log showed the driver was reset; reconnecting; the next candidate, core +30, was judged STALLED at 325 it/s, 45 W, peak 54 C; this was the first probe after the reset. Why it scored so low is not known: the stress load re-creates its D3D device and recompiles its shader in the first batch after a lost device, and that time is counted in the probe's score; the card itself may or may not have been back; the driver was reset; reconnecting again; core +15 STABLE at 5751 it/s, 156 W; Ctrl+C then ended the run: stopped: aborted -- card restored to stock, exit within about a second. gao.exe did not crash. The journal had no begin without a complete (entries closed DEVICE LOST, STALLED, STABLE, ABORTED). Windows System log: one driver reset, nvlddmkm event 153 ("Resetting / Reset / Restarting TDR occurred"), 15:12:25-15:12:26. The desktop: unusable afterwards. After the black screen the taskbar came back, but no application window could be opened or seen; Windows Application log: Application Error for Fork.exe at 15:12:30; the owner signed out and in to get the desktop back. In this round the load came back about two seconds after the driver restarted and the card stayed loaded for about two more minutes (the next check's baseline and power probes). Failed (the tool survived the reset; the machine was not usable afterwards). Changed since (unit-tested; none of it has been run on hardware): a card that computes almost nothing now ends the running probe within seconds as STALLED, judged over the last three seconds, instead of only after the probe (906e369); memory is searched before the core, and both searches go one grid step at a time with nothing bisected (b6e3fda); after a probe that ends DEVICE LOST, STALLED or NO TELEMETRY, and after a failed write, the search reconnects, writes stock, rests 20 s without load and then runs a 5 s probe at stock that must be STABLE at 90 % of the baseline score or better, at most three such probes with a rest before each, before any further candidate; a card that does not come back ends the run (32d6436, fd604c1); the reset budget: at the first such event in a run the search explores no further, keeps the last value that passed and confirms four steps below it, and at the second the run ends at stock with nothing saved; a failed write is no longer given one more try, as it was in build 3080023 (58355f3, 098789e); a journal entry at stock clocks never becomes a ceiling of zero (098789e, 72bf3e5). Round B (2026-10-04, reference RTX 4070, driver 617.14, tools\hwchecks.ps1 -Checks 51, no tray app running, desktop with Chrome, Discord, Steam, WhatsApp, Bambu Studio and claude-manager (a web-view application) open). The reset was forced with dxcap -forcetdr while core +90 was running. First run (build 8c837ef): no result for the recovery. The candidate that was running did not log DEVICE LOST: core +90 / mem +200: STABLE score=2378 it/s peak=69 C power=98 W (the candidates before it scored 5760 to 5880 it/s), with fans: a write did not verify; the driver has them again before it; the next candidate, core +105, was journaled and its write failed: setting power 105 % failed; treated as a driver reset, core: the driver reset at +105; using +90, the last value that passed, the driver was reset; reconnecting, journal entry closed as SET FAILED. The test script took the journaled +105 for a value applied after the reset and stopped the run with Ctrl+C two seconds later: RESULT: not applied -- aborted (card at stock), driver resets during this run: 1. The script now allows one step above the running candidate (9b76473). The desktop after this run: a new program started and showed its window after 0.6 s, no window was gone, no Application Error event; the window of claude-manager stopped drawing and that application had to be restarted. Second run (build 9b76473, same binaries): Pass on criteria 1 to 3. Baseline STABLE 5642 it/s; power 105 %; memory bandwidth peak +650, confirmed +650, applying +450; core +15 to +75 STABLE, 5681 to 5872 it/s; core +90 / mem +450: STABLE score=2509 it/s peak=69 C power=102 W (the reset fell in this probe; again no DEVICE LOST and no STALLED, the score is 44 % of the baseline, above the 25 % floor); setting power 105 % failed; treated as a driver reset; core: the driver reset at +105; using +90, the last value that passed; the driver was reset; reconnecting; resting 20 s before the card is loaded again; preparing the stress load; checking that the card is back: 5 s at stock; health: STABLE score=5465 it/s peak=66 C power=164 W (97 % of the baseline, first try); confirm core +30 / mem +450: STABLE; core: highest stable +90, confirmed +30, applying +15; soak: 300 s at power 105 %, core +15, mem +450, STABLE 5590 it/s; RESULT: power 105 %, core +15 MHz (confirmed +30), mem +450 MHz (confirmed +650), driver resets during this run: 1, exit code 0. No core value above +105 was journaled and none above +30 after the reset. gao --status agreed with the result; the journal had no begin without a complete (+90 STABLE, +105 SET FAILED, +30 STABLE, soak STABLE); no new crash dump. Windows System log: three nvlddmkm 153 events within one second (22:38:19-22:38:20), the forced reset, and none after it; no Display 4101 event. The desktop: a new program started and showed its window, no window was gone, no Application Error event, and this time claude-manager kept drawing; the load came back on the card after the 20 s rest and stayed for the confirmation and the 300 s soak. Differences from the Expected column, both on this card in both runs: the candidate running at the reset ends STABLE at a reduced score instead of DEVICE LOST, and the search notices the reset one candidate later, at a failed write, so N is the next candidate and M the one that was running; the core: the driver reset at +N line comes before the driver was reset; reconnecting, not after the health: line. Criterion 4 (the owner's judgement) is not recorded. |
| 52 | A real climb past the edge is survived | gao --optimize best (elevated), with a few programs open on the desktop, run to the end; then gao --status, the last lines of %ProgramData%\GpuAutoOptimizer\journal.jsonl, the nvlddmkm events of the Windows System log for the time of the run, and the desktop |
yes | All four criteria in the Notes ("Checks that involve a driver reset") hold. (1) gao: the first candidate that ends DEVICE LOST, STALLED or NO TELEMETRY is followed, in this order and before any further candidate, by the driver was reset; reconnecting, resting 20 s before the card is loaded again, preparing the stress load, checking that the card is back: 5 s at stock and a health: line (the rest and the check up to three times, with preparing the stress load once, again only after a failed rebuild, until a health: STABLE line at 90 % of the baseline score or better); the search then explores no further: when the reset came during a climb, a core: the driver reset at +N; using +M, the last value that passed line (or the mem: line of the same kind, followed by core: not searched; the driver reset earlier in this run), no new value above M, and confirm lines only at four steps below M or lower; when it came during a confirm probe or the soak, the next try is four steps lower; the run ends with a RESULT: line with the applied values after the driver reset during this run; the search stopped exploring there, or with RESULT: not applied -- <reason> and one of the reasons listed in the Notes; gao.exe does not crash; gao --status shows the result (or stock) applied and fans: driver control or the curve; the journal has no begin without a complete. (2) Windows System log: at most one driver reset (nvlddmkm) during the run, unless the run ended with one of the three reasons the Notes name for a second reset. (3) The desktop, within one minute after the run ends: the windows that were open are visible and usable; a new program starts and shows its window; no sign-out or restart was needed; programs that crashed are noted. (4) The owner's judgement: the machine feels normal afterwards. A run in which no probe resets the driver (the search reached the top of its range, or ended on WRONG RESULT) does not exercise this check: record what it did and leave the reset criteria open. |
First run: Failed (2026-10-04, reference RTX 4070, driver 617.14, binaries of 8c837ef, tools\hwchecks.ps1 -Checks full-best, no tray app running, a browser and other programs open on the desktop). The run never reached the edge. Baseline STABLE 5662 it/s; power 105 %; mem +0: bandwidth 437.6 GB/s, mem +50: bandwidth 442.6 GB/s, mem +100: bandwidth 420.5 GB/s, measured again 456.8 GB/s; at +150 the candidate was STABLE and the log showed bandwidth measurement failed: bandwidth did not settle within 20 runs, mem +150: the bandwidth measurement failed; treated as a driver reset, mem: the driver reset at +150; using +100, the best of the values that passed, mem: bandwidth peak +100, confirmed +0, applying +0, core: not searched; the driver reset earlier in this run, then the recovery (the driver was reset; reconnecting, the 20 s rest, preparing the stress load, health: STABLE 5425 it/s) and the soak at stock clocks, STABLE. RESULT: power 105 %, core +0 MHz (confirmed +0), mem +0 MHz (confirmed +0), driver resets during this run: 1, exit code 0, saved; total 469 s. The Windows System log shows no nvlddmkm event for the time of the run: the driver did not reset. Readings that did not agree were counted as a reset event, which ended the memory scan, skipped the core search and saved a profile without a clock offset. gao.exe did not crash, the journal had no begin without a complete, gao --status agreed with the result, the desktop stayed usable. Fixed after this run: bandwidth readings that do not settle end the memory scan at the best value measured and are no reset event (mem +N: the bandwidth readings did not settle; the memory scan ends here); a measurement that fails because the device was lost or a timing query failed still counts. Second run (2026-10-04, same card and driver, build 2d9d115, same script, the same programs open): Pass for the run; the reset criteria were not exercised, because this card's edge was a wrong result and the driver did not reset. Baseline STABLE 5679 it/s; power 105 %; memory: mem +0: bandwidth 440.2 GB/s, +50 445.2, +100 441.4, +150 447.0; at +200 the candidate was STABLE and the log showed bandwidth measurement failed: bandwidth did not settle within 20 runs, mem +200: the bandwidth readings did not settle; the memory scan ends here, then confirm core +0 / mem +50: STABLE, mem: bandwidth peak +50, confirmed +50, applying +0; no reconnect, no rest, no health probe. Core: +15 to +210 STABLE, 5587 to 5938 it/s; core +225 / mem +0: WRONG RESULT score=0 it/s peak=-1 C power=-1 W; confirm core +210 / mem +0: WRONG RESULT; confirm core +195 / mem +0: STABLE; core: highest stable +210, confirmed +195, applying +135; soak: 300 s at power 105 %, core +135, mem +0, STABLE 5795 it/s, peak 70 C, 200 W; RESULT: power 105 %, core +135 MHz (confirmed +195), mem +0 MHz (confirmed +50), exit code 0, saved; total 564 s (9.4 min). Before: 5679 it/s at 2762 MHz; after: 5795 it/s at 2907 MHz. gao.exe did not crash; gao --status agreed with the result; the journal had no begin without a complete; no new crash dump. Windows System log: no nvlddmkm event during the run. The desktop: a new program started and showed its window, no window was gone. Observation, not a result of this check: with other programs using the card the bandwidth readings are noisy on this desktop. Four memory scans on this evening ended at a peak of +350, +650, +100 and +50 (applied +200, +450, +0 and +0), each ended by a reading more than 1 % below the best or by readings that did not settle; the profile saved on 2026-09-27 with driver 610.74 had memory +950. The scan and the measurement are the same code as in v0.2.0. Third run (2026-10-04 23:45, same card and driver, build 2d9d115, started by the owner from the window of GpuAutoOptimizer.exe, elevated, Best of my GPU, with few other programs open): a real driver reset, in the memory search. What is recorded comes from the journal, gao --status and the Windows System log; the window's log was not kept. The memory candidates +50 to +2550 were all STABLE (+800 twice); no +2600 was journaled. Windows System log at 23:50:35-23:50:36: four nvlddmkm 153 events and one nvlddmkm 14, and none after them. The run went on: a memory candidate at +2250 STABLE (the confirm probe, four steps below +2450), then the soak entry at memory +1550, STABLE; no core candidate was journaled. Saved at 23:56: power 105 %, core +0 MHz, mem +1550 MHz; gao --status agreed; the journal had no begin without a complete; no new crash dump; the window application was still running afterwards. The reset handling held, and the result showed a flaw in the rule: after a reset in the memory search the core was not searched, so on this card, whose memory scan ends in a reset when the desktop is quiet, a run saved no core offset (the second run above, with a noisy scan that ended early, found core +135). Changed after this run: each clock has its own search, and the core is searched after a reset in the memory search. The desktop after the reset was not recorded. Fourth run (2026-10-05 00:17, same card and driver, build 93ede85, tools\hwchecks.ps1 -Checks full-best, no tray app running, few programs open): Pass for the run; no driver reset happened, so the new rule (the core is searched after a reset in the memory search) was not exercised on hardware. Baseline STABLE 5736 it/s; power 105 %. Memory: the bandwidth rose from 454.0 GB/s at +0 to 542.8 GB/s at +2500; three of the 51 readings were below the one before, by at most 0.4 %; no reading was repeated and none failed to settle; core +0 / mem +2550: WRONG RESULT score=5031 it/s; confirm core +0 / mem +2300: STABLE; mem: bandwidth peak +2300, confirmed +2300, applying +1600. Core, with memory +1600 applied: +15 to +210 STABLE, 5774 to 6132 it/s; core +225 / mem +1600: WRONG RESULT; confirm core +210 / mem +1600: STABLE; core: highest stable +210, confirmed +210, applying +135. soak: 300 s at power 105 %, core +135, mem +1600, STABLE 5996 it/s, peak 71 C, 204 W. RESULT: power 105 %, core +135 MHz (confirmed +210), mem +1600 MHz (confirmed +2300), exit code 0, saved; total 701 s (11.7 min). Before: 5736 it/s, core 2766 MHz, memory 10355 MHz; after: 5996 it/s, core 2899 MHz, memory 12100 MHz. gao --status agreed with the result; the journal had no begin without a complete; no new crash dump; no nvlddmkm event in the Windows System log during the run; a new program started and showed its window. On this card the memory edge is at +2550 in both runs with a quiet desktop: once a driver reset (third run), once a wrong result (this run). Seen twice more on 2026-10-08 with build 9ac1474 (see checks 49 and 50): the memory edge was at +2550 both times, once a wrong result and once a driver reset, and both runs carried on to a core result. |
| 53 | The stress load can be rebuilt | .\build\Release\gao.exe --stress-recreate |
no | Both runs STABLE (before and after), the rebuild time (recreate: ok, N ms) and the first batch's time (first batch: N ms, N iterations) printed, exit 0. The first batch should be near the 250 ms batch target and must have more than 0 iterations; first batch: DEVICE LOST or a count of wrong values there is a fail (exit 1). before includes the batch-size ramp from one dispatch and after does not, so a higher after score is not an effect of the rebuild. Loads the card for two 3 s runs; changes no setting. |
Pass (2026-10-04, reference RTX 4070, driver 617.14, build 87773c3, not elevated), three runs, all exit 0. before: STABLE 5094-5209 it/s, recreate: ok 3984-4178 ms, first batch: 419-459 ms with 1024 iterations, after: STABLE 5445-5628 it/s. The first batch takes about 250 ms longer than 1024 iterations do at the steady rate (about 190 ms): the first batch on a new device is slower than the batch target, by a fraction of a second, once per rebuild. |
| 54 | The window after its display device went away (the incident) | First exit the tray app that is running (tray menu, Exit) and confirm in Task Manager which executable is running: the app is single-instance, and a second GpuAutoOptimizer.exe --tray returns at once without a message, so the running build's behaviour would be recorded as the new build's. Then start the build under test, once, as the logon task starts it (elevated, --tray) and never open the window: on the first run the installed v0.2.0 (GpuAutoOptimizer.exe --tray from an elevated shell in its install folder), on the second run the new build (.\build\Release\GpuAutoOptimizer.exe --tray from an elevated shell in the repository root); reinstall the NVIDIA driver (or disable and enable the display adapter in Device Manager); click the notification or the tray icon |
yes (tray) | the process is still running; no new crash-*.dmp in %ProgramData%\GpuAutoOptimizer; the desktop is usable within one minute after the reset (the windows that were open are visible and usable, a new program can be started and shows its window, no sign-out or restart was needed, programs that crashed are noted); the owner's judgement: the machine feels normal afterwards. The window opens and keeps updating. Run it on the installed v0.2.0 first and record whether the crash reproduces there: without that, a pass on the new build proves nothing. |
|
| 55 | A reset with the window open | First exit the tray app that is running (tray menu, Exit) and confirm in Task Manager which executable is running: the app is single-instance, and a second start returns at once (with --tray without a message; without it, it only brings the running window forward), so the running build's behaviour would be recorded as the new build's. For the new build, start .\build\Release\GpuAutoOptimizer.exe --tray from an elevated shell and open the window from the tray icon. With the window visible and no search running, force a driver reset: dxcap -forcetdr |
yes | the process is still running; no new crash-*.dmp in %ProgramData%\GpuAutoOptimizer; the desktop is usable within one minute after the reset (the windows that were open are visible and usable, a new program can be started and shows its window, no sign-out or restart was needed, programs that crashed are noted); the owner's judgement: the machine feels normal afterwards. The window comes back within a few seconds and keeps updating; the Windows System log shows exactly one driver reset, the forced one. Whether this passes on v0.2.0 is not known (row 33 ran with the window hidden, where nothing is drawn): run it on v0.2.0 first as well and record the outcome. One more observation to record: recovery of the window's device now clears and flushes the old device context before creating the new one; record whether the window gets its picture back, because whether that works on a removed device is not known. |
New build: Pass (2026-10-04, reference RTX 4070, driver 617.14, build 2d9d115, started elevated without --tray, so the window opened by itself; no other tray app running; no search running; driven by a script that kept the window above the other windows, took screenshots of the screen and ran dxcap -forcetdr). Before the reset the window showed the dashboard with changing telemetry (core 2490 then 210 MHz). 5 s, 15 s and 40 s after the reset the window showed the dashboard again and the telemetry kept changing (core 210, 780, 210 MHz; power 14, 33, 17 W). The process was still running after 40 s; no new crash-*.dmp; Windows System log: three nvlddmkm 153 events within one second, the forced reset, and nothing else; a new program started and showed its window after 0.5 s, no window was gone. Not observed: whether the window lost its picture in the first seconds (the first screenshot is from 5 s after the reset). In a first attempt the window was covered by other windows and is on none of the screenshots; the process survived that reset too. Not run on v0.2.0. The owner's judgement is not recorded. |
| 56 | A reset during a run started from the window | Exit the running tray app first and confirm the executable, as in 54; start .\build\Release\GpuAutoOptimizer.exe from an elevated shell, pick Best, Optimize; once the core climb runs (core +15 / mem +N, core +30 / mem +N, ... in the window's log), run dxcap -forcetdr from an elevated shell; after the health: line, press Abort |
yes | the window's picture comes back; the window's log continues through preparing the stress load and a health: line; the Abort button works afterwards (the run ends with aborted, card at stock); the process is still running; no new crash-*.dmp in %ProgramData%\GpuAutoOptimizer; the desktop is usable within one minute after the reset (the windows that were open are visible and usable, a new program can be started and shows its window, no sign-out or restart was needed, programs that crashed are noted); the owner's judgement: the machine feels normal afterwards; the Windows System log shows exactly one driver reset, the forced one. |
|
| 57 | A refused profile at logon is no strike | set "mem_mhz": 99999 in the profile of gao.json; start GpuAutoOptimizer.exe --tray elevated; read the last line of boot.log and boot_strikes; restore the value |
yes | apply failed: profile values out of range; nothing applied, and boot_strikes is what it was before |
Pass (2026-10-05, reference RTX 4070, driver 617.14, build abc6419). boot_strikes 0 before; the tray start logged apply failed: profile values out of range; nothing applied; boot_strikes 0 after. With the copy installed before (a build of 2026-09-27) the same refusal at a real logon had left strikes: 1 of 3 five minutes later. |
| 58 | The logon copy follows a newer build | with apply-at-logon on and an older copy in Program Files, start the new GpuAutoOptimizer.exe elevated from its build folder; gao --status |
yes | boot copy: up to date; both installed executables carry the new version; a copy with a higher version is left alone |
Pass (2026-10-05, same machine and build). Before: boot copy: OUTDATED, installed 0.3.0. After the elevated start: boot copy: up to date, gao.exe and GpuAutoOptimizer.exe in Program Files version 0.3.1. Not checked: that a newer installed copy is left alone. |
| 59 | An older copy in the tray is replaced | with the released v0.3.0 GpuAutoOptimizer.exe --tray running elevated, start the new GpuAutoOptimizer.exe unelevated; answer Yes, accept the elevation prompt |
no, then yes | a question An older version of GPU Auto Optimizer is running in the tray. Replace it with version N?; after Yes and the prompt the old process is gone, the new window is open, the tune is still applied and boot.log shows the old copy's own exit (its strike cleared) |
Pass (2026-10-05, same machine and build). The running window had no version property; the question appeared; five seconds after Yes the app window belonged to a new process with version property 0.3.1 and was visible; one GpuAutoOptimizer.exe process; boot.log ran 2 minutes without a crash; strike cleared from the old copy's exit; applied: power 105 %, core +135 MHz, mem +1600 MHz; strikes: 0 of 3. Not checked: No as the answer, and a replace while the old copy runs an optimize. |
| 60 | The app updates itself | a build whose version is set below the latest release, in a folder of its own: start GpuAutoOptimizer.exe, wait for Update available at the bottom left, click it; and gao --update from the same kind of folder |
no | the line appears within seconds of the start; after the click both executables in the folder are the release's, the old running one is renamed aside, and the app is running again as the new version; gao --update prints the version found and updated to version N, exit 0 |
Pass (2026-10-05, same machine; a build of abc6419 with the version set to 0.2.9, latest release v0.3.0, unelevated, in a temporary folder). Window: Update available showed with the tooltip Version 0.3.0 is available (this is 0.2.9).; five seconds after the click a new process ran from the same folder with file version 0.3.0, gao.exe and GpuAutoOptimizer.exe were the release's sizes and version, and GpuAutoOptimizer.exe.old-N was the 0.2.9 copy. Command line: version 0.3.0 is available (this is 0.2.9); downloading, updated to version 0.3.0, exit 0; with the current version up to date. The installed copy, elevated (the same 0.2.9 build copied into Program Files and started with --tray, as the logon task does; the window opened and the line clicked by a script): five seconds after the click a new process ran from Program Files with file version 0.3.0; both installed executables were 0.3.0; the running one had been renamed aside; %ProgramData%\GpuAutoOptimizer\update was gone again; applied: power 105 %, core +135 MHz, mem +1600 MHz; strikes: 0 of 3, the old copy having cleared its strike on the way out. A download that does not match its SHA-256 is covered without hardware or network, by unit tests with a fake source (tests/test_update.cpp, run on this machine on 2026-10-05 with the rest of core_tests, 269 test cases passing): a source that serves another hash, or one wrong digit, is refused with the download does not match the published SHA-256; nothing was changed, and neither the unpack nor the replace step runs; a download that cannot be read back is refused with the download could not be read back to check its SHA-256; nothing was changed; a release with no usable published SHA-256 is refused before anything is downloaded. For those tests the order of the steps moved from app/common.cpp into install_release (core); the real download path that calls it was run again from the command line on 2026-10-08 (build f57d37b with the version set to 0.2.9, unelevated, in a temporary folder, latest release v0.3.1): version 0.3.1 is available (this is 0.2.9); downloading, updated to version 0.3.1, exit 0, both executables file version 0.3.1, the running gao.exe renamed aside, and a second gao --update said up to date. The click in the window and the elevated copy in Program Files were not repeated with this build; they call the same function. Not checked: an update from 0.3.1 to a later release (there is none yet), which is the first that restarts with --resume into a build that knows the flag. |
| 61 | The voltage/frequency curve is read | gao --curve |
no | the points of the graphics clock, lowest voltage first, on the card's frequency grid; a core offset that is applied shows as the same raw offset on every point; exit 0 | Pass (2026-10-08, reference RTX 4070, driver 617.14, build dbce226, not elevated). 127 points from 450 to 1240 mV, about 6 mV apart, every frequency a multiple of 15 MHz, the top at 3060.0 MHz. The saved tune (core +135 MHz) was applied: raw offset read 135000 on all 127 points. That settles two things the code did not assume: the read-out includes the offset (the card's own top is 2925 MHz), and the driver stores offsets in kHz one to one. It also shows that the core offset and the curve are one table. |
| 62 | A flat top written by hand | Exit the tray app (its watchdog re-applies the saved tune over the curve). Elevated: gao --reset, gao --curve; pick a point near 1000 mV and the frequency of the next point above it; gao --curve-flatten <mV> <MHz>; gao --curve; gao --stress 20; gao --reset; gao --curve |
yes | after the reset every raw offset is 0; --curve-flatten ends flat top applied ... -- OK, exit 0; in the curve after it the chosen point reads within 8 MHz, no point above it reads more than that, and the points below it are unchanged; under --stress the core clock stays at about ; the last reset puts every raw offset back to 0; the machine does not freeze |
Pass (2026-10-08, reference RTX 4070, driver 617.14, elevated, tray app exited, driven by a script; three runs, builds 816c12c, 9f975b5 and 9ac1474; each passed; the numbers are the first run's). After gao --reset all 127 raw offsets read 0. gao --curve-flatten 1000 2685 (the point at 1000 mV ran 2670 MHz at stock): flat top applied: slot 88 runs 2685 MHz, in 2 write rounds -- OK, exit 0. The curve then read 2685.0 MHz on every point from 1000 mV up, with raw offsets from +15000 at the anchor down to -240000 at the top, and the points below it unchanged with raw 0. gao --stress 20: STABLE, the core clock 2685 MHz for the first seconds and 2670 MHz once the card was at 57 C, about 155 W, against 2759 MHz at 190 W at stock. gao --reset: all raw offsets 0 again. The machine did not freeze; this was the first curve write on this card since the Python version. In the third run the same point read 2655 MHz at stock and the target was 2670 MHz: the built-in curve was one step lower on the warmer card. |
| 63 | The automatic undervolt | Tray app exited. gao --undervolt (elevated) to the end; then gao --curve, gao --stress 30, gao --reset |
yes | baseline:, target: N MHz, which the card reaches at V mV by itself, then one line per candidate, each a few mV lower with a higher +N MHz, all STABLE until the first that is not; a confirm line; undervolt: lowest stable ..., confirmed ..., applying ...; a soak line STABLE; RESULT: N MHz at A mV instead of V mV; in after: the clock is the baseline's within 30 MHz, the score at least 97 % of it and the power lower; the journal has no begin without a complete and its new entries carry "uv"; gao --reset returns the curve to stock |
Pass on the third run (2026-10-08, reference RTX 4070, driver 617.14, elevated, tray app exited, few programs open). First run (build 816c12c): no result. Baseline STABLE 5726 it/s at 2759 MHz, 190 W; the first candidate ended writing the curve failed: a point below the anchor moved. Between two reads the built-in curve had moved with temperature, and the write compared the frequencies of points it had not written. Cold against warm the read-out differs by up to 45 MHz per point, up at low voltages and down at high ones. Fixed in 9f975b5: untouched means without an offset. Second run (build 9f975b5): no result. Thirteen candidates from 1065 mV (+15 MHz) down to 990 mV (+150 MHz) were all STABLE at 2760 MHz, the power falling from 185 W at stock to 140 W; the fourteenth ended the card did not move the curve by what was written (asked 75 MHz, moved 45). Two reads a moment apart differ by up to two grid steps per point while the card warms or cools; that check was removed, and a candidate that cannot be set now only ends the descent (9ac1474). Third run (build 9ac1474), 440 s in all: baseline: STABLE score=5658 it/s clock=2747 MHz peak=65 C power=188 W; target: 2745 MHz, which the card reaches at 1060 mV by itself; twenty-one candidates from 1050 mV (+15 MHz) to 925 mV (+300 MHz), each a point lower, all STABLE at 2744 to 2749 MHz with the power falling from 172 W to 120 W; 920 mV (+315 MHz): WRONG RESULT; confirm 925 mV (+300 MHz): STABLE; undervolt: lowest stable 925 mV, confirmed 925 mV, applying 960 mV (+210 MHz at that point); soak 960 mV (+210 MHz): STABLE score=5584 it/s clock=2744 MHz peak=57 C power=141 W; RESULT: 2745 MHz at 960 mV instead of 1060 mV (lowest stable 925 mV, confirmed 925 mV), exit 0. Against the baseline: the same clock, 98.7 % of the score, 47 W less (25 %), 8 C cooler. gao --stress 30 afterwards: STABLE, 2745 MHz, 143 W, peak 57 C. The journal entries of the run carry "uv" and each begin has its complete (STABLE throughout, WRONG RESULT for +315). gao --reset returned every raw offset to 0. No nvlddmkm event in the Windows System log, no freeze. The raise that failed, +315 MHz at 920 mV, is higher than the +225 MHz at which a plain core offset fails on this card: the two are found at different voltages. The 3 s probes scored between 94 % and 100.5 % of the 30 s baseline at the same clock. |
| 64 | A stop request during an undervolt run | gao --undervolt (elevated); Ctrl+C during the candidates |
yes | RESULT: not applied -- aborted (card at stock) within about a second; gao --curve shows every raw offset 0; the last journal line is a complete |
Pass (2026-10-08, reference RTX 4070, driver 617.14, build 9ac1474, elevated, tray app exited, driven by a script). Ctrl+C was sent once five candidates had been journaled, during the sixth probe: abort requested -- stopping the probe and restoring stock, 1035 mV (+60 MHz): ABORTED, stopped: aborted -- card restored to stock, RESULT: not applied -- aborted (card at stock); gao.exe exited 0.4 s after the key press, exit code 1. The run added 5 begin and 5 complete lines to the journal, the last "verdict":"ABORTED". gao --curve then read 0 of 127 raw offsets other than 0, and gao --status applied: power 100 %, core +0 MHz, mem +0 MHz. |
| 65 | The curve and the core offset are one table | Elevated, tray app exited, after a flat top from check 62: gao --set-core 30, gao --curve; then gao --reset, gao --curve |
yes | to be observed and recorded, not assumed: what the table reads after a core offset is written over a flat top (replaced by a uniform offset, or added to it), and what gao --status reports as the applied core offset while a flat top is applied. After --reset: every raw offset 0 |
Observed (2026-10-08, reference RTX 4070, driver 617.14, three runs, the same every time). With a flat top applied, gao --set-core 30 printed requested 30 MHz, read back 30 MHz, OK, and the curve then read raw 30000 on all 127 points: a core offset replaces a flat top with one offset for every point; it is not added to it. gao --status showed applied: power 100 %, core +30 MHz, mem +0 MHz. After gao --reset every raw offset was 0. What the applied-state read-back reports while a flat top is on the card was not recorded. |
| 66 | The undervolt is saved as the profile | Tray app exited; keep a copy of %ProgramData%\GpuAutoOptimizer\gao.json. gao --undervolt (elevated) to the end; then gao --status, gao --curve, the profile object of gao.json, gao --stress 20; gao --reset and the copy of gao.json back |
yes | the run ends as in check 63, then Saved: ..., exit 0; gao.json has "preset": "undervolt", power 100, core 0, mem 0 and an undervolt object whose volt_uv and freq_khz are the voltage and clock of the RESULT: line and whose raise_khz is the +N MHz of the soak line; gao --status shows profile: undervolt: N MHz at A mV ...; to be recorded, not assumed: what applied: reports as the core offset while the flat top is on the card |
Pass (2026-10-08, reference RTX 4070, driver 617.14, a build of 244c0c7 with the first step of the branch undervolt-in-app, elevated, tray app exited, driven by a script). The run took 444 s: baseline: STABLE score=5637 it/s clock=2760 MHz peak=61 C power=185 W; target: 2760 MHz, which the card reaches at 1070 mV by itself; 925 mV (+315 MHz): WRONG RESULT; confirm 935 mV (+285 MHz): STABLE; undervolt: lowest stable 935 mV, confirmed 935 mV, applying 970 mV (+195 MHz at that point); soak 970 mV (+195 MHz): STABLE score=5582 it/s clock=2760 MHz peak=58 C power=144 W; RESULT: 2760 MHz at 970 mV instead of 1070 mV (lowest stable 935 mV, confirmed 935 mV); Saved: ...; The copy that starts at logon was updated to this version (0.3.1).; exit 0. gao.json: "preset": "undervolt", "power_pct": 100, "core_mhz": 0, "mem_mhz": 0, "undervolt": {"freq_khz": 2760000, "raise_khz": 195000, "volt_uv": 970000}, "boot_strikes": 0. gao --status: profile: undervolt: 2760 MHz at 970 mV, power 100 %, core +0 MHz, mem +0 MHz (driver 617.14, saved 2026-10-08 14:03) and applied: power 100 %, core +0 MHz, mem +0 MHz: the offset read-out reports a core offset of 0 while a flat top is on the card. gao --curve: 43 of 127 raw offsets other than 0, the lowest of them at 970 mV (raw 180000, 2760.0 MHz), no point above 2760 MHz; the anchor's offset was 180000 where the search had counted a raise of 195 MHz, the built-in curve reading one step higher at the soak's write than at the read after the baseline. gao --stress 20 with it applied: STABLE, 137 W on average, peak 57 C. gao --reset: every raw offset 0. The baseline ran 2760 MHz and the target was the point at 1070 mV; in check 63, on a warmer card, it was 2745 MHz at 1060 mV. The copy of gao.json was put back afterwards. |
| 67 | A saved undervolt is applied, by hand and at logon | Tray app exited; keep a copy of gao.json; put the profile check 66 saved in its place. Elevated: (A) gao --reset, gao --apply, gao --status, gao --curve, gao --stress 20; (B) gao --apply again; (C) gao --apply with, one at a time, volt_uv changed to a voltage the curve does not have, raise_khz set to 60000, core_mhz set to 30, and raise_khz removed; (D) the saved profile again, gao --reset, then GpuAutoOptimizer.exe --tray started elevated (what the logon task runs), boot.log, gao --status, exit from the tray; (E) the same start with the unknown voltage; (F) with the flat top applied, gao --apply with the overclock profile, then with the undervolt again, then with a profile that has core +0 and no undervolt. At the end gao --reset and the copy of gao.json back |
yes | (A) applied undervolt: N MHz at A mV ..., OK; --status shows undervolt: applied; the curve has offsets from A mV up and none below; the stress run is STABLE at the power of check 66. (B) the same again. (C) each ends not applied: ... with exit 1 and every raw offset 0: no point at that voltage, the raise it was tested with, a core offset next to an undervolt, and (the undervolt not whole) no saved profile. (D) boot.log: applied undervolt: ..., strike 1 clears in 2 minutes; undervolt: applied. (E) boot.log: apply failed: the undervolt was not applied: ... -- card at stock, the curve at stock, boot_strikes unchanged. (F) the overclock leaves the same offset on every point; the undervolt over it leaves the flat top; the profile with core +0 leaves every raw offset 0 |
Pass (2026-10-08, reference RTX 4070, driver 617.14, a build of 00fea70 with the second step of the branch undervolt-in-app, elevated, driven by a script, the profile of check 66: 2760 MHz at 970 mV, tested with a raise of 195 MHz). (A) After the reset 0 of 127 raw offsets were other than 0. applied undervolt: 2760 MHz at 970 mV, power 100 %, core +0 MHz, mem +0 MHz (driver 617.14, saved 2026-10-08 14:03), OK, exit 0, within a second. gao --status: applied: power 100 %, core +0 MHz, mem +0 MHz, undervolt: applied. The curve: 43 raw offsets other than 0, the lowest at 970 mV (raw 180000, 2760.0 MHz), no point above 2760 MHz. gao --stress 20: STABLE, 135 W on average, peak 55 C; the curve read the same after it. (B) The same line and the same curve. (C) not applied: the undervolt was not applied: the card's curve has no point at 971 mV -- card at stock; not applied: the undervolt was not applied: the curve point would be raised by 180 MHz; the undervolt was tested with 60 MHz -- card at stock; not applied: a core offset and an undervolt cannot be combined; nothing applied; not applied: no saved profile; run an optimize first (and gao --status: profile: none valid in ...). Exit 1 each time, every raw offset 0 afterwards, and undervolt: not applied (the curve is at stock). (D) boot.log: applied undervolt: 2760 MHz at 970 mV, ..., strike 1 clears in 2 minutes; undervolt: applied; the same curve as in (A). The strike read 0 ten seconds later, not 1: this start came from the build folder, found another build in Program Files and replaced it, and installing the logon copy resets the strikes (as it did before this branch). The start of the installed copy at the end of the script showed strikes: 1 of 3. (E) boot.log: apply failed: the undervolt was not applied: the card's curve has no point at 971 mV -- card at stock; every raw offset 0; boot_strikes 0 before and after. (F) The overclock (best: power 105 %, core +135 MHz, mem +1500 MHz) over the flat top: raw 135000 on all 127 points. The undervolt over that: the flat top of (A) again, applied: power 100 %, core +0 MHz, mem +0 MHz. A profile with core +0 over the flat top: every raw offset 0. Not done: a real log-off and log-on with the undervolt saved. |
| 68 | The watchdog and a saved undervolt | Keep a copy of gao.json; put the profile check 66 saved in its place; exit the tray app. Elevated: gao --reset, then GpuAutoOptimizer.exe --tray of this build. (A) wait 40 s. (B) four times: gao --set-core 15 and gao --set-core 0 right after each other (a uniform offset replaces the flat top, then none is left), wait for the next watchdog: line in boot.log, gao --status, gao --curve; then wait 45 s more. (C) start the tray app again, gao --set-core 30, wait 60 s. (D) no tray app: gao --apply, dxcap -forcetdr, gao --curve after 12 s. (E) the tray app again, dxcap -forcetdr, wait for the watchdog: line. At the end gao --reset and the copy of gao.json back |
yes | (A) boot.log: applied undervolt: ...; no watchdog: line while nothing changes. (B) the first three times watchdog: the tune had been reset (driver reset or TDR); re-applied within 30 s and undervolt: applied again; the fourth time watchdog: 4 resets within an hour; stopped re-applying, card at stock, every raw offset 0, and nothing is re-applied afterwards. (C) watchdog: another program changed the GPU settings; leaving them alone, and the offset is still there a minute later. (D) to be observed and recorded: whether a driver reset leaves the flat top on the curve. (E) the tray app is still running, no new crash-*.dmp, and the undervolt is applied again within a minute (or, if the driver kept it, was never gone); the Windows System log shows the forced reset and no other |
Pass (2026-10-08, reference RTX 4070, driver 617.14, a build of 0cd4b36 with the third step of the branch undervolt-in-app, elevated, driven by a script, the profile of check 66: 2760 MHz at 970 mV). (A) applied undervolt: 2760 MHz at 970 mV, ..., strike 1 clears in 2 minutes; undervolt: applied; no watchdog: line in 40 s. (B) After the two offset writes the curve read 0 of 127 raw offsets other than 0 each time. watchdog: the tune had been reset (driver reset or TDR); re-applied came 10, 27 and 28 s later, each followed by undervolt: applied and the flat top on the curve again (lowest offset at 970 mV, raw 180000, no point above 2760 MHz). The fourth time, 27 s later: watchdog: the driver reset 4 times within an hour; stopped re-applying, card at stock (that line now reads watchdog: 4 resets within an hour; stopped re-applying, card at stock: here it was the tune that had been reset, not the driver), undervolt: not applied (the curve is at stock), every raw offset 0; no further line in 45 s and the curve still at stock. (C) After gao --set-core 30 (raw 30000 on all 127 points): watchdog: another program changed the GPU settings; leaving them alone 20 s later; 35 s after that the curve still read 30000 on every point and gao --status applied: power 100 %, core +30 MHz, mem +0 MHz, undervolt: not applied (the curve carries other offsets). (D) Before the forced reset the flat top was on the curve; 12 s after it every raw offset read 0 and undervolt: not applied (the curve is at stock): a driver reset removes a flat top, as it removes a core offset. Windows System log: three nvlddmkm 153 events within two seconds. (E) 6 s after the forced reset the tray app was running and the curve was at stock; watchdog: the tune had been reset (driver reset or TDR); re-applied came 14 s after the script had started the reset, and the curve carried the flat top again (undervolt: applied). The tray app was still running, one crash-*.dmp in the state folder before and after, and the Windows System log showed three nvlddmkm 153 events within two seconds and nothing else. Not exercised on hardware: a reset that leaves the tune applied (it does not happen on this card and driver; unit tests cover the count), and four real driver resets within an hour. The desktop after the two forced resets is the owner's to judge. |
| 69 | The undervolt from the window | Keep a copy of gao.json; exit the tray app; start this build's GpuAutoOptimizer.exe elevated. Dashboard: the profile cards. Optimize page: select Undervolt, press Undervolt GPU, let the run end. Then the dashboard, gao --status, and gao --set-core 15 followed by gao --set-core 0 with the app still running; boot.log 40 s later. With a saved undervolt applied (gao --apply) start the app again and look at the dashboard. At the end gao --reset and the copy of gao.json back |
yes | the dashboard and the Optimize page show a fifth card, Undervolt, under the four clock profiles, its text not cut off; with it selected the button reads Undervolt GPU, the time About 8 minutes and the fan-curve choice is greyed out; the run screen is titled Finding the undervolt and lists the probes of check 63; the result screen says Applied: N MHz at A mV instead of V mV, has a before/after table and Saved.; gao --status shows the undervolt profile and undervolt: applied; after the two offset writes boot.log gets watchdog: the tune had been reset ...; re-applied (a run from the window leaves the watchdog on); the dashboard shows Applied now: Undervolt (the saved tune) and a row Undervolt N MHz at A mV |
Pass (2026-10-08, reference RTX 4070, driver 617.14, a build of ae2e6bc with the fourth step of the branch undervolt-in-app, started elevated by a script that took pictures of the window and clicked in it). The fifth card showed on both pages; on the dashboard its text was cut off after the first line in the first build and the card was made as tall as its text (seen in a second build). With the card selected: Undervolt GPU, About 8 minutes of full GPU load., the fan-curve row greyed out. The run, started with the click on the button: Finding the undervolt, baseline: 30 s at stock, one line per candidate down to 940 mV (+285 MHz): WRONG RESULT, confirm 945 mV (+270 MHz): STABLE, undervolt: lowest stable 945 mV, confirmed 945 mV, applying 985 mV (+180 MHz at that point), the soak. Result screen: Applied: 2775 MHz at 985 mV instead of 1075 mV, Lowest stable voltage: 945 mV, confirmed 945 mV.; score 5694 and 5681 it/s (-0.2 %), core clock 2774 and 2775 MHz, peak temperature 61 and 61 C, power 186 and 163 W (-12.4 %); Saved. It stays applied until reboot; .... gao --status: profile: undervolt: 2775 MHz at 985 mV, ... saved 2026-10-08 14:26, undervolt: applied. After the two offset writes the curve was at stock; watchdog: the tune had been reset (driver reset or TDR); re-applied came within 36 s and the flat top was back. With the profile of check 66 applied the dashboard read Applied now: Undervolt (the saved tune), Undervolt 2760 MHz at 970 mV, core and memory offset +0 MHz, power limit 100 %. Two things this run showed and did not settle. The saving was smaller than in checks 63 and 66 (-25 % and -22 %): a 60 s stress run with the result applied drew 148 to 151 W at 2775 MHz up to 56 C and 165 W from 59 C on, at the same clock; and with the card cold the curve points from 1020 mV up read 2790 MHz, one step above the flat top. The built-in curve moves with temperature under the offsets that were written, so the top does not stay flat. And the table compares a 30 s run at stock with a 300 s run: the temperatures are not like for like. |
| 70 | The run draws the curve, and the result says what it compares | As check 69, with a build that has the curve picture: start an undervolt run from the window and watch the run screen; then the result screen | yes | during the run a card Voltage/frequency curve shows the curve on the card over the card's own, changing as the candidates change: a flat top that starts one point further left each time, and a dot where the card runs now with the voltage in the legend; the result screen names the length of both runs in the table's headers, has a row Fan speed at the end, says under the table that the short stock run was not heated through, and shows the curve as the run left it, without the dot | Pass (2026-10-08, reference RTX 4070, driver 617.14, a build of 86a0d4c with the fifth step of the branch undervolt-in-app, started elevated by a script that took pictures of the window). 90 s into the run the picture showed the stock curve in grey, the written curve in blue leaving it near 975 mV and running flat at 2775 MHz to the right, and Now: 2775 MHz, which this curve reaches at 975 mV with the candidate 990 mV (+165 MHz) just passed in the probe list. Result screen: Applied: 2775 MHz at 985 mV instead of 1075 mV; headers Before (stock, 30 s) and After (5 min); score 5705 and 5583 it/s (-2.1 %), core clock 2775 and 2761 MHz, peak temperature 59 and 59 C, fan speed at the end 60 and 62 %, power 185 and 148 W (-20.0 %); the note about the short stock run; the curve as the run left it. In a first build the dot followed the idle card on the result screen (Now: 735 MHz ...); it is now drawn during a run only (seen compiled, not seen on screen). The curve picture during an overclock run (gao --optimize profiles from the window) was not looked at. |
| 71 | The stock measurement lasts as long as the soak | As check 69: start an undervolt run from the window; watch the top of the run screen during the first five minutes and during the soak; then the result screen | yes | the probe list starts baseline: 300 s at stock; above the telemetry tiles a line Measuring the card at stock with the time left (M:SS left of 5 min) and a bar that fills; the same for Confirming the lowest voltage and Soak: the result has to hold; nothing there during the short probes of the descent; the result table is headed Before (stock, 5 min) and After (5 min) and has no note under it about a short stock run; the stock column shows the temperature and fan speed of a card that is heated through |
Pass (2026-10-08, reference RTX 4070, driver 617.14, a build of e072da1 with the sixth step of the branch undervolt-in-app, started elevated by a script that took pictures of the window). 25 s after the click: baseline: 300 s at stock, Measuring the card at stock, 4:38 left of 5 min, the bar a little filled; the card ran 2805 MHz at 61 C and 194 W then. During the soak: Soak: the result has to hold, 4:07 left of 5 min. The whole run took about twelve and a half minutes. Result screen: Applied: 2790 MHz at 990 mV instead of 1095 mV, Lowest stable voltage: 950 mV, confirmed 950 mV.; headers Before (stock, 5 min) and After (5 min); score 5725 and 5715 it/s (-0.2 %), core clock 2788 and 2790 MHz, peak temperature 67 and 61 C, fan speed at the end 72 and 66 %, power 196 and 166 W (-15.3 %); no note under the table. Against the 30 s stock runs of checks 66, 69 and 70 (59 to 61 C, 185 to 186 W): the card at stock, heated through, was 6 to 8 C warmer and drew about 10 W more than those runs had shown. During the soak the curve picture read Now: 2790 MHz, which this curve reaches at 1020 mV, not the anchor's 990 mV: what check 69 had found from the power, seen directly (check 72 is about that). The line for the confirm probe was not caught in a picture. The optimize run of the four clock profiles still measures stock for 30 s: its score is the reference of the power search there, and its table keeps the note. |
| 72 | A flat top that stays the top on a warm card | Tray app exited, elevated. For each shape, several times and from different starting temperatures (a cool card; right after a gao --stress 150 at stock): gao --reset, gao --curve-flatten <mV> <MHz> 0 (exactly flat) or gao --curve-flatten <mV> <MHz> (the points above the anchor stored 45 MHz lower), gao --curve, gao --stress 210, gao --curve; compare clock and power second by second against the temperature. The same at stock, as the reference. Then gao --undervolt to the end and gao --stress 150 with its result applied |
yes | to be measured, and the lowered points kept only when it holds: with them the card draws no more than with the exactly flat top at every temperature reached, and clearly less where the flat top goes to a higher voltage; every stress run is STABLE; the write ends flat top applied ... -- OK; gao --undervolt ends with a soak that is STABLE at about the power of the stress runs. To be recorded: what the card reads back for points stored lower than the anchor |
Kept (2026-10-08, reference RTX 4070, driver 617.14, builds of ff328bc with the seventh step of the branch undervolt-in-app, elevated, tray app exited, driven by a script; anchor 990 mV, target 2790 MHz, the result of check 71). At stock, twice, 210 s: 194 to 197 W, 2805 MHz up to 63 C and 2790 MHz from 64 C on, peak 66 C, 5724 and 5717 it/s. Exactly flat, the same offsets on the card each time (one of 127 differed between the first two runs): from 54 C, 153 W at 2790 MHz up to 58 C, then 154 W at 2775 MHz, peak 59 C, 5659 it/s; from 56 C, 153 W at 57 C, 163 W at 59 C and 166 to 168 W at 60 to 61 C, 2790 MHz throughout, 5722 it/s; written on the warm card after a stock run, 154 to 155 W at 2775 MHz, 59 to 60 C, 5664 it/s. So the same curve ran in two ways once warm: on the anchor's voltage, one clock step lower, or on a higher voltage, holding the clock, for 12 to 14 W more. The soaks of checks 69 to 71 (163, 148 and 166 W) were those two ways too; in check 71 the picture named the voltage (1020 mV). What the card reads back: with the points above the anchor written 15 or 45 MHz lower over a flat top, their raw offsets read 15000 or 45000 lower and their frequencies still read 2790.0 MHz, the anchor's; after a stress run the anchor read 2775.0 MHz and so did they. The card reports for every point the most that it or any point below it runs. A first way of writing them (each point straight to its own target, checked by its frequency) therefore ended the curve did not settle on the target (the anchor reads 2790 MHz, the target is 2790) twice; the write is now flat first, then one write that lowers them, checked by the offsets. The points above stored 45 MHz lower (flat top applied: slot 86 runs 2790 MHz, in 2 write rounds -- OK each time): from 50 C, 150 to 152 W at 2790 MHz up to 55 C, then 152 to 155 W at 2775 MHz, peak 60 C, 5655 it/s; from 56 C, 153 to 155 W at 2775 MHz, peak 60 C, 5665 it/s; written on the warm card, 154 to 156 W at 2775 MHz, 58 to 60 C, 5663 it/s; and 45 s each with 15 and 45 MHz written by hand over a flat top, 153 to 155 W at 2775 MHz. Never above 156 W, every run STABLE. At the same clock (2775 MHz) the two shapes draw the same; what the lowered points do is keep the card from buying the last 15 MHz with 12 W. That costs about 1 % of the score against the flat top where that held the clock (5665 against 5722 it/s) and nothing where it did not. gao --undervolt with it, 12 minutes: baseline: STABLE score=5732 it/s clock=2788 MHz peak=67 C power=195 W; 940 mV (+300 MHz): WRONG RESULT; confirm 945 mV (+285 MHz): WRONG RESULT; confirm 950 mV (+270 MHz): STABLE; undervolt: lowest stable 945 mV, confirmed 950 mV, applying 990 mV (+180 MHz at that point); soak 990 mV (+180 MHz): STABLE score=5708 it/s clock=2778 MHz peak=59 C power=155 W; before: ... peak=67 C fan=72 % power=195 W (300 s), after: ... peak=59 C fan=64 % power=155 W (300 s): 40 W less (20.5 %) at 99.6 % of the score, against 30 W less (15.3 %) at 99.8 % with the flat top in check 71. gao --stress 150 afterwards: STABLE, 155 to 156 W at 2775 MHz, 57 to 60 C. Not reached: any temperature above 60 C with the lowered points (the card does not get warmer by itself with them), and another card. |
| 73 | A flat top over a core offset | Tray app exited, elevated. (A) gao --reset, gao --set-core 135, gao --curve, gao --stress 150. (B) gao --reset, gao --curve-flatten <mV> <MHz> 45 135 with the clock (A) settled on and a few curve points below where the offset curve reaches it; gao --curve, gao --status, gao --stress 150, gao --curve. (C) a gao.json whose profile has core_mhz 135 and an undervolt object for that point: gao --apply, gao --status, gao --curve; gao --apply again; gao --set-core 135 and gao --apply; gao --reset |
yes | to be observed and recorded: whether the card takes a flat top on top of a core offset and runs it. (B) flat top applied ... -- OK; the points below the anchor keep raw 135000, the anchor carries more, the points above less than the anchor; applied: still reports core +135 MHz; the stress run is STABLE at the clock of (A), or one step below it once warm, on clearly less power. (C) applied ..., undervolt: applied, the same curve as in (B) each time; after the reset every raw offset 0 |
Pass (2026-10-08, reference RTX 4070, driver 617.14, a build of c07677f with the first step of the branch all-in-one, elevated, tray app exited, driven by a script; power limit 100 %, memory at stock). (A) Raw 135000 on all 127 points. 150 s: 2955 MHz in the first seconds, 2925 MHz to 61 C, 2895 MHz from 63 C on, 196 to 199 W of 200 (the card sits on its power limit), peak 67 C, STABLE, 5965 it/s. The offset curve reaches 2895 MHz at 1050 mV. (B) gao --curve-flatten 1015 2895 45 135: flat top applied: slot 90 runs 2895 MHz, in 2 write rounds -- OK. The curve: raw 135000 from 450 mV up to 1010 mV, 210000 at 1015 mV (2895.0 MHz: 210 MHz over the built-in curve), 165000 and falling above it down to -45000 at 1240 mV, every point from 1015 mV up reading 2895.0 MHz. gao --status: applied: power 100 %, core +135 MHz, mem +0 MHz: the offset read-out keeps reporting the core offset under the flat top. 150 s: 2880 MHz throughout, 166 to 172 W, peak 62 C, STABLE, 5922 it/s (5950 once running); the curve then read 2880.0 MHz on the top. Against (A): 27 W less (13.5 %) and 5 C cooler for one clock step. Against stock (check 72: 195 W, 5732 it/s): 12 % less power at 3.8 % more score. (C) gao --apply: applied best: 2895 MHz at 1015 mV, power 100 %, core +135 MHz, mem +0 MHz ..., OK; undervolt: applied; 124 of 127 raw offsets other than 0, 135000 at 450 mV, no point above 2895 MHz. The same after a second gao --apply. gao --set-core 135 over it left 135000 on every point (an offset write of the value the driver already reports does rewrite the table here), and gao --apply put the flat top back. gao --reset: every raw offset 0. |
| 74 | The undervolt searched on top of the saved overclock | Tray app exited, elevated, with a saved overclock in gao.json (here best: power 105 %, core +135 MHz, mem +1500 MHz): gao --undervolt --on-overclock to the end; gao --status, gao --curve, gao --stress 150 |
yes | the log starts on top of the saved tune: ... and baseline: 300 s with the overclock; target: N MHz, which the card reaches at V mV with the overclock; candidates one point lower each, named by their raise over the built-in curve (the core offset included), until one fails; confirm, undervolt: ..., soak ...: STABLE; RESULT:, Saved:; the profile keeps its power limit and offsets and gains the undervolt object; applied: keeps reporting the core offset; the points below the anchor keep the core offset as their raw offset. To be measured: how much an undervolt adds to an overclock that already carries the safety margin |
Pass, and little gained (2026-10-08, reference RTX 4070, driver 617.14, a build of e292768 with the second step of the branch all-in-one, elevated, tray app exited, driven by a script). on top of the saved tune: power 105 %, core +135 MHz, mem +1500 MHz; baseline: 300 s with the overclock, STABLE score=5900 it/s clock=2906 MHz peak=68 C power=201 W; target: 2910 MHz, which the card reaches at 1085 mV with the overclock; ten candidates from 1075 mV (+150 MHz) to 1020 mV (+240 MHz) STABLE, the power falling from 174 to 150 W; 1015 mV (+255 MHz): WRONG RESULT; confirm 1020 mV (+240 MHz): WRONG RESULT; confirm 1025 mV (+225 MHz): STABLE; undervolt: lowest stable 1020 mV, confirmed 1025 mV, applying 1075 mV (+150 MHz at that point); soak 1075 mV (+150 MHz): STABLE score=5885 it/s clock=2895 MHz peak=67 C power=196 W; RESULT: 2910 MHz at 1075 mV instead of 1085 mV; before: ... peak=68 C fan=73 % power=201 W (300 s), after: ... peak=67 C fan=72 % power=196 W (300 s); Saved: ..., exit 0, 12 minutes. gao --status: profile: best: 2910 MHz at 1075 mV, power 105 %, core +135 MHz, mem +1500 MHz ..., applied: power 105 %, core +135 MHz, mem +1500 MHz. gao --stress 150: STABLE, 2895 MHz, 192 to 195 W of 210, peak 66 C. What it shows: 5 W of 201. With the margin of 70 % on the raise that held 30 s (225 MHz over the built-in curve) 150 MHz is left, and the overclock is 135 of it. An overclock already is an undervolt at every frequency: the whole curve runs more clock on the same voltage. The two are ends of one line, not gains that add up. (Without the margin, in check 73, the same clock ran at 1015 mV on 172 W: that point is 210 MHz over the built-in curve, one step inside what failed here.) One thing was wrong: the anchor at 1075 mV already ran the target with the core offset alone when the soak wrote it (its raw offset stayed 135000; only the points above were lowered), and gao --status said undervolt: not applied (the curve carries other offsets). curve_state now counts a flat top whose anchor needed no raise of its own (unit test; not run on the card again). |
| 75 | All in one, from the command line | Tray app exited, elevated, other programs that use the graphics card closed: gao --optimize all to the end; gao --status, gao --curve; then gao --stress 150 |
yes | five steps in the log: step 1 of 5: the card at stock, five minutes with a stock: line; step 2 of 5: the overclock and the log of an optimize run; step 3 of 5: the undervolt on top of it, at a clock between stock and the overclock with baseline: the overclock's own run: ... (nothing measured twice) and a target half-way between the stock clock and the overclock's; step 4 of 5 ...: fan N %: T C lines going down until one is too warm or the fans' minimum is reached, fan tune: N % holds T C, final test: 300 s with everything applied; RESULT: power ..., core ..., mem ..., N MHz at A mV, fans N % at T C, a stock: and a now: line of 300 s each and that is +x % score and -y % power against stock; Saved: ...; exit 0. The profile has the preset all, the overclock, the undervolt object and the tuned fan_curve; undervolt: applied |
Pass for the run, not for its numbers (2026-10-08, reference RTX 4070, driver 617.14, a build of 339c256 with the fourth step of the branch all-in-one, elevated, tray app exited, driven by a script; 32 minutes). Step 1: stock: STABLE score=5641 it/s clock=2790 MHz peak=67 C fan=72 % power=194 W. Step 2: mem: bandwidth peak +2300, confirmed +2300, applying +1600; core +225 / mem +1600: WRONG RESULT, confirm core +195 / mem +1600: STABLE, core: highest stable +210, confirmed +195, applying +135; soak: STABLE score=5887 it/s peak=70 C power=200 W. Step 3: baseline: the overclock's own run: STABLE score=5887 it/s clock=2897 MHz ...; target: 2850 MHz, which the card reaches at 1035 mV with the overclock (half-way between 2790 and 2897 is 2843; the curve's point under it); 1025 mV (+150 MHz): SLOW score=5393 it/s clock=2380 MHz (the one candidate below: the card took the curve and did not run it in those 3 s; not explained); confirm 1035 mV (+135 MHz): STABLE; undervolt: lowest stable 1035 mV, confirmed 1035 mV, applying 1035 mV (+135 MHz at that point): the overclock's curve cut off at 2850 MHz, no point raised further; soak 1035 mV (+135 MHz): STABLE score=5725 it/s clock=2835 MHz peak=63 C power=172 W. Step 4: fan tune: the lowest fan speed that keeps the card at 70 C under this load, from 70 %; fan 70 %: 58 C, 65 %: 59 C, 60 %: 61 C, 55 %: 65 C, 52 %: 67 C (52 % is the minimum this card's fans hold); fan tune: 52 % holds 67 C. Step 5 and the result: RESULT: power 105 %, core +135 MHz, mem +1600 MHz, 2850 MHz at 1035 mV, fans 52 % at 67 C; stock: score=5641 it/s core=2790 MHz mem=10493 MHz peak=67 C fan=72 % power=194 W (300 s); now: score=5365 it/s core=2834 MHz mem=12100 MHz peak=72 C fan=52 % power=173 W (300 s); that is -4.9 % score and -10.8 % power against stock; Saved, exit 0. gao --status: profile: all: 2850 MHz at 1035 mV, power 105 %, core +135 MHz, mem +1600 MHz ..., undervolt: applied. The score of the final test is not the tune's. Stress runs right after it, same settings: 5370 it/s at 61 C with the driver's fans, 5422 it/s at 71 C with the app driving the tuned curve, 5412 it/s again with the driver's fans: the same low score whatever the fans did, with seconds at 5700 to 5800 in between. Windows showed qemu-system-x86_64-headless (an emulator the owner had started) on 11 % of the card's 3D engine. Step 3's soak, minutes before it, is the clean number for these settings: 5725 it/s at 2835 MHz and 172 W against 5641 it/s at 2790 MHz and 194 W at stock, 1.5 % more score on 11 % less power. The run now says so itself when the final test scores more than 3 % under the soak before it at the same clock (not clean: ...), and the Optimize page asks to close other programs first. Two more things it showed. The final test ran with the fans standing at 52 %: the curve driver took the speed the tune had left for another program's and stepped aside (fixed: it now starts from driver control; seen in the stress run with the app, 71 C). And 52 % read 67 C when the tune judged it and 72 C five minutes later, above the 70 C it aims for: a speed is now given four looks instead of three before it counts as settled (unit test; not on the card yet). Still to do: the run from the window, and a run with nothing else on the card for numbers that compare. |
| 76 | All in one, from the window, with nothing else on the card | Close every program that uses the graphics card (check with Task Manager's GPU column, or Get-Counter '\GPU Engine(*engtype_3D)\Utilization Percentage'); exit the tray app; start this build's GpuAutoOptimizer.exe elevated. Optimize page: All in one is selected; press Optimize GPU and let the run end. Then the result screen, the dashboard and gao --status |
yes | the Optimize page shows the card All in one first, selected, About 40 minutes of full GPU load. and the line that asks to close other programs; the run screen is titled Optimizing: All in one, the probe list names the five steps, and a line with the time left and a bar shows during the stock measurement, the soaks, every fan speed and the final test; the curve picture shows the overclock above the card's own curve and, from step 3 on, cut off flat; the result screen says what was applied and what the fans do, has no note about a disturbed final test, and its table compares five minutes at stock with five minutes now: more score, less power, a lower fan speed; Saved.; the dashboard shows Applied now: All in one (the saved tune) |
Pass (2026-10-08, reference RTX 4070, driver 617.14, build dd2b84a, started elevated by a script that took pictures of the window and clicked in it; before the start no process was above 0.5 % on the card's 3D engine; 34 minutes). Optimize page: as expected. Run screen, 25 s in: step 1 of 5: the card at stock, five minutes, Measuring the card at stock, 4:38 left of 5 min, 2805 MHz, 60 C, 194 W. Nine minutes in: the memory scan of step 2 (core +0 / mem +1750: STABLE). During step 3: on top of the saved tune: power 105 %, core +135 MHz, mem +1500 MHz, baseline: the overclock's own run: STABLE score=5945 it/s clock=2897 MHz peak=70 C power=200 W, target: 2850 MHz, which the card reaches at 1035 mV with the overclock; Soak: the result has to hold, 1:21 left of 5 min; the curve picture with the written curve above the card's own up to 1035 mV and flat from there, Now: 2850 MHz, which this curve reaches at 1035 mV, 62 C, 176 W; then undervolt: lowest stable 1035 mV, confirmed 1035 mV, applying 1035 mV (+135 MHz at that point) and soak 1035 mV (+135 MHz): STABLE score=5847 it/s clock=2850 MHz peak=63 C power=176 W: again the overclock's curve cut off, no point raised further. Step 4: fan tune: the lowest fan speed that keeps the card at 70 C under this load, from 70 %, fan 70 %: 60 C; Fan speed 60 %: waiting for the temperature to settle, 2:27 left of 3 min; at 52 % the card read 70 C on 180 W. Result screen: Applied: core +135 MHz, memory +1500 MHz, power limit 105 %, 2850 MHz at 1035 mV; Fans: 52 % keeps the card at 70 C under full load; the fan curve goes through that point.; headers Before (stock, 5 min) and After (5 min); score 5728 and 5820 it/s (+1.6 %), core clock 2789 and 2835 MHz, memory clock 10495 and 12000 MHz, peak temperature 67 and 71 C, fan speed at the end 73 and 62 %, power 196 and 180 W (-8.2 %); no note; Saved. ...; the curve as the run left it. gao --status: profile: all: 2850 MHz at 1035 mV, power 105 %, core +135 MHz, mem +1500 MHz (..., saved 2026-10-08 20:09), undervolt: applied. Dashboard: Applied now: All in one (the saved tune), Undervolt 2850 MHz at 1035 mV, core +135 MHz, memory +1500 MHz, power limit 105 %; the fan tile 52 % (curve). What the numbers say: the same settings drew 176 W at 63 C with the driver's fans at about 68 % (step 3) and 180 W at 71 C with the tuned curve at 62 %: the quieter fans cost 4 W and 8 C, by choice (the tune aims for 70 C). The fan tile read (driver) while the run was setting the speeds by hand (the label only knows the app's own curve driver; not changed). Not seen: the note for a disturbed final test (there was none to give), and an abort from the window during this run. |
| 77 | The fan tile says who has the fans during a run | Start an All in one run from the window (elevated) and read the Fan speed tile in each step; then again when the run has ended | yes | step 1 (stock) and step 3 (the undervolt): N % (driver); step 2 (the overclock, once the card is warm enough for its fan curve): N % (run); step 4, while a fan speed is being judged: N % (tuning), with N the speed the line above the tiles names, and the text not cut off; step 5 (the final test): N % (run); after the run: N % (curve) once the app's own curve has the fans again, or (driver) while they are in the stop zone; never (driver) while the card reports a speed set by hand |
Pass for who has the fans; the last wording not seen in the fan step (2026-10-08, reference RTX 4070, driver 617.14, build a4da898, started elevated by a script that took pictures of the window; no process above 0.5 % on the card's 3D engine before the start; 33 minutes). Step 1: 57 % (driver). Step 2, nine minutes in: 61 % (run). Step 3, at the end of its soak: 66 % (driver). Step 4: with Fan speed 55 %: waiting for the temperature to settle above the tiles the tile read 55 % (measur, and with Fan speed 52 % 52 % (measur: the right state and the right speed, and the word cut off at the edge of the tile. Step 5: 55 % (run) under Final test: everything together. The word is now (tuning), as long as (driver), which fits, and a tile draws a value that is wider than itself smaller (348 unit tests pass, one of them on the length of the labels). The build with that change, started after the run with the card idle: 0 % (driver), the other tiles unchanged. Not seen on the card: the word (tuning) itself during a fan tune, the smaller drawing of a value that does not fit, and (curve) after a run (the fans were in their stop zone). The run itself, a second clean one: Applied: core +135 MHz, memory +1100 MHz, power limit 105 %, 2850 MHz at 1035 mV, Fans: 55 % keeps the card at 67 C under full load; score 5731 and 5781 it/s (+0.9 %), core clock 2792 and 2819 MHz, memory clock 10491 and 11601 MHz, peak temperature 67 and 71 C, fan speed at the end 72 and 55 %, power 195 and 174 W (-10.8 %); no note about a disturbed final test. Against check 76: the memory scan ended lower this time (+1100, there +1500), and the fan tune, now with four looks before a speed counts as settled, stopped at 55 % where it had gone to 52 %. |
| 78 | A saved run switches apply-at-logon on | Elevated: gao --boot off, gao --status (boot-apply: off); start this build's GpuAutoOptimizer.exe; Optimize page: the checkbox Keep the result applied at every logon is shown and ticked; start a run (Cool & efficient is the shortest) and let it end; the result screen, gao --status, the dashboard. Again with the box unticked |
yes | with the box ticked: the result screen ends Saved, and applied at every logon from now on. There is nothing left to do. and shows no Apply at every logon button; gao --status: boot-apply: on (logon task registered), boot copy: up to date; the dashboard's Apply at logon switch is on; the checkbox is gone from the Optimize page. With the box unticked: Saved. It stays applied until reboot; turn on apply at logon to keep it., the button, and boot-apply: off |
Pass with the box ticked; unticked not run (2026-10-08, reference RTX 4070, driver 617.14, a build of 846b48d with the checkbox, started elevated by a script that took pictures of the window and clicked in it). gao --boot off: boot-apply off: task removed, installed copy removed; boot-apply: off. The Optimize page showed the ticked box with Copies the app to Program Files and starts it in the tray at logon. next to it. Cool & efficient, 8 minutes: Applied: power 90 %, core +0 MHz, memory +0 MHz; headers Before (stock, 30 s) and After (5 min) with the note about the short stock run under the table; Saved, and applied at every logon from now on. There is nothing left to do., and only Revert to stock and Back under it. gao --status: profile: cool: power 90 %, ... saved 2026-10-08 23:14, boot-apply: on (logon task registered). The tray app started from Program Files at the end of the script applied the profile that was put back (boot copy: up to date). Not done: a run with the box unticked, and a real log-off and log-on after it. |
| 79 | The page scrolls with the mouse on any card | Start GpuAutoOptimizer.exe in a window that is too low for the whole dashboard (its default size is). Turn the mouse wheel with the cursor on the All in one card, on the Undervolt card, on one of the four profile cards and on the tuning card |
no | the page scrolls each time, as it does with the cursor between the cards | Pass for the two cards that failed (2026-10-08, reference RTX 4070, build of 6883a39 with the fix, version 0.4.0, started by a script that turned the wheel and took pictures). Before the fix the owner found that the page did not scroll with the mouse on the All in one card or on the Undervolt card: those two are as tall as their text, such a panel can be a pixel short of its contents, and it then kept the wheel for itself. Every card now passes the wheel on. With the cursor on the All in one card, two notches down: the card's top edge went from y 522 to y 355. With the cursor on the Undervolt card, one notch up: to y 439; one notch down again: back to y 355. The other cards were not tried with the script (they scrolled before). |
| 80 | A caught fault in an NVML call: no hang, the app restarts itself | With a local test build with a temporary patch that is never committed: --inject-nvml-fault=S gives one nvmlDeviceGetTemperature call a bad result pointer S seconds after the app started, so the call faults inside nvml.dll after NVML has counted it in. Not elevated, no other copy running. (a) GpuAutoOptimizer.exe --inject-nvml-fault=8, window open; (b) GpuAutoOptimizer.exe --tray --inject-nvml-fault=6; (c) GpuAutoOptimizer.exe --resume --after-fault --hidden --inject-nvml-fault=3. Watch the processes and their command lines |
no | (a) within 2 s of the fault a new copy runs with --resume --after-fault, the first copy has exited with code 0 and the new one responds, window open; (b) the same with --hidden added and no window; (c) the copy goes on running and responding on the library it no longer shuts down, and makes way for a new one only 60 s after it started |
Pass (2026-10-10, reference RTX 4070, driver 617.42, test build of c7f18c9). What it is for: on 2026-10-09 the window froze for good during an All in one run (v0.4.0 build; the NVIDIA installer had replaced driver 617.14 by 617.42 ninety seconds before the run, with the app running, and the process still had the 617.14 nvml.dll loaded). The driver reset at memory +2550 MHz, as it does at that edge; the card ended at stock. A dump of the frozen process showed the window's thread in nvmlShutdown, waiting for NVML's count of calls in flight to reach 0 while it held the lock nvmlInit takes, the run's thread spinning on that lock in nvmlInit, the count at 1 and no thread inside any NVML call: a call had been thrown out by an access violation that our guard caught. Reproduced on its own on 617.42 with a 40-line program: after a guarded nvmlDeviceGetTemperature with a bad result pointer, nvmlShutdown does not return (waited 5 s) and neither does an nvmlInit after it; without the fault both return at once. With the fix: (a) fault at 8 s, new copy --resume --after-fault at 8.3 s, first copy gone at 8.7 s with exit code 0, new copy responding with its window; (b) fault at 6 s, new copy --resume --after-fault --hidden at 6.4 s, no window; (c) fault at 3 s, still running and responding at 20 s, new copy at 60.7 s. Not covered here, see check 81: anything elevated (the boot.log line, a tune kept applied across the restart, a fault during a run). |
| 81 | A caught fault in an NVML call, elevated: the tune stays applied, a run ends properly | With the test build of check 80, elevated, no other copy running, a saved tune applied. (a) GpuAutoOptimizer.exe --tray --inject-nvml-fault=20, wait 60 s, gao --status, read the end of boot.log; (b) GpuAutoOptimizer.exe --inject-nvml-fault=120, start a run at once and leave the window open until the run has ended, then close the window |
yes | (a) boot.log has a call into the NVIDIA driver library faulted; restarting the app to load the library afresh; the tray icon is back within seconds and says tune kept applied; applied: equals the saved tune. (b) the run goes on after the fault at two minutes and ends at its result or at stock; its result stays on the screen; the app restarts only when the window is closed, and its icon is back in the tray |
(a) Pass (2026-10-10 22:18, reference RTX 4070, driver 617.42, a test build of 1410aa9, started and watched by an elevated script): the fault at 20 s; at 20.7 s a new copy runs with --resume --after-fault --hidden --watch=0 and the first is gone; boot.log has applied best: power 105 %, core +135 MHz, mem +1400 MHz ..., a call into a driver library faulted: ... nvml.dll+0x4c604 < ... and a call into the NVIDIA driver library faulted; restarting the app to load the library afresh; at 60 s applied: is the saved tune. Not looked at: the text of the tray icon (--watch=0 is what makes the new copy say tune kept applied). (b) Not as expected: the run does not go on. Tried twice with Best of my GPU, which now starts with five minutes at stock (check 82). Fault at 120 s, in that measurement: the run ended 2 s later, the card at stock. Fault at 400 s, in the search but at stock clocks: the run ended within a second with Not applied, the driver reset at stock clocks (NO TELEMETRY), The card is back at stock. One telemetry read that faults is taken for a driver reset, and at stock clocks that ends a run. The rest was as expected: the message stayed on the screen in the same process for 30 s, and 0.3 s after the window was closed a new copy ran with --resume --after-fault --hidden, with the restart line in boot.log. Not tried: a fault while a candidate is measured. (c) Found on the way, and the reason a run on this driver can end at stock: a Best of my GPU run with nothing injected (22:23) measured memory up to +2500 MHz as stable; then the driver reset (the user saw the screen go black). After that the first NVML call of every telemetry read, nvmlDeviceGetClockInfo, faulted inside nvml.dll (nvml.dll+0xf3ea0 < +0xf1dbb < +0x1767eb < +0x371c9), on the run's thread in all 15 reconnect attempts over 30 s and on the window's thread as well; boot.log: reconnecting to the driver failed 15 times; the last time: the telemetry read faulted. The run ended at stock, the saved tune was untouched. This is what the run of 12:38 the same day ended on (the driver did not come back after a reset), and on v0.4.0 a fault like it is what freezes the window (check 80). It does not happen at every reset: the run of 18:10 recovered from one, and a third run at 22:48 never reached one (check 82). The cause, and the fix: check 84. |
| 82 | The clock profiles measure stock for five minutes | Elevated, nothing else on the card: start this build's GpuAutoOptimizer.exe, choose Best of my GPU and run it to the end |
yes | the run screen first counts down Measuring the card at stock from five minutes, with the fan tile on (driver); the search starts after it with its own 30 s baseline; the result table says Before (stock, 5 min), has no line about a stock run that was too short, and its stock temperature and fan speed are those of a card heated through (on the reference card about 67 C and 72 %, as in checks 76 and 77) |
Pass (2026-10-10 22:48, reference RTX 4070, driver 617.42, a test build of 2f5ad7d that starts the run by itself and opens the Optimize page; pictures of the app's own window every 30 to 60 s): at 0:45 the run screen says Measuring the card at stock, 4:14 left of 5 min, fan tile 66 % (driver); the log then has stock: STABLE score=5705 it/s clock=2789 MHz peak=67 C fan=72 % power=195 W, baseline: 30 s at stock and baseline: STABLE score=5662 it/s. The run ended after 960 s: Applied: power 105 %, core +135 MHz, memory +800 MHz, Confirmed edges: core +195 MHz, memory +1150 MHz; the table has Before (stock, 5 min) 5705 it/s, 2789 MHz, 10494 MHz, 67 C, 72 %, 195 W and After (5 min) 5944 it/s (+4.2 %), 2908 MHz, 11300 MHz, 70 C, 65 %, 202 W, and no line about a stock run that was too short. The heading said Optimizing: All in one: the test build starts the run past the page's own choice. The memory search stopped at +1300 MHz this time, where the runs before it went on to +2500 MHz; why was not looked at. The run of 22:23 measured stock the same (67 C, 72 % (driver), 198 W at 4:45) and ended at a driver reset: check 81 (c). |
| 83 | The logon task through the Task Scheduler interface, and no install at start | (a) Elevated shell, this build: gao --boot on, gao --status, Get-ScheduledTask -TaskPath '\GpuAutoOptimizer\' -TaskName BootApply, gao --boot off, gao --status, gao --boot on; then look at Windows Security's protection history. (b) Start this build's GpuAutoOptimizer.exe without administrator rights, click Restart as administrator, wait a minute, then switch Apply at logon off and on; look at the protection history again |
yes | (a) the task exists with action C:\Program Files\GpuAutoOptimizer\GpuAutoOptimizer.exe --tray, the user's own account, highest run level, a logon trigger and no time limit; boot copy: up to date; after --boot off the task and the copy are gone; nothing in the protection history. (b) the app stays up after restarting itself elevated; switching the logon start off and on removes and installs the copy and the task; nothing in the protection history |
(a) Pass (2026-10-10 19:00, reference RTX 4070 PC, Windows 11 26200, Defender engine 1.1.26080.3, definitions 1.459.655.0, run from an elevated PowerShell script): boot-apply on: ... --tray runs at every logon; the scheduler shows action C:\Program Files\GpuAutoOptimizer\GpuAutoOptimizer.exe --tray, the principal is the user's own account, run level Highest, logon type Interactive, MSFT_TaskLogonTrigger, limit PT0S, state Ready; boot copy: up to date; --boot off: task removed, installed copy removed, task present False; on again: exit 0; no detection in the 20 s after it. (b) Pass, on one run (2026-10-10 21:44, same PC, engine and definitions): the user reported no Defender message. Read afterwards: the task file and gao.json were rewritten and the copy in Program Files was created anew at 21:44:28, which only an elevated process can do; Defender logged no event and no detection between 21:00 and 21:46; at 21:46 the build, the copy and the task were still there and the build's GpuAutoOptimizer.exe was running elevated. The logs do not show the elevated copy the app started with its button: the process running at 21:46 had started at 21:44:44, 16 s after the switch, with Explorer as its parent. The user started it that second time, as administrator, after going through the steps. One clean run does not show that the change is what made the difference: the verdict comes from a cloud model. What it was for: at 18:48 the same day the build before this change was started without administrator rights and restarted itself elevated with its button. 1.07 s after the elevated copy started, Defender got a cloud verdict (FastPath) Behavior:Win32/Execution.A!ml on it, quarantined the build, and a minute later removed the copy it had installed in Program Files and the logon task. In that second the copy had run schtasks /Query, copied itself to Program Files and run schtasks /Create. Defender's log does not say which step it was. Starts that did only part of it were not detected: the 0.3.1 zip restarting itself elevated on 2026-10-09 (it installed nothing: the logon copy was newer), a build installing itself when started from an elevated PowerShell on 2026-10-08, and test builds that install nothing on 2026-10-10. |
| 84 | NVML across a driver reset: nothing faults, and the app and a run go on | Elevated. A driver reset is made as a search makes one: gao --set-mem 2800 (2700 when that resets at once), then gao --stress 20; each reset is one nvlddmkm event 14 in the System log. (a) A standalone probe, not part of the project: one process that calls nvmlDeviceGetHandleByIndex_v2, nvmlDeviceGetClockInfo (core and memory), nvmlDeviceGetTemperature, nvmlDeviceGetPowerUsage, nvmlDeviceGetPowerManagementLimit and nvmlDeviceGetFanSpeed every 100 ms, each under its own guard, with a second thread every second, and once in a loop without a pause; first as it is, then with the rule of this build. (b) This build's GpuAutoOptimizer.exe --tray with a saved tune, and three resets; watch the process, boot.log and gao --status, then open the window. (c) A Best of my GPU run that reaches a reset at the memory edge: a test build whose memory climb starts at +2750 MHz. (d) A Best of my GPU run of this build with nothing done to it |
yes | (a) as it is: the readings fault after a reset; with the rule: none does, and the readings come back. (b) the same process goes on, boot.log has the watchdog's line for every reset and no line about a fault, the tune is applied again and the window shows readings. (c) the run says the driver was reset, reconnects, goes on and ends at its result. (d) the run ends at its result |
Pass (2026-10-11, reference RTX 4070, driver 617.42, nvml.dll 8.17.16.1742). (a) As it is, 4 resets in 2 processes: from each process's first reset on, both clock calls, the temperature and the power usage faulted at every call until the process ended (1,319 and 1,177 times each), always a read of the same block of memory at nvml.dll+0xf3ea0; the power limit and the fan speed answered 999; the handle call went on answering 0; nvmlShutdown at the end did not return within 5 s. The faulting instruction reads memory the driver shares with the process, in a retry loop on a sequence number. With the power limit alone asked first: 3 resets at 100 ms, no fault, 3 new sessions; in the loop without a pause 1 fault in 2 resets (power limit 0, fan speed 999, then the readings faulted, all in one round). With both asked first: 4 resets in the loop without a pause, 800,000 rounds, no fault; the new session took between 15 ms and 8 s to start, and once longer than 8 s. The fan speed call sometimes answers 27 (not ready) for seconds before a reset; in the passes before this one the readings still worked then. In this last pass the probe read nothing in such a round either, which this build does not do: that happened before one of its four resets. A reset forced through D3DKMT_ESCAPE_TDRDBGCTRL is refused on this Windows (0xC00000BB), and these resets are not logged as Display 4101. (b) Three resets at +2800: the same process throughout; boot.log has watchdog: the tune had been reset (driver reset or TDR); re-applied three times and nothing else; applied: is the saved tune after each; the window afterwards shows 660 MHz, 5001 MHz, 35 C, 32 of 210 W and 0 % (driver); the app exits when asked. (c) The stock measurement and the baseline as in check 82; at 420 s the driver reset in the candidate at +2800 (journal: DEVICE LOST); the log: mem: the driver reset at +2800; using +2750, the last value that passed, the driver was reset; reconnecting, resting 20 s before the card is loaded again, checking that the card is back: 5 s at stock, health: STABLE; then +2550 confirmed, the core climbed to +195, and the run ended after 861 s with best: power 105 %, core +135 MHz, mem +1750 MHz saved and applied; nothing new in boot.log. This is the reset the run of 2026-10-10 22:23 ended on (check 81 (c)). The result was not kept: memory was not searched by bandwidth in this test build. A run of the same build that started at +2400 met no reset: +2600 was a wrong result, and so were the three confirm tries below it. (d) 926 s, no reset: best: power 105 %, core +135 MHz, mem +650 MHz, nothing new in boot.log. Not covered: another card or driver; a card that goes between the two calls and the readings, a matter of microseconds, after which the fault is caught as in check 80. During a reset the thread that starts the new session waits for the library, the window's thread as well; before this build a reading waited there as long. |
| 85 | The installed app is in the Start menu, and an update of the copy in Program Files asks for administrator rights | With a test build of this branch numbered 0.4.0, so that the update to 0.4.1 is on offer. (a) Elevated: gao --boot on and look in the Start menu's Programs folder for all users; delete the entry by hand and start the installed GpuAutoOptimizer.exe --resume --hidden, as the copy after an update starts; gao --boot off; gao --boot on again. (b) Not elevated: open Start, type gpu auto, start the app; click Update available |
(a) yes, (b) no | (a) GPU Auto Optimizer.lnk is there and points at the installed GpuAutoOptimizer.exe; the copy started with --resume makes it again; after --boot off it is gone. (b) Windows' search finds the app; its window opens without administrator rights; the click asks for them, and the elevated copy downloads the update, installs it and starts as the new version |
Pass (2026-10-11, reference RTX 4070 PC, Windows 11 26200). (a) 10:27, from an elevated script: before, no entry; after --boot on, entry -> C:\Program Files\GpuAutoOptimizer\GpuAutoOptimizer.exe; removed by hand and there again 8 s after the start with --resume; after --boot off: task removed, installed copy removed, no entry; after --boot on again the entry is back. No Defender detection in the 15 s after it. (b) The user went through the three steps and ticked them off without a remark. Found afterwards: both executables in Program Files are byte for byte those of the v0.4.1 release zip, the app runs elevated since 10:43:37, the Start menu entry is there, and Defender logged nothing since 10:20. The prompt and the window themselves were seen by the user only. What it is for: earlier that morning 0.4.0, started from Program Files without administrator rights, failed its update with could not copy ...\GpuAutoOptimizer-update\files\gao.exe to C:\Program Files\GpuAutoOptimizer (error 5), and Windows' search found no app: the only entry on this PC pointed at a predecessor of this program. The copy this leaves installed is the released 0.4.1, which has neither change. |
| 86 | After a driver update the saved tune is tested again and applied, or not | Elevated, the tray app exited, a copy of gao.json kept. A driver update is imitated by writing another version into the profile ("driver": "617.14") and gao --reset. (a) gao --status, gao --apply; start GpuAutoOptimizer.exe and look at the dashboard. (b) Click Test the tune (5 min) and leave it to the end; look at the result, the dashboard, gao --status and boot.log. (c) Write the old version again and a memory offset the card does not hold ("mem_mhz": 2800), then gao --retest; gao --status. Put the copy of gao.json back. (d) Not imitated: log on after a real driver update with apply-at-logon on |
yes | (a) --apply refuses with the driver was updated (617.14 -> ...); the dashboard says the tune was tested on 617.14 and is not applied until it has been tested again, and its button reads Test the tune (5 min). (b) The Optimize page counts Testing the saved tune down from five minutes; then The tune holds; the dashboard shows the tune as applied and the button reads Apply saved tune again; driver: matches; boot.log has tested again on driver .... (c) RESULT: not applied -- the tune did not hold on driver ..., the card at stock, and the profile in gao.json as it was written: still 617.14, still refused. (d) a balloon says the driver was updated and the tune is not applied; a click on it opens the window |
Pass for (a) to (c), with one fault in the window (2026-10-11, reference RTX 4070, driver 617.42, a test build of the branch on top of 0.4.5, started elevated by a script that took pictures of the window). (a) gao --apply: not applied: the driver was updated (617.14 -> 617.42); test the tune again in the app (5 minutes, or gao --retest), or optimize again; the dashboard shows the card at stock and the button Test the tune (5 min). The fault: the line under the saved tune was cut off after this tune was tested on 617.14,: the card counted rows, and this one wraps over three lines. Fixed since (the card measures its text), and seen whole in a picture of the dashboard taken without elevation, with a test build that shows the saved tune as tested on 617.14. (b) The test ran 301 s under Testing the saved tune with its countdown; then The tune holds, Five minutes of full load on driver 617.42 (it was tested on 617.14): stable. It is applied again: core +135 MHz, memory +650 MHz, power limit 105 %, score 5742 it/s, peak 70 C; the dashboard after it shows the tune as applied and Apply saved tune; driver: 617.42 (matches); boot.log: tested again on driver 617.42 (it was tested on 617.14): applied best: .... (c) With "mem_mhz": 2800: NO TELEMETRY ... (5 s), RESULT: not applied -- the tune did not hold on driver 617.42 (NO TELEMETRY) -- card at stock; the profile still says 617.14 and +2800 and is still refused; applied: power 100 %, core +0 MHz, mem +0 MHz. The driver had reset and the command came back by itself. (d) not run. |
| 87 | A card that gets hot at stock is not refused, and the undervolt search has the fans | Elevated, the tray app exited, a copy of gao.json kept. (a) With a test build whose stock limit is lowered to a temperature the reference card passes under load (kStockTempC 62; the card reaches 67 C at stock): gao --optimize cool to the end. (b) With this build as it is: gao --undervolt to the end, reading the fan speed and the temperature every ten seconds (nvidia-smi --query-gpu=temperature.gpu,fan.speed); then gao --status and gao.json. Put the copy of gao.json back. (c) Not possible here: a card that reaches 85 C at stock with the driver's fans |
yes | (a) stock: TOO HOT ... peak=63 C, then the card reached 63 C at stock with the driver's own fan control (fans at N %): the measurement stopped there, after N s. The run goes on with the fans on a curve., then baseline: and a run that ends in RESULT:; before: shows the shortened duration. (b) from the first probe on the fan speed follows the curve in use for the temperature read with it, and reaches the driver's own speed only by coincidence; no probe ends TOO HOT; the run ends in RESULT:; afterwards fans: driver control until the tray app takes them, and gao.json has "fan_control": true. (c) on the 3080 Ti of the report: the run gets past the measurement at stock |
(a) Pass (2026-10-11, reference RTX 4070, driver 617.42, a test build of the fix with the stock limit at 63 C, started elevated by a script): stock: TOO HOT ... peak=64 C fan=66 %, the line that the measurement stopped there after 99 s, then baseline:, nine power steps, the soak and RESULT: power 85 %; before: shows (99 s). (b) Pass (same day, same build without the lowered limit): baseline: 300 s at stock ... peak=68 C, 28 candidates, none TOO HOT, RESULT: 2790 MHz at 990 mV instead of 1100 mV; the fans read 61 % at 68 C at the end of the baseline and 53 % at 63 C at the end of the soak, which is the curve in use (60 C 45 %, 70 C 65 %, learned minimum 52 %); fans: driver control afterwards and "fan_control": true. The ten-second samples were not recorded: nvidia-smi wrote nothing to its file. (c) Pass for the measurement at stock (same day, a tester's RTX 3080 Ti Founders Edition, driver 610.88, v0.4.3, from pictures of the window): stock: TOO HOT ... peak=86 C fan=66 %, stopped after 148 s, and the run went on with the fans at 100 % (run). That run then froze the machine in the memory search: see the decision "Without the bandwidth measurement the memory is left at stock". |
| 88 | A run refuses to start next to Afterburner or RivaTuner; the lines of a run are in run.log; the list of probes fills the window |
(a) Not elevated: start a harmless program from a copy named RTSS.exe, then gao --optimize best; the same with a copy named MSIAfterburner.exe; then with neither. (b) Elevated, the tray app exited: any run (gao --optimize cool); read %ProgramData%\GpuAutoOptimizer\run.log while it runs and after it. (c) A run from the window, with the window taller than its content. (d) Not possible on the reference card without a test build: a bandwidth measurement that fails at +0 |
(b), (c) | (a) RivaTuner Statistics Server (RTSS) is running. Close it, ..., then MSI Afterburner is running. Close it, ..., exit code 1, nothing written to the card; with neither: optimizing changes clocks and power limits and needs administrator rights. (b) a line === gpu-auto-optimizer <version>, then every line the run printed, each with its time, the newest one already there while its step runs. (c) the Probes card reaches the bottom of the window and shows as many lines as fit. (d) the bandwidth measurement does not work here; the memory is not overclocked, mem: left at stock, and the core is searched |
(a) Pass (2026-10-11, reference PC, this branch's gao, not elevated, with a copy of cmd.exe under each name): all of --optimize best, --undervolt and --optimize all refuse with the tool's name and exit 1; with neither running the answer is the one about administrator rights. In the window (same day, a test build of this branch that starts a run by itself, elevated, pictures of the window only): with a program named MSIAfterburner.exe running the Optimize page shows Not started and the sentence that names MSI Afterburner; the saved tune stays applied and run.log gets its header and nothing else. (b) Pass: twenty seconds into the run run.log holds === gpu-auto-optimizer 0.4.3 and the card at stock, five minutes, each with its time; after the stop the line stock: ABORTED ... is there as well. (c) Pass: in a window 1460 pixels tall the Probes card reaches the bottom of the window. (d) not run. |
| 89 | Where the NVIDIA app keeps Automatic Tuning, and whether that can be relied on | Reference PC, NVIDIA app 11.0.9.251, driver 617.42. A script that only reads (a copy of nvtopps.db3, the NVIDIA registry keys, the times of the app's settings files, the NVIDIA processes) every two seconds, while Automatic Tuning is switched on in the NVIDIA app (System, Performance). Then gao --status of this branch, and nvidia_auto_tuning_in on a copy of the database taken while the switch was on |
no | the switch shows in something that can be read, at the moment it is flipped, and what is read is what the driver's service acts on | Pass for Automatic Tuning, with one part not measured; nothing found for the overlay (2026-10-11). Switched on at 14:19:09: in the same second ocScannerSettings.backgroundScanEnabled of this card went 0 to 1 (grdOcEnabled stayed 0), a row appeared in ocScannerResults, the value HKLM\SOFTWARE\NVIDIA Corporation\Global\NvApp\OC\<user SID> = 1 was created, and nvtopps.log said ScannerMode starting scan. No file of the NVIDIA app and no other registry value changed with it. The switch could not be put off again in the app: STOP SCAN ended the scan (stopping scan early) and the next one started 14 ms later, and the switch is locked while a scan runs. After the NVIDIA services restarted (14:27:57) the scan resumed two seconds later, the service stopped answering, and the app showed every reading as N/A and the switch as off, while the field still read 1. With the two NVIDIA services stopped, the field set back to 0 and the registry value removed (both by hand, elevated, with a copy of the database kept), the services started without a scan. So the field is what the service acts on. gao --status then read nvidia app: Automatic Tuning off from the live file, and the reader returned on for the copy taken at 14:33 while the field was 1, and off for the other card in that file. Not measured: switching off through the app; grdOcEnabled set (no scan ever finished); a PC without the NVIDIA app; another driver. The overlay switch was not flipped; see the decision row for what was looked at. |
| 90 | Does a lowered power limit make the memory-bandwidth measurement fail? | Elevated, the tray app exited, a copy of gao.json kept. For a power limit of 100, 80 and 65 %, with both clocks at stock (a saved profile with power_pct N and offsets 0, gao --reset, gao --apply): five times gao --bandwidth --after-load 10, which loads the card for ten seconds and measures on the same device right after, as the memory search does after a probe. Put the copy of gao.json back and gao --apply |
yes | the question, not an expectation. Per power limit: how many of the five measurements gave a number; for each, how many runs it took (runs:), between which values they lay and how many were thrown away for a clock change; and what the card drew while it measured (after the measurement: ... power=N/M W), which says whether the measurement ran into the limit at all. All fifteen give a number within a few runs: the power limit is not why it failed on the tester's card. Failures or long, scattered measurements at 80 or 65 % only: it is, and the each: lines say which of the two ways (see the decision "Open: why the bandwidth measurement fails on some cards"). Not reproducible here either way: a card that throttles on temperature |
No (2026-10-11, reference RTX 4070, driver 617.42, build 94ee36b, started elevated by a script). All fifteen measurements gave a number, in three runs (thirteen times) or four (twice, at 65 %); no run was thrown away for a clock change; the readings of one measurement lay within 0.2 to 0.8 % of each other, and within 1.1 and 1.2 % in the two that took four runs. The bandwidth did not move with the power limit: 429.2 to 430.5 GB/s at 100 %, 425.5 to 427.4 at 80 %, 427.4 to 428.6 at 65 %. The card was held back as intended: under the load it drew 164, 144 and 119 W and its core clock averaged 2706, 2608 and 2441 MHz (score 5319, 5109, 4850 it/s), and after a measurement at 65 % it read up to 127 of 130 W. The copy is bound by the memory, not by the core the limiter slows. So a power limit of 65 % is not why the measurement failed on the tester's card. On that card itself the measurement works as well (same day, RTX 3080 Ti Founders Edition, v0.4.5, gao --bandwidth --after-load 30 with the card at its owner's power limit of 70 %, set in MSI Afterburner): load: STABLE ... peak=71 C avg power=237 W avg core=1628 MHz, power=241/245 W after the measurement, runs: 3, 3 readings from 815.9 to 817.6 GB/s (spread 0.2 %). So the failure in the 0.4.3 run is not reproduced outside a run. What differed then: the app had set the limit to 65 %, the card had just come down from 83 C, the driver was 610.88 (whether it has been updated since is not confirmed), another program was open on the card, and it was measured inside a run. Earlier the same day, without elevation, at the tune that was applied (105 %): runs: 3, 3 readings from 453.1 to 455.7 GB/s (spread 0.6 %) after five seconds of load, with power=179/210 W after the measurement; without the load before it, the first of four runs read 91.3 GB/s, the card not clocked up yet (2026-10-11, reference RTX 4070, driver 617.42). |
| 91 | A whole run on a second card: the tester's RTX 3080 Ti Founders Edition | v0.4.5, Best of my GPU from the window, MSI Afterburner closed; the result screen and run.log sent by the tester |
yes | the run gets through, the memory search never goes past where the bandwidth stops rising, and run.log says what happened |
Pass for the run, with three findings (2026-10-11, from run.log and a picture of the result). Stock stopped at 86 C after 159 s with the driver's fans at 66 % and the run went on. Power: 65 % (73 C; 70 % read 80 C). The journal held the earlier freeze: a previous run froze the machine at mem +2200; staying below it from now on. Memory: mem +0: bandwidth 795.5 GB/s, then at +50 bandwidth did not settle within 20 runs: 20 readings from 763.9 to 821.2 GB/s (spread 7.5 %), so the scan ended with +0 and nothing was thrown away for a clock change. Core: stable to +285, DEVICE LOST at +300, the driver came back (reconnect, 20 s rest, health STABLE), confirmed +225, applied +150, soak STABLE. Result: power 65 %, core +150, memory +0; 4749 it/s where stock scored 4937 (-3.8 %), 73 C instead of 86, 226 W instead of 287, fans at 80 % instead of 66 %. Finding 1: inside a run the bandwidth readings of this card scatter by 7.5 % within about a second, where the same card read 815.9 to 817.6 GB/s after thirty seconds of load (check 90). The measurement comes three seconds after the load starts; the confirm probe below drew 302 W in its first second at a limit of 227 W, so the power limiter had not caught up yet. Not confirmed. Finding 2: confirm core +225 / mem +0: TOO HOT ... peak=76 C power=302 W ended within a second, because the health probe after the reset runs at stock power and had heated the card to 76 C; a confirm probe that is too hot counts as holding, so +225 was confirmed by a probe that tested nothing. The soak at +150 did run its five minutes. Changed since: a confirm probe that is too hot before the clock was loaded for ten seconds does not confirm, and the card rests before the next, lower try (see the decision "A confirm probe that is too hot at once confirms nothing"). On a card: not run yet; it needs a driver reset on a card that runs hot. Finding 3: the result is slower and louder than stock, as the open design point in docs/planned.md expects for a card that is above the profile's 75 C at stock. |
| 92 | The undervolt profile starts with the measurement at stock every run uses, and a hot card is not refused | Elevated, the tray app exited, a copy of gao.json kept. (a) gao --undervolt to the end; then gao --status. (b) The same with a test build whose stock limit is lowered to a temperature the reference card passes under load (63 C; it reaches 67). Put the copy of gao.json back. (c) Only on a card that is above the profile's limit at stock with the fans at 100 % (the tester's RTX 3080 Ti: 82 to 83 C): the Undervolt profile from the window, to the end, and its run.log |
yes | (a) the card at stock, five minutes, a stock: STABLE ... line with the driver's fan speed, then baseline: the measurement at stock, and no second five minutes at stock: the first candidate follows at once; no candidates are stopped at line; RESULT:; before: shows 300 s and the fan speed the driver chose. (b) stock: TOO HOT ... peak=64 C, the line that the measurement stopped there, the cool-down, then baseline: the measurement at stock and a search that runs to RESULT: with before: showing the shortened time; no candidates are stopped at line, because 64 C is under the profile's 75. (c) no not stable at stock; the card reached 86 C at stock: candidates are stopped at 85 C, not at the profile's 75 C; candidates that end STABLE or WRONG RESULT, none TOO HOT; a result that is cooler and draws less than stock at the same clock |
First run: not as expected, and that found a fault (2026-10-11, reference RTX 4070, driver 617.42, a test build of d1c99e9, started elevated by a script). The first halves held: (a) stock: STABLE ... peak=65 C fan=70 %, then baseline: the measurement at stock four seconds later and no second five minutes; (b) stock: TOO HOT ... peak=64 C, stopped after 34 s, the run went on, no candidates are stopped at line. But both runs then ended with the card held no undervolt: the first step below its own voltage already failed: their first candidates read 1070 mV (+15 MHz): SLOW ... clock=2323 MHz and 1050 mV (+15 MHz): SLOW ... clock=2411 MHz, on a card that ran 2791 MHz in the measurement just before. The search's load had not run yet when that candidate began (see the decision "With its reference handed over, the undervolt search warms its load up before the first candidate"). Second run, with the warm-up: pass for (a) and (b) (same day, a test build of 0733695). (a) stock: STABLE score=5426 it/s clock=2788 MHz peak=66 C fan=70 %, four seconds later baseline: the measurement at stock, warming the load up: 5 s at stock, target: 2790 MHz, which the card reaches at 1090 mV by itself, and the first candidate 1085 mV (+15 MHz): STABLE ... clock=2775 MHz; 26 candidates, none SLOW or TOO HOT, the edge at 950 mV, RESULT: 2790 MHz at 990 mV instead of 1090 mV; before: 5426 it/s, 189 W, fan 70 % (300 s), after: 5325 it/s, 149 W, fan 53 % (300 s). (b) stock: TOO HOT ... peak=64 C, stopped after 74 s, the run went on, the same warm-up and a search to the same result (990 mV, edge 950 mV); before: shows 75 s; no candidates are stopped at line. The saved tune of before the test was put back and applied. (c) not run. |
| 93 | A speed profile that would end slower than stock keeps nothing | Only on a card that is warmer at stock than the profile allows (the tester's RTX 3080 Ti; the reference card ends faster than stock with every speed profile): Best of my GPU from the window, to the end; the result screen, the dashboard and run.log |
yes | the run goes through all its steps and its soak; then Not applied with the result is slower than stock: N it/s against M (-X %), the power limit it had to go down to, and the pointer to Undervolt; The card is back at stock.; the dashboard shows stock and the saved tune of before the run, if there was one; run.log ends with stopped: the result is slower than stock ... -- card restored to stock |
Not run yet. On the reference card: nothing to see, by design. |
| 94 | All in one, with the undervolt step searching for the first time | Elevated, the tray app exited, a copy of gao.json kept: gao --optimize all to the end with a build that has the warm-up of the undervolt step; then gao --status; put the copy back |
yes | five steps; in step 3 a warming the load up line, a first candidate that is not SLOW, and a descent; steps 4 and 5 run on the tune of steps 2 and 3; RESULT: |
Not as expected at step 4, and that found a fault (2026-10-11, reference RTX 4070, driver 617.42, a test build of 0733695, unattended). Step 1: stock: STABLE score=5325 it/s peak=65 C fan=70 % power=186 W. Step 2: power 105 %, mem: bandwidth peak +2300, confirmed +2300, applying +1600, core: highest stable +210, confirmed +210, applying +135, soak STABLE score=5568 it/s peak=70 C power=197 W. Step 3, as wanted: warming the load up: 5 s with the overclock, target: 2865 MHz, which the card reaches at 1050 mV with the overclock, nine candidates STABLE from 1045 mV down, WRONG RESULT at 990 mV, confirmed at 1010 mV after two tries, and applying 1050 mV (+135 MHz at that point): the margin left no raise above the overclock, so the curve was cut off, as in the runs before the warm-up; soak STABLE score=5434 it/s peak=67 C power=176 W. Step 4 did not begin: the tune could not be applied for the fan tune: the undervolt was not applied: the point already runs more than the target -- card at stock. The saved tune of before the test was put back and applied. Cause and fix: the decision "A flat top goes back on a card that is cooler than when it was found". Second run, with the gao.exe of the released v0.4.6: pass, all five steps (same day, started elevated by a script). Step 1: stock: STABLE score=5309 it/s peak=66 C fan=70 % power=185 W. Step 2: power 105 %, mem: bandwidth peak +850, confirmed +850, applying +550, core: highest stable +210, confirmed +210, applying +135, soak STABLE score=5568 it/s. Step 3: target: 2850 MHz, which the card reaches at 1045 mV with the overclock, undervolt: lowest stable 975 mV, confirmed 985 mV, applying 1025 mV (+165 MHz at that point), soak STABLE score=5363 it/s peak=66 C power=163 W. Step 4: the tune went back on the card, fan 60 %: 63 C, fan 55 %: 65 C, fan 52 %: 67 C, fan tune: 52 % holds 67 C. Step 5: the final test ran its 300 s. RESULT: power 105 %, core +135 MHz, mem +550 MHz, 2850 MHz at 1025 mV, fans 52 % at 67 C; stock: 5309 it/s, 185 W, fan 70 %, 66 C, now: 5353 it/s, 166 W, fan 52 %, 70 C: +0.8 % score and -10.3 % power against stock; undervolt: applied. The saved tune of before the test was put back. What this run did not show: its undervolt had a raise of its own (+165 over an offset of +135), so the anchor was raised and not merely cut off, and the case of the first run (an anchor that reads a step above the target on a cooler card) did not come up. That fix has still only its tests. The memory offset differs a lot from run to run on this card, and the two logs say why: +1600 in the first run (bandwidth peak +2300), +550 in the second (peak +850), +650 in the run that saved the tune in use that morning (the journal shows its scan ending at +1000 too). The card was in the same state each time (68 C, power 105 %, a reading every three to four seconds). In both logged scans the bandwidth rises in a straight line, 0.36 and 0.46 % per 50 MHz step, from 420 GB/s at +0 to 509 GB/s at +2450; the first run went on to WRONG RESULT at +2550 without the bandwidth ever turning down. The readings scatter around that line with a standard deviation of 0.4 and 0.7 %, single ones by more than 1 %. The scan ends at a reading more than 1 % below the best so far when a second reading agrees, and in the second run that was chance: +950 read high (462.8 GB/s, where the line is at about 458), +1000 read 456.6 twice, 1.3 % below it. In the first run the one low reading (+600) was followed by a higher one, and the scan ran to the first wrong result. So on this card there is no bandwidth peak before the errors, and where the scan stops is decided by noise. |
| 95 | The memory scan no longer ends by chance | (a) Not elevated, nothing changed on the card: a measuring build takes 24 bandwidth readings in one process, with 3 s of the stress load before each as a scan has, at 64, 128 and 256 dispatches per timed run; then the same with the app's window open next to it, drawing a frame every 100 ms and every 33 ms. (b) Elevated, the tray app exited, a copy of gao.json kept: gao --optimize best twice in a row with the build of the change, then the copy put back |
(a) no, (b) yes | (a) how much a reading scatters, and on what that depends. (b) in both runs every mem +N: bandwidth line shows three readings that agree within about 1 %; the scan does not end before the first WRONG RESULT; the two mem: bandwidth peak values are within 300 MHz of each other and the applied offset is above +1000; the soak is not slower than at stock |
(a) measured (2026-10-11, reference RTX 4070, driver 617.42, the saved tune applied: power 105 %, core +135, memory +650; standard deviation over the 24 readings of a series, and the lowest and highest against the mean). 64 dispatches, no window: 0.44 % (-0.55 to +1.07, one reading did not settle), 0.66 % (-1.58 to +1.49, one did not settle), 1.31 % (-4.83 to +1.27), 0.86 % (-1.44 to +2.05). 128 dispatches, no window: 0.43 % (-1.10 to +0.79). 256 dispatches, no window: 0.22 % (-0.43 to +0.42) and 0.60 % (-0.80 to +1.37; the last five readings of this series were 451 to 453 GB/s after nineteen of 444 to 449). With the window drawing every 100 ms: 64 dispatches 1.13 % (the first six readings 452 to 453 GB/s, the rest 439 to 451), 256 dispatches 0.24 % (-0.47 to +0.61). With the window drawing every 33 ms: 64 dispatches 0.58 %, 256 dispatches 0.25 % (-0.35 to +0.51). A reading took 6.5, 5.3 and 3.9 timed runs to settle at 64, 128 and 256 dispatches. So: the longer timed run takes the scatter from 0.4 to 1.3 % down to 0.2 %, the window's drawing adds nothing, and apart from the scatter the readings sit about 1.8 % higher for up to a minute at a time (the stretch at the end of the second 256 series ran on into the next series; the last 64 series went back and forth between 444 and 452 GB/s). What sets that level is not known. (b) the offset is the same twice; the scan ends in a driver reset twice (2026-10-11, same card and driver, a test build of 2764609, unattended, 40 minutes for the two runs). Run 1: stock: STABLE score=5311 it/s peak=66 C fan=70 % power=185 W; 49 steps from mem +0: bandwidth 422.7 GB/s (422.7 422.6 422.7) to mem +2400: bandwidth 501.4 GB/s (501.3 502.3 501.4); at +2450 the candidate was STABLE and the measurement after it lost the device: mem +2450: the bandwidth measurement failed; treated as a driver reset, mem: the driver reset at +2450; using +2250, the best of the values that passed, 20 s of rest, health: STABLE, confirm core +0 / mem +2050: STABLE, mem: bandwidth peak +2250, confirmed +2050, applying +1400; core: highest stable +210, confirmed +210, applying +135; soak STABLE score=5536 it/s peak=69 C power=195 W; driver resets during this run: 1. Run 2: stock 5312 it/s; 50 steps from 421.8 GB/s at +0 to 502.8 at +2450; the same failure at +2500; mem: bandwidth peak +2300, confirmed +2100, applying +1450; core +135; soak STABLE score=5555 it/s peak=69 C power=196 W; one driver reset. The three readings of a step differed by 0.4 % on average and by 1.2 % at most; no step was more than 0.7 % below the best before it, so neither scan came near ending early, by the old rule or the new; every step had its three readings. The scan took 5 min 25 s (about 6.5 s a step; about 3 min before the change). As expected: the two peaks are 50 MHz apart, the applied offsets are +1400 and +1450, and both results are 4 % faster than stock. Not as expected: neither scan reached a WRONG RESULT. Both ended one and two steps before +2550 with the device lost during the bandwidth copy; Windows logged it both times (System log, nvlddmkm events 14 and 153 at 19:51:22 and 20:11:27), and the screen goes black for a moment. In the run of check 94 that got this far, the measurements at +2450 and +2500 gave a number and the stress load reported the first wrong result at +2550, without a reset. A step now copies about seven times as much as it did (three readings of about four runs of 128 GB, against one reading of about six runs of 32 GB), and the copy checks nothing, so where the memory starts to fail it is the driver that gives out, not a verdict. Two runs against one: likely the cause, not proven. The search handles it as it should (rest, health probe, confirm four steps lower, the core is still searched), but a driver reset in every run on a card like this is not what the change was for. The saved tune of before the test was put back and applied (applied best: power 105 %, core +135 MHz, mem +650 MHz), the tray app restarted, Defender reported nothing. (c) the same two runs with one reading a step, three only for a step in doubt (2026-10-11 20:26 to 21:03, a test build of 71e7c2b, unattended): the reset is still there. Run 1: stock 5328 it/s; 50 steps from mem +0: bandwidth 420.4 GB/s to mem +2450: bandwidth 502.4 GB/s; at +2500 the candidate was STABLE and then bandwidth measurement failed: the timing of the copy did not come back ten seconds later (nvlddmkm event 14 at 20:37:07); mem: bandwidth peak +2200, confirmed +2000, applying +1400; core +135; soak STABLE score=5572 it/s peak=70 C power=197 W; one driver reset. Run 2: stock 5311 it/s; 421.9 GB/s at +0 to 508.0 at +2450; at +2500 the device was lost during the copy failed (hr=0x887A0006), the device hung (event 14 at 20:55:29); mem: bandwidth peak +2400, confirmed +2200, applying +1500; core +135; soak STABLE score=5571 it/s peak=70 C power=196 W; one driver reset. No step was in doubt in either run (the lowest was 0.75 % below the best before it), so every step was read once; the scan took 3 min 33 s. The two peaks are 200 MHz apart: with one reading a step, which offsets count as within 1 % of the best moves by a few steps. Applied +1400 and +1500, both 5 % faster than stock. So the reset does not come from reading a step three times. Four runs out of four with the longer timed run ended this way, at +2450 or +2500. Not merged; the design for a scan that does not end like this is the open row in docs/development.md. The saved tune was put back and applied again (core +135, memory +650), the tray app restarted, Defender reported nothing. (d) the edge is remembered (a test build of 7c215c4; the same two runs): expected is that the first scan ends as before and logs nothing new, the journal gets one mem_edge line with the GPU id and the driver version, and the second run logs mem: the memory gave out at +N in an earlier run on this card and driver; the scan stops at +(N-200), has no driver resets during this run line, applies more than +1000 and is not slower than stock. Run 1 took the machine down; the test ended there (2026-10-11 21:23, unattended). Stock 5321 it/s, power 105 %, then the scan: 49 steps from +0 to mem +2400: bandwidth 539.3 GB/s, two of them read three times (+1200 and +1250). At +2450 the candidate was STABLE and the copy after it failed as in the four runs before: bandwidth measurement failed: the copy failed (hr=0x887A0007): the device was lost at 21:35:05, mem: the driver reset at +2450; using +2250, the best of the values that passed, the driver was reset; reconnecting. That is the last line. Windows restarted by itself within fifteen seconds: Kernel-Power event 41 at 21:35:23, The previous system shutdown at 21:34:57 was unexpected, no bugcheck, no dump, no nvlddmkm event written. So the fifth scan that met the edge with the longer timed run did not end in a driver reset the search recovers from, but in a reset of the whole machine, during the reconnect or right after it. What held: the candidate's journal entry was closed, so no ceiling of a freeze was left; the line {"mem_edge":2450,"rig":"GPU-... / 617.42"} was on disk before the machine went down, so no later run of this build goes there again; the run had saved nothing, and at logon the tray app applied the tune of before the test (applied best: power 105 %, core +135 MHz, mem +650 MHz). Not answered: the second run. Seen along the way and not explained: from the first step of the scan the stress load scored 5760 it/s and the bandwidth read about 7 % higher than in every run before (486.6 GB/s at +900 against about 455), while the measurement at stock five minutes earlier was the usual 5321. (e) how the stress load meets the edge (the same build, no search): gao --reset, then gao --set-mem and gao --stress 3 at +2300, +2350, ... until the verdict is not STABLE, five times, at stock power and core +0; expected is one of two outcomes, WRONG RESULT every time (then the stress load can lead the copy to the edge) or DEVICE LOST (then it cannot). Not run: the test ended in (d). It goes to the edge on purpose, five times, and after (d) that is a risk for the machine that needs its owner's word first. (f) the two-pass search as it is now (one copy; the second pass reads again from four steps below the highest step that passed; see docs/development.md): tested on a simulated card only. On the reference card, with the edge at +2450 in its journal and no other program on the card: gao --optimize best once; expected other programs on the card: none, mem: the memory gave out at +2450 in an earlier run on this card and driver; the scan stops at +2250, no memory candidate above +2250, mem: the scan passed +2250; reading again from +2200 down, a few mem +N: bandwidth ... GB/s, read again lines, no driver resets during this run, an applied offset above +1000 and a soak that is not slower than stock. As expected (2026-10-11 23:34 to 23:51, elevated, a test build of e5433ee, the editor and the videos closed). other programs on the card: none; stock: STABLE score=5741 it/s clock=2790 MHz peak=67 C fan=72 % power=196 W, 8 % above every stock measurement of that day, which were all taken next to the editor; power 105 %. mem: the memory gave out at +2450 in an earlier run on this card and driver; the scan stops at +2250: 46 steps in 2 min 41 s, from mem +0: bandwidth 452.9 GB/s to mem +2250: bandwidth 533.3 GB/s, each read once. mem: the scan passed +2250; reading again from +2200 down: 533.8, 528.4, 530.7 and 527.8 GB/s at +2200 to +2050; confirm core +0 / mem +2100: STABLE; mem: bandwidth peak +2100, confirmed +2100, applying +1450. core: highest stable +210, confirmed +210, applying +135 after WRONG RESULT at +225, as in every run. Soak STABLE score=5918 it/s peak=70 C power=200 W: 3.1 % faster than stock. No reset event, no nvlddmkm event in the System log, nothing new in the journal, sixteen and a half minutes in all. The second look after the run found the card as before it. The saved tune of before the test was put back and applied (core +135, memory +650), the tray app restarted, Defender reported nothing. Not tested by this: a first scan on a card whose edge is not in the journal yet. (g) what else uses the card (2026-10-11 22:00, after the restart of (d), not elevated, the saved tune applied, nothing changed): gao --stress 5 scored 5828 it/s and gao --bandwidth --after-load 3 read 484.0 GB/s with its three runs within 0.0 %, against 5226 it/s and 444 to 452 GB/s with the same tune earlier that evening. The series of (a) again, 24 readings each: 64 dispatches 480.0 GB/s, 0.23 % (-0.66 to +0.18); 256 dispatches 475.2 GB/s, 0.68 %, sliding from 480 to 471 during the series; 64 dispatches again 454.0 GB/s, 3.02 % (-4.37 to +4.22), the readings jumping between 434 and 473. Read right after it, the card's use per program: Chrome 8.6 % of the video decoder (19 % at most) and 2.8 % of the 3D engine, with a YouTube video playing; nothing else above 1.1 %. So the short copy is as steady as the long one on a card that is left alone, and a video playing next to it costs about 8 % of the bandwidth and of the stress score and makes the readings jump. Then 40 readings with the short copy, and next to each the other programs on the card in the seconds before it (22:13): 464 to 476 GB/s while Chrome used the video decoder, 444 to 455 while it used the 3D engine as well, and from the reading after Chrome stopped, 477 to 483.5 for the last eleven readings with no other program on the card. So another program using the card lowers the reading by 3 to 8 % from one reading to the next. That explains the scatter and the level changes of (a) in kind. During the tests of that day it was most likely not a video (hardly any was watched) but a code editor built on a browser engine that had stood open since the morning: its last files were written at 21:31:43 and 21:31:44, when it was closed, and the run of (d) logs the bandwidth going from 430.4 to 457.1 GB/s at 21:31:45. So every card measurement of checks 94 and 95 up to then was taken with that program on the card: about 7 % lower, and more scattered, than the card alone. A match to the second; not measured with the editor started again. |
| 96 | Other programs on the card are found and named before a run | (a) Not elevated, nothing changed on the card, the saved tune applied: gao --stress 5 and bandwidth readings with nothing else on the card, with an editor (Trae) standing open, and with a video playing in Chrome; next to every reading the card's use per program. (b) Not elevated: gao --busy with the editor open and with it closed. (c) The window's question, from a look build that shows it without a run. (d) Elevated, with the editor open: gao --retest, then gao --retest --busy-ok; and Optimize GPU in the window, each of the three answers |
(a) to (c) no, (d) yes | (a) how much another program costs. (b) other programs on the card: Trae (2 %), and none. (c) the list, the explanation and the three buttons, nothing cut off. (d) the first prints the list and the advice and exits 1 with nothing changed on the card; the second logs the list and going on with them and runs; in the window Check again with the editor closed starts the run, Go on anyway starts it and logs the list, Cancel ends in Not started with the list |
(a) measured (2026-10-11, reference RTX 4070, driver 617.42, saved tune: power 105 %, core +135, memory +650). Nothing else on the card: 5828 it/s, bandwidth 480 GB/s over 24 readings scattering by 0.23 %, and 477 to 483.5 in a later series. Trae open and idle, 2.2 % of the 3D engine: 5329 it/s, 452 to 457 GB/s over 28 readings, then two at 441 and 444. A YouTube video in Chrome, 8.6 % of the video decoder and 2.8 % of the 3D engine while the card idles: 464 to 476 GB/s while it decodes, 444 to 455 while it draws as well, 24 readings scattering by 3.0 %; back to 483 on the first reading after it stopped. Under the run's own load the other program's share reads 1 % or less, so it can only be seen while the card idles. One sample of the counters read 183 455 208 183 776 %. That day's card tests had all been taken with the editor open, from the morning until 21:31:44, when its last files were written; the log of the run under way shows the bandwidth going from 430.4 to 457.1 GB/s at 21:31:45. (b) as expected with the editor open: other programs on the card: Trae (2 %), twice. Not run with it closed. (c) as expected: the heading, chrome (12 %, video), Trae (2 %)., the explanation in two lines, Check again, Go on anyway and Cancel, at 1180 x 900. (d) the command line's refusal: as expected (2026-10-11 22:46, elevated, a test build of ff80e3d, the editor and the chat application this was written in open). gao --busy: other programs on the card: Trae (1 %). gao --retest: other programs on the card: Trae (2 %), msedgewebview2 (1 %), the four lines of advice, RESULT: not applied -- other programs are using the graphics card: Trae (2 %), msedgewebview2 (1 %), exit 1, four seconds after it began; the profile and what is applied read the same before and after, and run.log has the line. Two things seen: the editor read 1 % in one look and 2 % in the next, so the threshold went from 1 % to 0.5 % after this run; and msedgewebview2 is the browser engine inside another application, a name that tells its user nothing. Not run: --busy-ok, and the three answers in the window, on a real run. |
- Check 6 is the one that matters. If a write NVAPI accepts cannot be
distinguished from one it applies, the search cannot be trusted, and the approach needs rethinking before
more is built on it.
--set-core 5000(5 GHz above stock) is expected to be silently ignored by the driver;gaomust printMISMATCH, neverOK, for that request. - Run check 4 (
--reset) again after check 6 to leave the card in a known state, since a refused write can leave the read-back at whatever the driver's own limit clamped it to rather than exactly the requested value.
A driver reset that gao survives is not by itself a pass. Round A of check 51
showed why: the tool survived the reset and the desktop was unusable afterwards.
A check that involves a driver reset (51, 52; for 54, 55 and 56 see their rows) passes only when all four of these
hold:
gao. The run ends with aRESULT:line: the applied values, orRESULT: not applied -- <reason>with one of the reasons below.gao.exedoes not crash. The card is at the result or at stock, andgao --statusagrees. The journal has nobeginwithout acomplete.- The Windows System log. At most one driver reset (nvlddmkm) during the
run, or two when the first came in the memory search and the core was
searched after it, unless the run ended with
the driver reset twice,the driver reset three times,the card was not usable when checked after a driver reset (...)orthe card computed almost nothing after a driver reset.gaomay count one more reset than the System log shows: it also counts a stalled or blind probe, a refused write and a failed bandwidth measurement on an overclocked candidate, which only suggest a reset. Bandwidth readings that did not settle are not counted: the load measured, and the memory scan ends there. - The desktop. Within one minute after the run ends, the windows that were open are visible and usable, a new program can be started and shows its window, and no sign-out or restart was needed. Programs that crashed are noted.
- The owner's judgement. The machine feels normal afterwards.
The reasons a run can end with after a driver reset, all with the card at stock
and nothing saved: the driver reset twice; the driver reset three times;
the driver reset at stock clocks (...);
the card did not recover after a driver reset;
the card was not usable when checked after a driver reset (...);
the card computed almost nothing after a driver reset;
the driver did not come back after a reset;
could not set the card to stock while recovering;
aborted; soak failed 4 times. When the reset to stock
itself failed, the RESULT: line says so instead of (card at stock).
None of the reset handling beyond what round A showed has been run on hardware.
In particular it is not known whether the 20 s rest keeps the desktop usable:
there is one observation of an unusable desktop, it came from a forced reset
(dxcap -forcetdr), and nothing has been re-run since.
For checks 51 and 52, record whether a tray app was running during the check and which build it was: an installed v0.2.0 with its window open can itself crash at the forced reset.
Check 33's re-run pass was recorded on a build whose recovery of the window's
device did not call ClearState and Flush on the old device context; the
recovery now does (release_device() in main_gui.cpp), on the path a reset
takes while the tray app runs, so check 33 and the reset half of check 41 (no
result is recorded for it here) must both be run again on this build before
their results are relied on.