Skip to content

Commit 8c87365

Browse files
authored
Merge pull request #31 from Rovey/retest-after-driver-update
feat: after a driver update the saved tune is tested again, five minutes, instead of found again
2 parents ab616d8 + 32ccbee commit 8c87365

16 files changed

Lines changed: 747 additions & 56 deletions

‎CMakeLists.txt‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,8 @@ add_library(core STATIC
5858
src/core/search.cpp
5959
src/core/boot.hpp
6060
src/core/boot.cpp
61+
src/core/retest.hpp
62+
src/core/retest.cpp
6163
src/core/task_xml.hpp
6264
src/core/task_xml.cpp
6365
src/core/watchdog.hpp
@@ -144,6 +146,7 @@ add_executable(core_tests
144146
tests/test_journal.cpp
145147
tests/test_search.cpp
146148
tests/test_boot.cpp
149+
tests/test_retest.cpp
147150
tests/test_task_xml.cpp
148151
tests/test_watchdog.cpp
149152
tests/test_fan_curve.cpp

‎README.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ It cannot leave you worse off than stock:
3333
- **It gives up before you have to.** Three logons in a row that crash switch apply-at-logon off; four driver resets within an hour with the tune applied put the card back at stock.
3434
- **It plays fair.** If MSI Afterburner or another tool changes the settings, it steps aside instead of fighting over them.
3535

36-
The one time it asks for you again is after a driver update: a tune belongs to the driver it was tested on, so the app does not apply it, says so, and one more click tunes the card for the new driver.
36+
The one time it asks for you again is after a driver update: a tune belongs to the driver it was tested on, so the app does not apply it, says so, and one more click tests it on the new driver: five minutes, instead of a whole new run.
3737

3838
What else is in it:
3939

@@ -236,7 +236,7 @@ The NVIDIA app can stay installed. Two of its features get in the way of a run:
236236
<details>
237237
<summary><b>What happens after a driver update?</b></summary>
238238

239-
The saved profile is tied to the driver version it was tuned on. After an update it is not applied, and the app asks you to optimize again, because a new driver can change what is stable.
239+
The saved profile is tied to the driver version it was tuned on, because a new driver can change what is stable. After an update it is not applied: the app says so at logon, and the dashboard shows **Test the tune (5 min)**. That loads the card for five minutes with the tune on it. If it holds, the tune is applied again and belongs to the new driver; if it does not, the card goes back to stock and you optimize again. The test never starts by itself. On the command line: `gao --retest`.
240240
</details>
241241

242242
<details>

‎docs/development.md‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ src/core/ pure logic: no windows.h, no driver calls, no D3D. Unit-tested in
3232
journal.* write-ahead log of clock candidates; a freeze becomes a ceiling
3333
search.* the tuner: baseline, power, memory, core, soak; the recovery after a driver reset
3434
boot.* when a logon apply may run (strikes, driver, card) and the verified apply
35+
retest.* the saved tune tested again on a new driver: five minutes, then applied or stock
3536
watchdog.* the tray app's decisions: re-apply, give up, back off, driver changed
3637
task_xml.* the logon task definition
3738
update.* reading GitHub's "latest release" answer; version numbers
@@ -81,13 +82,14 @@ The applied offset is always at least one step below the confirmed edge. Profile
8182
| Open: why the bandwidth measurement fails on some cards, and what then | On the tester's RTX 3080 Ti it failed at +0, and since 0.4.4 that means no memory overclock at all on such a card, though it computed right far beyond +2000 MHz. The measurement gives a number only when three runs in a row agree within 1 % (`kBwSettle`), and it throws away every run the driver's timer calls disjoint (the clock changed under it). That card was held at a power limit of 65 % and ran at 83 C when it was measured; the reference card has only ever been measured at 105 %, where the measurement itself draws 179 of 210 W. So the suspicion is the power limit, and hardware check 90 tests exactly that on the reference card. What it shows decides what is built, and nothing is built before it: **(1) runs are thrown away as disjoint, and the ones that are kept agree:** the timer is what fails, not the card. Time the copy differently (wall clock around a flush, or count a disjoint run when its neighbours agree) and keep the method. **(2) the readings themselves scatter by more than 1 %:** a peak that is found by a 1 % drop (`kBandwidthDrop`) cannot be found in that noise. Then the memory has to be measured where the card is not held back: search it before the power step, at stock power, where a probe and its measurement last five seconds; or take more readings and compare medians. **(3) it does not fail at 65 % on the reference card:** the power limit is not the cause. Then it was the heat (a card throttling at its temperature limit changes clock as well), or another program, and only the tester's `run.log` can say: since this change a measurement that never settled writes how many runs gave a reading, between which values, and how many were thrown away. In no case does the climb on stability alone come back. **Outcome of check 90: (3).** On the reference card the measurement gave a number fifteen times out of fifteen at 100, 80 and 65 %, in three or four runs, with nothing thrown away, and the bandwidth was the same at every limit. The power limit is ruled out; (1) and (2) are not built. What is left is the heat of that card, another program, or a timer query that failed, and the tester's `run.log` is the next thing that can tell them apart. **The tester's run (check 91): it is (2), but only inside a run.** Twenty readings between 763.9 and 821.2 GB/s, none thrown away, three seconds after the load began; after thirty seconds of load the same card read within 0.2 %. The suspicion now is the moment, not the card: the measurement is taken while the power limiter is still catching up. If `gao --bandwidth --after-load 3` scatters on that card and `--after-load 30` does not, that is it, and the fix is to measure when the card has settled (keep taking runs for some seconds instead of twenty runs), not to change the method. |
8283
| Every line of a run is also in `run.log` | `logged()` wraps the log hook of both front ends: a line is appended and flushed before it is shown, so the step a machine freezes in is the last line on disk. One file in the state folder, moved aside at 1 MB (`run.log.old`). Only elevated and only after `ensure_app_dir` has verified the folder: an elevated process appends to nothing a user could have put there. |
8384
| A run does not start while the NVIDIA app's Automatic Tuning is on | Switched on, a service of the driver scans the card when it sees fit: it loads it, changes its clocks, and starts again by itself (hardware check 89: a scan that was stopped started again 14 ms later, and after a restart of the service it resumed within two seconds). A run next to that measures nothing it can trust, so it is refused, like a run next to Afterburner, and with the same weight: a courtesy, not a guard. Read from where that service keeps the switch, `%ProgramData%\NVIDIA Corporation\nvtopps\nvtopps.db3`, table `ocScannerSettings`, fields `backgroundScanEnabled` and `grdOcEnabled`, joined to `TelemetryGpuCfg` so that only a card that is in the PC counts (a row of an earlier card can never be switched off again). That this is the source and not a copy of it was measured, not assumed: with the field set the service took the scan up again after every restart, with it cleared it did not, while the NVIDIA app itself showed the switch as off. Nothing documents the file. So: opened read-only and immutable (no lock, the service never waits for us), through `winsqlite3.dll` from System32 (no dependency added), and everything that is not exactly as expected reads as "unknown", which starts the run as before. The registry value the app sets next to it (`HKLM\SOFTWARE\NVIDIA Corporation\Global\NvApp\OC\<user SID>`) changes at the same moment but belongs to the app, not to the service, and is not used. **The overlay is not checked:** no setting for it was found, its processes run whether it is open or not, and the one value that moved (`IsShadowPlayEnabled`) followed the service being up, not a switch. NVAPI was not tried: an undocumented call is no better than an undocumented file, and it cannot be compared with anything. |
84-
| A run does not start next to MSI Afterburner or RivaTuner | `other_tool_refusal`, asked before anything else in `run_optimize`, `run_undervolt` and `run_all_in_one`, so the window and `gao` say the same. Afterburner sets clocks and fans itself; RivaTuner hooks into every program that uses the card, the stress load among them. By file name (`MSIAfterburner.exe`, `RTSS.exe`), which is all a list of processes gives; a list that cannot be read counts as "none runs", because the run's own read-backs and the watchdog are what it relies on. Not the cause of the freeze above: on that PC Afterburner was closed and RivaTuner not installed. |
85+
| A run does not start next to MSI Afterburner or RivaTuner | `other_tool_refusal`, asked before anything else in `run_optimize`, `run_undervolt`, `run_all_in_one` and `run_retest`, so the window and `gao` say the same. Afterburner sets clocks and fans itself; RivaTuner hooks into every program that uses the card, the stress load among them. By file name (`MSIAfterburner.exe`, `RTSS.exe`), which is all a list of processes gives; a list that cannot be read counts as "none runs", because the run's own read-backs and the watchdog are what it relies on. Not the cause of the freeze above: on that PC Afterburner was closed and RivaTuner not installed. |
8586
| Edges are confirmed, then backed off | A 3 s probe can pass by luck; the edges get 30 s probes before the safety margin is applied, and the result must pass a 300 s soak. |
8687
| The search climbs to a per-card bound | A fixed +300 MHz cap decided the result on cards that hold more. The bound is the offset range the driver reports, accepted only when it is plausible (spans stock, contains the applied offset, below a sanity limit). Bisecting such a range would start hundreds of MHz too high, so the search climbs from stock. |
8788
| Both searches go one grid step at a time (15 MHz core, 50 MHz memory); nothing is bisected | The first value that fails is then exactly one step above the last one that passed, for an ordinary failure and for a driver reset alike, and the climb probes nothing more after a value that failed or reset the driver. The confirm probe then runs one step below an ordinary failure (at the last value that passed) and, after a reset, four steps below the last value that passed. The earlier scheme, 60 MHz strides and a bisect after the first failure, could leave the last passing value up to 45 MHz below the edge and bisected right below a value that had just reset the driver. The cost is more probes and a longer run; how much longer has not been measured. |
8889
| Memory is searched before the core | The first driver reset ends exploring (see the reset budget row). The assumption, not measured, is that a reset is far more likely in the core climb than in the memory scan: memory normally loses bandwidth long before it fails, and both resets seen so far came from the core. With the core first, its reset would leave the memory untuned. The core climb runs with the chosen memory offset applied; memory is tuned at stock core clocks, and the combination is proven by the soak. If the memory search itself has a reset, the core is still searched (seen on the reference RTX 4070: a memory scan ended in a reset, the core climb ends in a wrong result), and the run can then take one more reset. |
8990
| The optimize run reconnects after a driver reset | The edge is found by crossing it, and past it the driver resets. A call into NVML faulted after a reset, in the tray app (hardware check 33) and in an optimize run (check 51), so the run owns guarded connections: a fault inside a driver DLL becomes a failed call, a reconnect re-creates both libraries, and it never happens with a journal entry open. A fault that still escapes ends the run with the entry closed as `CRASHED`, and the card is then reset to stock. |
9091
| One NVML session for the process, and the power limit and the fan speed are asked first | On driver 617.42 the clock, temperature and power readings come from memory the driver shares with the process. A driver reset takes that memory away, the library goes on reading it, and every such call is an access violation from then on (hardware check 84); one that was caught leaves `nvmlShutdown` waiting for ever (check 80). Only a complete `nvmlShutdown` and a new `nvmlInit` bring the memory back. The calls that ask the driver instead answer a reset with a status, so `device()` in `nvml.cpp` makes two of them before every other call on a card: when one that worked before says the card is gone, nothing reads, and two seconds later the session is stopped and started again. `nvmlInit` is counted by the library, so all `Nvml` objects share one session: with one each, the window's would keep the stale session alive under the reconnect of a run. |
92+
| After a driver update the tune is tested again, not found again, and only on a click | A tune belongs to the driver it was tested on (`decide_boot`), and a new driver can change what is stable: 617.42 did. Finding the tune again takes a whole run; whether it still holds takes the five minutes a result is tested for anyway (`retest_saved_tune`). Stable: the profile is stamped with the new driver and stays applied. Anything else: stock, and the profile is not touched, so it stays refused. The test never starts by itself: at logon the app only says that the driver was updated and the tune is not applied, and the dashboard has the one button. Five minutes of full load that nobody asked for, in the middle of whatever the PC is doing, is not what this app does. The fans are as in daily use, on the app's curve or with the driver, because that is what the tune will run with. What is planned on top of this is in [planned.md](planned.md). |
9193
| A probe that computes almost nothing is `STALLED` | Past its edge an RTX 5070 passed a probe at 3 % of its baseline score with no wrong value and no lost device (hardware check 51). Below a quarter of the baseline a "stable" probe is `STALLED`. A running probe ends as soon as its last three seconds fall below that floor, so a 300 s soak does not keep loading a card that stopped computing; three seconds, so that one delayed batch does not look like a dead card. Probes shorter than that are judged after they end. |
9294
| A clock search stops exploring at its first driver reset; a run ends at one more than its searches may have | Nothing can guarantee that a probe will not reset the driver, so resets are counted. What counts: a probe that ends `DEVICE LOST`, `STALLED` or `NO TELEMETRY`; a candidate, power or soak write that fails; a failed bandwidth measurement on an overclocked candidate, unless the load measured and its readings only did not agree (other programs using the card): that ends the memory scan at the best value measured and counts as nothing. At the first one in a clock search that search ends at the last value that passed (in a bandwidth scan: the bandwidth peak of the values that passed), confirmation starts four steps lower, and a reset during a confirm probe or the soak moves the next try four steps down instead of one. Each clock has its own search: after a reset in the memory search the core is still searched, because on the reference RTX 4070 a memory scan ended in a driver reset at +2550 MHz while the core climb ends in a wrong result, and skipping the core left that card with no core offset at all (hardware check 52). A run allows one reset, and two when the first came in the memory search and the core is searched after it; at one more the run ends at stock, nothing saved, nothing else started. With no clock offset applied (the baseline, the power step, a soak at stock clocks) there is nothing to back off to, and the first one ends the run. Only `DEVICE LOST` proves a reset; the others suggest one, so `gao` can count a reset the Windows log does not show. |
9395
| After a reset: rest, then prove the card is back | In round A of hardware check 51 the first probe after a forced reset, at core +30 and about two seconds after the driver restarted, scored 325 it/s at 45 W and was judged `STALLED`; the desktop was unusable afterwards. Why that probe scored so low is not known: in the build round A ran on, the stress load re-created its D3D device and recompiled its shader in the first batch after a lost device, and that time was counted in the probe's score; the card itself may or may not have been back. So before any further candidate the search reconnects, writes stock, leaves the card without load for 20 s, and runs a 5 s probe at stock that must be `STABLE` at 90 % of the baseline or better: up to three such probes, a rest before each. A card that does not come back ends the run, and so does a reset during that check: no step of the recovery starts another recovery. The numbers are first choices, not measured. |

0 commit comments

Comments
 (0)