Skip to content

Commit 6c3442d

Browse files
vupleaclaude
andcommitted
Bound the refinement probes and fix its edge-case reporting
Review findings on the refinement and its reporting, fixed together: The re-centered-cap candidate assumed the settle sits mid-gap; on the 5 mV anchor gaps three of four Blackwell caps have, the settle sits on the cap anchor and the formula proposed a cap 16 MHz low - a probe the scorer could keep. The cap now derives from the plan's own geometry. Every probed pair is also held to the plan's shape - the cap within a bin of the plan's, the rise within 8..24 MHz (below 8 the pair measurably folds; the e2e shape assertions cover exactly this space) - and the flat scan alternates around the plan's value before walking deeper, so an upward landing is reachable within the budget. SnapNote no longer infers a settle from a single elevated top anchor - one bin of noise on the curve's last anchor is not a plateau, the same rule EffectiveOperatingPoint already encodes. The reference-planned report only claims "the written offset matches the reference exactly" when the refinement didn't adjust it, and the kept-closest line names the point it kept. The e2e settle ceiling is clamped absolutely at three anchors above the cap - the plateau inference can walk far up a tilted read, and the measured thermal steps span less. The written-flat levelness assertion gets one bin of per-anchor slack: the flat is level relative to the curve the plan was built from, and anchors step individually with temperature between that and the test's own live read. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PACndXGEsXBAWWWjSvpJpW
1 parent eeb7425 commit 6c3442d

7 files changed

Lines changed: 152 additions & 52 deletions

File tree

‎DEVELOPMENT.md‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -96,9 +96,9 @@ settles. NVIDIA doesn't document this; measured on an RTX 5090 under sustained l
9696
`GpuTuning.BuildCurvePlan` therefore writes the cap→flat segment as a straight line through
9797
(settle voltage, requested clock) with a fixed 16 MHz rise: the smallest the driver keeps, and a
9898
bound on the realized miss at half of it, with the post-write refinement absorbing the bin
99-
rounding. On a 10 mV gap this operates the card 5 mV above the
100-
named cap (`--mv 900` runs at 905 mV); keeping the voltage at or under the cap would cost a
101-
stock-slope step of clock, the worse side of the trade.
99+
rounding. On a 10 mV gap this operates the card 5 mV above the named cap (`--mv 900` runs at
100+
905 mV); keeping the voltage at or under the cap would cost a stock-slope step of clock, the
101+
worse side of the trade.
102102

103103
## Generations
104104

‎e2e/LoadTests.cs‎

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -97,16 +97,18 @@ public void TelemetryUnderLoad_ReportsARealOperatingPoint()
9797
/// actually holds: no lower than the cap point (a flatten-at-the-cap shape settles one step
9898
/// BELOW the cap, which is what the lower bound exists to catch) and no higher than one boost
9999
/// step below the effective top plateau's first anchor. The upper bound comes from the
100-
/// read-back rather than the written shape because the driver can round the written flat's
101-
/// tail a bin up, moving the plateau — and the settle — one or more anchors above the written
102-
/// flat start (the post-apply report carries a note when it does). The clock assertion beside
103-
/// each call bounds the plateau's height, so a wrong written shape can't vouch for itself
104-
/// through its own wrong plateau.</summary>
100+
/// read-back rather than the written shape because anchors step with temperature between the
101+
/// apply and the loaded settle, moving the plateau — and the settle — a step or two up (the
102+
/// post-apply report carries a note when the write itself staircased). The plateau inference
103+
/// can walk far up a tilted read, so the ceiling is also clamped absolutely at three anchors
104+
/// above the cap — beyond every step measured — keeping the bound real; the clock assertion
105+
/// beside each call bounds the plateau's height the rest of the way.</summary>
105106
private void AssertSettledOnTheEffectiveCap(IReadOnlyList<(int Mv, int Mhz)> stock, int k, int settleMv)
106107
{
107108
(int Mv, int Mhz)? point = GpuTuning.EffectiveOperatingPoint(NvApi.GetVfCurve(_gpu.Gpu));
108109
Skip.If(point is null, "the effective curve didn't read back cleanly - retry.");
109-
Assert.InRange(settleMv, stock[k].Mv - 2, point!.Value.Mv + 2);
110+
int ceilingMv = Math.Min(point!.Value.Mv, stock[Math.Min(k + 3, stock.Count - 1)].Mv);
111+
Assert.InRange(settleMv, stock[k].Mv - 2, ceilingMv + 2);
110112
}
111113

112114
/// <summary>The point the boost settled on: after a warm-up pause, samples the live telemetry

