Skip to content

Commit 9ae59a7

Browse files
vupleaclaude
andcommitted
Hold the requested clock at the settle point, not mid-slope above it
Measured on a 5090 under load: the boost settles one 5 mV step below the flat top's first anchor, at the effective curve's clock there - mid-gap when the anchor spacing is 10 mV. The stock-slope-continued flatten therefore realized half a stock step above the request (+7..15 MHz at the profile caps, +52 MHz for a cap below the curve's knee). The cap->flat segment is now a straight line through (settle voltage, requested clock) with a fixed 16 MHz rise - the smallest the driver reliably keeps distinct (a written 8 MHz rise provably folds the plateau), which also bounds the realized miss at half the rise, inside the driver's own one-bin re-shaping noise. Every report now names the operating point: the plan and post-apply lines from the plan's own anchors, a replay from its written shape, status by inference from the flat (with no point fabricated when the top holds no plateau), and a cap->flat rise that reads back collapsed is flagged as a possible fold. The measured driver behavior is documented in DEVELOPMENT.md ("Where the boost settles"). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017bNKonjifgKN2zykAGFNq4
1 parent 0dd8ee1 commit 9ae59a7

12 files changed

Lines changed: 554 additions & 350 deletions

‎CURVE-FILES.md‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -67,11 +67,11 @@ base clock):
6767
"memoryOffset": 1400,
6868
"curve": [
6969
{ "mv": 890, "offset": 480, "mhz": 2820 },
70-
{ "mv": 895, "offset": 480, "mhz": 2842 },
70+
{ "mv": 895, "offset": 480, "mhz": 2850 },
7171
{ "mv": 900, "offset": 480, "mhz": 2872 },
72-
{ "mv": 910, "offset": 457, "mhz": 2872 },
72+
{ "mv": 910, "offset": 473, "mhz": 2888 },
7373
… the flattened anchors above the cap …
74-
{ "mv": 1240, "offset": -323, "mhz": 2872 }
74+
{ "mv": 1240, "offset": -307, "mhz": 2888 }
7575
]
7676
}
7777
```

‎DEVELOPMENT.md‎

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,44 @@ the card.
8888
The core-domain point slots come first (a per-entry type word says which domain a slot belongs to:
8989
0 = core, 1 = memory), and the memory slots follow — slot 80 on Pascal, slot 127 on a 5090.
9090

91+
## Where the boost settles
92+
93+
A flatten pins the boost by curve shape alone, so the plan is built on how the boost algorithm
94+
picks its operating point. NVIDIA doesn't document this; the following is measured on an RTX 5090
95+
under a sustained FurMark load, comparing written curve shapes against `watch` and `curve`
96+
read-backs:
97+
98+
- **The settle voltage is one 5 mV step below the flat top's first anchor.** A flat from 910 mV
99+
runs the card at 905 mV; a flat from 900 mV runs it at 895 mV. It never runs on the flat itself.
100+
- **The driver's curve is finer than the anchor table.** Table anchors sit 5 or 10 mV apart
101+
(Blackwell cycles 5-5-5-10, with the 10 mV gap right after every multiple of 25 mV), but the
102+
settle voltage exists at 5 mV granularity regardless: a flat from 910 mV settles at 905 mV,
103+
where no anchor exists — the clock there interpolates between the 900 and 910 anchors.
104+
- **The realized clock tracks the middle of the cap→flat segment, within one bin.** Clocks
105+
quantize to ~7.5 MHz bins on Blackwell (~15 MHz read-back steps on older generations). Measured
106+
at a 900 mV cap: a segment written 2880→2903 settles at 2887–2895; written 2872→2888 it settles
107+
at 2880; written 2880→2896 it settles at 2887. "Mid-segment" and "one bin below the flat" fit
108+
every measurement equally — indistinguishable at bin granularity.
109+
- **The driver re-shapes written anchors by a bin or two.** A pair written 2872/2888 reads back
110+
2880/2887 from the status curve, and neighbouring anchors of the same write shift by anything
111+
from 0 to +15 MHz. Aiming the shape finer than a bin is therefore meaningless.
112+
- **A cap→flat rise below ~2 bins folds.** Written 8 MHz apart, the driver snaps the pair into one
113+
plateau: the flat's first anchor becomes the cap anchor itself and the settle drops 5 mV lower,
114+
landing a stock-slope step below the request. Written 16 MHz apart, the pair stays distinct. On
115+
generations whose clocks step ~15 MHz, 16 MHz is a single bin and folding is plausible but
116+
unmeasured; a fold degrades to the settle-a-step-lower shape (less clock at less voltage — the
117+
safe direction), and the post-apply report flags a cap→flat rise that reads back collapsed.
118+
119+
`GpuTuning.BuildCurvePlan` encodes the consequences: the cap→flat segment is a straight line
120+
through (settle voltage, requested clock) with a fixed 16 MHz rise — the smallest the driver
121+
reliably keeps, which also bounds the realized-clock miss at half the rise, inside the bin noise.
122+
Where the gap above the cap anchor is 5 mV, the settle point *is* the cap anchor and holds the
123+
request outright; where it is 10 mV, the segment straddles the request 8 MHz each side and the
124+
card operates 5 mV above the named cap (`--mv 900` runs at 905 mV). The alternative — starting the
125+
plateau at the cap anchor so the card stays at or under the named voltage — realizes a clock a
126+
stock-slope step below the request; running one step higher in voltage at the requested clock is
127+
the safe side of that trade.
128+
91129
## Generations
92130

93131
**Pascal (GTX 10xx, 2016) is the architectural floor**: GPU Boost 3.0 introduced the per-point V/F

‎README.md‎

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -147,9 +147,11 @@ operates on.
147147
## Development
148148

149149
Every point above the cap anchor is flattened to one frequency through `ClkVfPointsSetControl`;
150-
the boost algorithm then pins voltage at the cap. A band of `--cap-points` anchors ending at the cap
151-
shares the cap's offset, so if the realized voltage settles a bin or two below the cap under load,
152-
an overclocked cap doesn't fall back down the steep stock curve.
150+
the boost algorithm then pins its operating point just below the flat. The cap→flat segment is
151+
aimed so the clock at that point is the requested one (measured settle behavior — see
152+
[DEVELOPMENT.md](DEVELOPMENT.md)). A band of `--cap-points` anchors ending at the cap shares the
153+
cap anchor's offset, so if the boost settles deeper below the cap under load, an overclocked cap
154+
doesn't fall back down the steep stock curve.
153155
Points below the band stay stock; `--mhz` only raises the cap region. After writing, the tool reads the
154156
curve back to confirm.
155157

‎e2e/App.cs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ public static void SkipIfCurveTransient(string output)
3434
/// and the "offsets don't match / reverted" path was not taken.</summary>
3535
public static void AssertWriteConfirmed(string output)
3636
{
37-
Assert.Contains("Confirming curve point", output);
37+
Assert.Contains("Confirming operating point", output);
3838
Assert.DoesNotContain("didn't change after writing", output);
3939
}
4040

‎e2e/LoadTests.cs‎

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@ namespace SimpleNvidiaUndervolt.E2E;
77
/// Tests that need the GPU working, not idle: a Playwright-driven WebGL load
88
/// (<see cref="GpuLoadFixture"/>) holds the boost algorithm at its operating point, and the tests
99
/// assert where that point lands against an applied cap. This is the tool's actual contract — the
10-
/// flatten is placed so the boost settles on the requested cap point (one 5 mV step below the flat
11-
/// start), and only a loaded card shows where the boost settles — so these are the only tests that
12-
/// verify the undervolt does what it says, rather than that the curve write landed.
10+
/// cap→flat segment is aimed so the boost, settling one 5 mV step below the flat start, lands on
11+
/// the requested clock — and only a loaded card shows where the boost settles, so these are the
12+
/// only tests that verify the undervolt does what it says, rather than that the curve write landed.
1313
/// </summary>
1414
[Collection(GpuCollection.Name)]
1515
public sealed class LoadTests : IClassFixture<GpuLoadFixture>
@@ -36,10 +36,10 @@ public void PlainCapUnderLoad_BoostSettlesOnTheCapPoint()
3636
var (settleMv, settleMhz) = SampleSettledPointOrSkip();
3737
AssertSettledOnTheCap(stock, k, settleMv);
3838

39-
// ...at the cap point's own clock: at or a hair above the stock clock there (the settle can
40-
// sit in a 10 mV anchor gap, interpolating toward the flat start), never up at the flat top
41-
// and never fallen down the stock curve below the cap.
42-
Assert.InRange(settleMhz, stock[k].Mhz - 25, stock[k + 1].Mhz + 25);
39+
// ...at the cap point's own stock clock, wherever in the anchor gap the settle voltage
40+
// falls - the straddle holds the requested clock at the settle point, so the tolerance is
41+
// read-back bins plus the thermal drift since the stock read, not an anchor step.
42+
Assert.InRange(settleMhz, stock[k].Mhz - 25, stock[k].Mhz + 25);
4343
}
4444

4545
[SkippableFact]
@@ -58,8 +58,9 @@ public void ReducedClockCapUnderLoad_BoostHoldsTheTargetAtTheCap()
5858

5959
// The requested clock is held at the settle point. The flatten-at-the-cap shape fails this
6060
// (and the voltage check above): the boost lands one anchor below the cap, a stock-slope
61-
// step (~15-25 MHz) under the target.
62-
Assert.InRange(settleMhz, targetMhz - 25, targetMhz + (stock[k + 1].Mhz - stock[k].Mhz) + 25);
61+
// step under the target. The stock-slope-continued shape fails it too on a wide anchor gap,
62+
// overshooting by half the stock step.
63+
Assert.InRange(settleMhz, targetMhz - 25, targetMhz + 25);
6364
}
6465

6566
[SkippableFact]

‎e2e/TuningShapeTests.cs‎

Lines changed: 33 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ public sealed class TuningShapeTests
1818
public TuningShapeTests(GpuFixture gpu) => _gpu = gpu;
1919

2020
[SkippableFact]
21-
public void PlainCap_LeavesTheCapAndFlatStartAtStock_AndFlattensOnlyAbove()
21+
public void PlainCap_WritesTheStraddle_AndFlattensAbove()
2222
{
2323
Skip.IfNot(_gpu.Available, _gpu.SkipReason);
2424
var (stock, k) = ReadStockAndPickAnchor();
@@ -27,17 +27,23 @@ public void PlainCap_LeavesTheCapAndFlatStartAtStock_AndFlattensOnlyAbove()
2727
Assert.Equal(0, exitCode);
2828
App.AssertWriteConfirmed(output);
2929

30-
// The shape signature, exact in the written deltas: nothing below or at the flat start
31-
// moves (a plain cap keeps stock clocks through it - the boost then settles one step below
32-
// the flat start, i.e. on the cap point), and everything above only comes down. A
33-
// flatten-at-the-cap shape would show a negative delta at k+1.
30+
// The shape signature, exact in the written deltas: the cap anchor and the band below share
31+
// one offset at most a spread below stock (the segment is aimed so the settle voltage holds
32+
// the stock clock, which writes the cap anchor a little under it), the flat top starts
33+
// exactly the fixed 16 MHz spread above the cap anchor, and everything from there up is one
34+
// level plateau. A flatten-at-the-cap shape would leave no rise between k and k+1; the
35+
// stock-slope-continued shape would show a stock-step rise instead of the fixed spread.
3436
int[] deltas = ReadDeltasAlignedWith(stock);
35-
for (int i = 0; i <= k + 1; i++)
37+
Assert.Equal(deltas[k], deltas[k - 1]);
38+
Assert.InRange(deltas[k], -16_000, 0);
39+
long capAnchorKhz = stock[k].Mhz * 1000L + deltas[k];
40+
long flatTopKhz = stock[k + 1].Mhz * 1000L + deltas[k + 1];
41+
Assert.Equal(16_000, flatTopKhz - capAnchorKhz);
42+
for (int i = k + 2; i < deltas.Length; i++)
3643
{
37-
Assert.True(deltas[i] == 0, $"anchor {i} ({stock[i].Mv} mV) moved by {deltas[i]} kHz below the flat start");
44+
Assert.Equal(flatTopKhz, stock[i].Mhz * 1000L + deltas[i]);
3845
}
3946

40-
Assert.All(deltas, d => Assert.True(d <= 0, $"a plain cap wrote a positive delta ({d} kHz)"));
4147
Assert.True(deltas[^1] < -15_000,
4248
$"the top anchor barely moved ({deltas[^1]} kHz) - the cap did not flatten the curve");
4349
}
@@ -53,18 +59,18 @@ public void ReducedClockCap_WritesOneSharedOffset_FromTheBandThroughTheFlatStart
5359
Assert.Equal(0, exitCode);
5460
App.AssertWriteConfirmed(output);
5561

56-
// The cap's offset is one value carried by the band below the cap, the cap anchor and the
57-
// flat start above it - that shared offset is what keeps the clock on a stock-parallel
58-
// slope however deep the boost settles. Exact equality holds regardless of whether the run
59-
// planned from the saved reference or a live read.
60-
// The margin is loose on purpose: planned from the saved reference, the written delta is
61-
// (live - reference) - 60, so the thermal gap between this run and the reference capture
62-
// rides on it. The two equalities below are the actual shape signature; this only has to
63-
// prove a real reduction landed.
62+
// The cap anchor's offset is one value carried by the band below it - that shared offset is
63+
// what keeps the clock on a stock-parallel slope however deep the boost settles - and the
64+
// flat top starts exactly the fixed 16 MHz spread above the cap anchor. Both equalities
65+
// hold regardless of whether the run planned from the saved reference or a live read.
66+
// The margin on the reduction is loose on purpose: planned from the saved reference, the
67+
// written delta is (live - reference) - 60, so the thermal gap between this run and the
68+
// reference capture rides on it; this only has to prove a real reduction landed.
6469
int[] deltas = ReadDeltasAlignedWith(stock);
6570
Assert.True(deltas[k] < -20_000, $"the cap anchor's delta ({deltas[k]} kHz) is not the requested reduction");
6671
Assert.Equal(deltas[k], deltas[k - 1]);
67-
Assert.Equal(deltas[k], deltas[k + 1]);
72+
Assert.Equal(16_000,
73+
(stock[k + 1].Mhz * 1000L + deltas[k + 1]) - (stock[k].Mhz * 1000L + deltas[k]));
6874
}
6975

7076
[SkippableFact]
@@ -85,7 +91,7 @@ public void MemoryOnlyTune_SetsTheMemoryClock_AndLeavesTheCurveAtStock()
8591
}
8692

8793
[SkippableFact]
88-
public void Status_WhileCapped_ReportsTheCapPoint()
94+
public void Status_WhileCapped_ReportsTheOperatingPoint()
8995
{
9096
Skip.IfNot(_gpu.Available, _gpu.SkipReason);
9197
var (stock, k) = ReadStockAndPickAnchor();
@@ -96,14 +102,17 @@ public void Status_WhileCapped_ReportsTheCapPoint()
96102

97103
var (exitCode, output) = App.Run(null, "status");
98104

99-
// The status line names the point the boost settles on - the cap anchor. One anchor of
100-
// slack: a read at a temperature away from the reference can seam the flat's edge, which
101-
// moves the reported point one anchor down (see GpuTuning.EffectiveCapPoint).
105+
// The status line names the point the boost settles on: between the cap anchor and one
106+
// boost step below the flat start. One anchor of slack each way: a read at a temperature
107+
// away from the reference can seam the flat's edge, which moves the inferred point one
108+
// anchor - a seamed flat start puts it one boost step below the anchor above the flat
109+
// (see GpuTuning.EffectiveOperatingPoint).
102110
Assert.Equal(0, exitCode);
103-
Match m = Regex.Match(output, @"capped at (\d+) mV");
104-
Assert.True(m.Success, $"no 'capped at' report in: {output}");
111+
Match m = Regex.Match(output, @"operating point (\d+) mV");
112+
Assert.True(m.Success, $"no 'operating point' report in: {output}");
105113
Assert.InRange(int.Parse(m.Groups[1].Value, CultureInfo.InvariantCulture),
106-
stock[k - 1].Mv, stock[k].Mv);
114+
stock[k].Mv - GpuTuning.BoostStepMv,
115+
Math.Max(stock[k + 1].Mv, stock[k + 2].Mv - GpuTuning.BoostStepMv));
107116
}
108117

109118
[SkippableFact]

0 commit comments

Comments
 (0)