Skip to content

fix: the curve of a GTX 10 card is read in kHz, so its undervolt search can start - #83

Merged
Rovey merged 4 commits into
mainfrom
fix/pascal-vf-curve-units
Nov 14, 2026
Merged

Rovey merged 4 commits into
mainfrom
fix/pascal-vf-curve-units

Conversation

@Rovey

@Rovey Rovey commented Nov 13, 2026 •

Copy link
Copy Markdown
Owner

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_undervolt no point of it was at or below the clock the card ran, and the search ended before its first candidate with the load clock is below the card's curve. All in one then keeps its overclock and reports no 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_curve and put_vf_raw_offset take 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::VfUnitsPerKhz asks the driver for the architecture with NvAPI_GPU_GetArchInfo, a public call of nvapi.h, once per connection. When it cannot be read the card counts as kHz, which is what the code did before.
  • gao --curve prints one extra line on a card that counts in half kHz, and nothing new on any other.
  • clock_grid_khz recognises 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

  • (a) Reference RTX 4070, driver 617.42, build 6a12a5a: gao --curve of 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 --status was identical before and after: the read changed nothing on the card.
  • (b) GTX 1080 Ti, driver 582.78, v0.4.10-rc1 (run by a user): gao --curve printed the half-kHz line and a top of 1999.5 MHz against a load clock of about 1860 MHz, not twice it. gao --undervolt ran 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" in docs/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:

  • A GTX 1080 (GP104) on driver 582.78, reported for simple-nvidia-undervolt: 1556.5 MHz at 850 mV read 3113000, 1873 MHz at 1050 mV read 3746000.
  • nvapi-rs counts the points and the offsets of this curve in half kHz (Kilohertz2, Kilohertz2Delta).
  • green-curve halves both fields for this architecture only (graphicsFrequencyScale = 2), with the same buffer sizes and offsets this project uses.

Tests

  • Three cases in tests/test_vf_curve.cpp for 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.
  • One check for the grid: the GTX 1080 Ti's curve gives 12667 kHz; the existing checks for the RTX 4070 (15 MHz), the RTX 5070 (7.5 MHz) and the greatest-common-divisor fallback still pass.
  • cmake --workflow --preset ci (MSVC, /W4 /WX, Release): no warnings, core_tests 501 of 501 cases (11735 assertions), app_tests passed. CI green.

🤖 Generated with Claude Code

Rovey and others added 3 commits November 13, 2026 16:11
…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>
@Rovey
Rovey marked this pull request as ready for review November 14, 2026 15:32
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Rovey
Rovey merged commit 740e308 into main Nov 14, 2026
2 checks passed
@Rovey
Rovey deleted the fix/pascal-vf-curve-units branch November 14, 2026 15:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant