Repository navigation
fix: the curve of a GTX 10 card is read in kHz, so its undervolt search can start - #83
Merged
Merged
Conversation
…ch can start A GTX 10 card (Pascal) counts the frequencies and the offsets of its voltage/frequency curve in half kHz; every later generation counts in kHz. Read as kHz, such a curve lies twice as high as the clock NVML reports, and the undervolt search ended before its first candidate with "the load clock is below the card's curve". Nothing wrong was written. Nvapi now asks the driver for the card's architecture (NvAPI_GPU_GetArchInfo) and brings both fields to kHz where the buffers are read and written. An architecture that cannot be read counts as kHz, as before. Not verified on hardware: the project has no GTX 10 card. The half kHz come from a GTX 1080 on driver 582.78 and from nvapi-rs and green-curve (docs/development.md). Hardware check 110 is open, also for the reference card. Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
A GTX 1080 Ti ran v0.4.10-rc1 (hardware check 110 (b)): the curve is read in kHz now and the undervolt search ran through its candidates. Its clock steps read 12.5 or 13 MHz, which fit none of the usual grids, so clock_grid_khz fell back to their greatest common divisor, 0.5 MHz. The drift allowed after the points above the anchor are lowered (two grid steps) shrank to 1 MHz, and the 881 mV candidate was refused for reading one step above its target. Such a curve now gets 12667 kHz. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ut the change Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
4 of 5 tasks
Rovey
marked this pull request as ready for review
November 14, 2026 15:32
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What was wrong
A GTX 10 card (Pascal) counts the frequencies and the offsets of its voltage/frequency curve in half kHz: 1873 MHz reads
3746000. Every later generation counts in kHz, and the code read every card as kHz.On a GTX 10 card the curve therefore lay twice as high as the clock NVML reports. In
find_undervoltno point of it was at or below the clock the card ran, and the search ended before its first candidate withthe load clock is below the card's curve. All in one then keeps its overclock and reportsno undervolt. Nothing wrong was written to a card: the search stops before any write. The overclock, the power limit and the fans do not use the curve calls.The README says GTX 10 or newer, so this is a promise the undervolt did not keep.
The fix
vf_units_per_khz(architecture): 2 for Pascal (NV_GPU_ARCHITECTURE_GP100,0x130), 1 for everything else.parse_vf_curveandput_vf_raw_offsettake that number and bring both fields to kHz where the buffers are read and written. The search, the saved tune and the window keep counting in kHz, on every card.Nvapi::VfUnitsPerKhzasks the driver for the architecture withNvAPI_GPU_GetArchInfo, a public call ofnvapi.h, once per connection. When it cannot be read the card counts as kHz, which is what the code did before.gao --curveprints one extra line on a card that counts in half kHz, and nothing new on any other.clock_grid_khzrecognises a GTX 10 card's clock grid (6a12a5a). Its steps are 38/3 MHz, read out in half MHz as 12.5 or 13 MHz, which fit none of the usual grids (15, 7.5, 5, 2.5 MHz). The fallback, the greatest common divisor of the steps, came out at 0.5 MHz, so the two grid steps of drift allowed after the points above the anchor are lowered shrank to 1 MHz. Such a curve now gets 12667 kHz. Other cards are unchanged: the usual grids are tried first, and the greatest common divisor stays the last resort.Hardware check 110
gao --curveof this build, of the v0.4.9 release and of this build again gave three identical tables (127 points, 450 to 1240 mV, the saved flat top's raw offsets) and no line about half kHz.gao --statuswas identical before and after: the read changed nothing on the card.gao --curveprinted the half-kHz line and a top of 1999.5 MHz against a load clock of about 1860 MHz, not twice it.gao --undervoltran its candidates (962 to 893 mV stable, each at the target 1860 MHz, so the offsets landed in the right unit) and saved 1860 MHz at 925 mV instead of 975. One candidate, 881 mV, was refused for reading one step (13 MHz) above its target after the lowering; that is the grid fix above. That fix has not run on a GTX 10 card yet; it is covered by a test built from this card's own 80-point curve.Details in
docs/hardware-checks.md(check 110) and the decision "A GTX 10 card's curve is read in half kHz" indocs/development.md.Out of scope here: the before/after line of that run compared 119 s at stock (stopped at 86 C) with 300 s on the profile's fan curve, so its numbers are not comparable. A fair stock-versus-undervolt comparison is a separate issue.
Where the half kHz come from
NVIDIA does not document the unit. Before check 110 (b), three sources agreed:
3113000, 1873 MHz at 1050 mV read3746000.Kilohertz2,Kilohertz2Delta).graphicsFrequencyScale = 2), with the same buffer sizes and offsets this project uses.Tests
tests/test_vf_curve.cppfor the units, written first and seen failing on the old behaviour (3113000 == 1556500): which architecture counts in half kHz, a GTX 1080's buffers read in kHz, and an offset written at twice its kHz that reads back as written.cmake --workflow --preset ci(MSVC,/W4 /WX, Release): no warnings,core_tests501 of 501 cases (11735 assertions),app_testspassed. CI green.🤖 Generated with Claude Code