‎e2e/TuningShapeTests.cs‎

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,14 @@ public void PlainCap_WritesTheStraddle_AndFlattensAbove()
4141
long capAnchorKhz = stock[k].Mhz * 1000L + deltas[k];
4242
long flatTopKhz = stock[k + 1].Mhz * 1000L + deltas[k + 1];
4343
Assert.InRange(flatTopKhz - capAnchorKhz, 8_000, 24_000);
44+
// The written flat is exactly level relative to the curve the plan was built from; this
45+
// measures it against a fresh live read, and anchors step individually by a bin with
46+
// temperature between the two reads (see DEVELOPMENT.md), so each anchor gets one bin of
47+
// slack. A stock-slope-continued shape still fails - its clocks keep climbing across the
48+
// whole flat.
4449
for (int i = k + 2; i < deltas.Length; i++)
4550
{
46-
Assert.Equal(flatTopKhz, stock[i].Mhz * 1000L + deltas[i]);
51+
Assert.InRange(stock[i].Mhz * 1000L + deltas[i], flatTopKhz - 8_000, flatTopKhz + 8_000);
4752
}
4853

4954
Assert.True(deltas[^1] < -15_000,
@@ -110,8 +115,8 @@ public void Status_WhileCapped_ReportsTheOperatingPoint()
110115
// against the read-back until the realized point is the plan's settle (one boost step
111116
// below the flat start, floored at the cap), so a same-power-state status read moments
112117
// later reports that point - one boost step of slack each way covers an anchor stepping
113-
// with temperature between the two reads, and a refinement that ran out of probes on a
114-
// knife-edge write. The settle-law physics itself is LoadTests' subject.
118+
// with temperature between the two reads. A kept-closest refinement whose miss exceeds a
119+
// step fails here by design. The settle-law physics itself is LoadTests' subject.
115120
Assert.Equal(0, exitCode);
116121
Match m = Regex.Match(output, @"operating point (\d+) mV");
117122
Assert.True(m.Success, $"no 'operating point' report in: {output}");

‎src/CurveRefinement.cs‎

Lines changed: 49 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -8,23 +8,39 @@ namespace SimpleNvidiaUndervolt;
88
/// rounding, but it is deterministic at a fixed thermal state and readable back in milliseconds,
99
/// so the exact write is found by probing: when the plan's own write misses its operating point,
1010
/// nearby (cap, flat) pairs are written and judged against their read-backs, keeping the best
11-
/// realization — measured, never modeled, so it carries no constants from any one card. The
12-
/// budget is small and the values move by less than a bin, and a probe sequence that never lands
13-
/// keeps the closest attempt: the refinement can only improve on the plan's write, and the
14-
/// realized point is reported from the final read-back either way. What it cannot beat is the
15-
/// thermal slice: anchors step with temperature, so a point landed at apply time can still sit a
16-
/// step away at operating temperature — physics the report's notes carry, not error.
11+
/// realization. The candidate heuristics are tuned on measured hardware, but correctness never
12+
/// rests on them: every pair is judged by its own read-back, every pair stays within
13+
/// <see cref="MaxCapAdjustMhz"/>/<see cref="MinRiseMhz"/>..<see cref="MaxRiseMhz"/> of the plan's
14+
/// shape, and a probe sequence that never lands keeps the closest attempt — the refinement can
15+
/// only improve on the plan's write, and the realized point is reported from the final read-back
16+
/// either way. What it cannot beat is the thermal slice: anchors step with temperature, so a
17+
/// point landed at apply time can still sit a step away at operating temperature — physics the
18+
/// report's notes carry, not error.
1719
/// </summary>
1820
internal static class CurveRefinement
1921
{
20-
/// <summary>Extra writes the probing may spend after the plan's own. The measured cases
21-
/// converge in one or two; the budget covers a full bin of flat values either way.</summary>
22+
/// <summary>Extra writes the probing may spend after the plan's own — the measured cases
23+
/// converge in one or two — plus at most one landing re-write when the best pair isn't the
24+
/// last one probed.</summary>
2225
internal const int MaxExtraWrites = 8;
2326

2427
/// <summary>The clock slack an on-target realization may keep: half a read-back bin — the
2528
/// resolution the effective curve reports at, so probing below it chases noise.</summary>
2629
internal const int ClockToleranceMhz = 4;
2730

31+
/// <summary>How far a probed cap may sit from the plan's (MHz): about a bin — the refinement
32+
/// absorbs bin rounding, so a candidate further out is chasing something else.</summary>
33+
internal const int MaxCapAdjustMhz = 8;
34+
35+
/// <summary>The written cap→flat rise a probe may carry (MHz). Below the minimum the pair
36+
/// measurably folds into one plateau and drops the settle a step (see
37+
/// <c>GpuTuning.FlatSpreadMhz</c>); above the maximum the probe has left the plan's shape.
38+
/// The bounds also keep every write the probing can produce inside the shape the e2e suite
39+
/// asserts.</summary>
40+
internal const int MinRiseMhz = 8;
41+
42+
internal const int MaxRiseMhz = 24;
43+
2844
/// <summary>What the refinement left on the card: the deltas the final write holds (also
2945
/// copied into the plan's array by <see cref="GpuTuning.Apply"/>), the settle point of the
3046
/// final read-back (null when it wasn't readable), how many extra writes were spent, and
@@ -37,10 +53,10 @@ internal sealed record Outcome(int[] DeltasKhz, (int Mv, int Mhz)? Realized, int
3753
/// point misses the plan's, probes alternative (cap, flat) pairs through
3854
/// <paramref name="apply"/> — which writes a delta array and returns the effective read-back.
3955
/// Candidates come from the read-back itself (the flat re-aimed at the value its first anchor
40-
/// realized, the cap re-centered to keep the settle clock) with a small scan as fallback.
41-
/// Ends on the best-scoring pair, re-applied if a later probe overwrote it; the outcome's
42-
/// realization is always the final read-back's, so a thermal step between probes can't leave
43-
/// a stale claim behind.
56+
/// realized, the cap re-centered to keep the settle clock) with an alternating scan around
57+
/// the plan's flat as fallback. Ends on the best-scoring pair, re-applied if a later probe
58+
/// overwrote it; the outcome's realization is always the final read-back's, so a thermal step
59+
/// between probes can't leave a stale claim behind.
4460
/// </summary>
4561
public static Outcome Refine(Func<int[], IReadOnlyList<(int Mv, int Mhz)>> apply,
4662
IReadOnlyList<(int Mv, int Mhz)> stock, GpuTuning.CurvePlan plan,
@@ -66,7 +82,8 @@ public static Outcome Refine(Func<int[], IReadOnlyList<(int Mv, int Mhz)>> apply
6682
break;
6783
}
6884

69-
if (v <= c || !tried.Add((c, v)))
85+
if (v - c < MinRiseMhz || v - c > MaxRiseMhz
86+
|| Math.Abs(c - plan.CapMhz) > MaxCapAdjustMhz || !tried.Add((c, v)))
7087
{
7188
continue;
7289
}
@@ -118,26 +135,38 @@ private static int Score((int Mv, int Mhz)? realized, GpuTuning.CurvePlan plan)
118135

119136
/// <summary>The probe sequence: first the pairs the read-back suggests — the flat re-aimed at
120137
/// the value its first anchor realized (the read-back is the local bin grid), with the cap
121-
/// kept, re-centered so the settle-point clock stays on the request, and dropped a step — then
122-
/// a flat-value scan around the plan's own, downward through one bin and one step up.</summary>
138+
/// kept, re-centered so the settle-point clock stays on the request, and dropped a step —
139+
/// then a flat-value scan alternating around the plan's own before walking deeper down: the
140+
/// driver rounds both ways, and a budget spent all downward would never reach an upward
141+
/// landing. The consuming loop's shape bounds filter what a distorted read-back suggests.</summary>
123142
private static IEnumerable<(int C, int V)> Candidates(GpuTuning.CurvePlan plan,
124143
IReadOnlyList<(int Mv, int Mhz)> planReadBack)
125144
{
126145
if (GpuTuning.PointAtVoltage(planReadBack, plan.FlatMv) is { } flatRead)
127146
{
128147
int vr = flatRead.Mhz;
129148
yield return (plan.CapMhz, vr);
130-
yield return (2 * plan.SettleMhz - vr, vr);
149+
yield return (RecenteredCap(plan, vr), vr);
131150
yield return (plan.CapMhz - 4, vr - 8);
132151
}
133152

134-
for (int d = 1; d <= 6; d++)
153+
foreach (int d in new[] { -1, +1, -2, +2, -3, -4, -5, -6 })
135154
{
136-
yield return (plan.CapMhz, plan.FlatMhz - d);
155+
yield return (plan.CapMhz, plan.FlatMhz + d);
137156
}
157+
}
138158

139-
yield return (plan.CapMhz, plan.FlatMhz + 1);
140-
yield return (plan.CapMhz, plan.FlatMhz + 2);
159+
/// <summary>The cap that keeps the settle-point clock on the plan's request when the flat is
160+
/// re-aimed at <paramref name="vr"/>. The settle sits <c>t</c> of the way into the cap→flat
161+
/// gap (the plan's own geometry, not a fixed fraction — anchor gaps vary within one curve),
162+
/// so the segment through (settle, request) solves to <c>(S − t·vr) / (1 − t)</c>; at
163+
/// <c>t = 0</c> — a gap of one boost step or same-mV anchors — the settle sits on the cap
164+
/// anchor and the cap is the requested clock itself.</summary>
165+
private static int RecenteredCap(GpuTuning.CurvePlan plan, int vr)
166+
{
167+
double t = plan.FlatMv == plan.CapMv ? 0
168+
: (double)(plan.SettleMv - plan.CapMv) / (plan.FlatMv - plan.CapMv);
169+
return t == 0 ? plan.SettleMhz : (int)Math.Round((plan.SettleMhz - t * vr) / (1 - t));
141170
}
142171

143172
/// <summary>The delta array for a probed (cap, flat) pair — the plan's own shape

0 commit comments

Comments
 (0)