Power & Thermals / deep
Per-core Curve Optimizer: finding each core's voltage floor
Per-core CO counts found by bisection, validated at idle rather than under load
Revised 2026-08-28 · 16 min · risk: moderate
We fit a Curve Optimizer count to each core on an AM5 part, by bisection against that core's own floor rather than copying a forum post. Everything here follows from one fact: a count shifts the voltage the SMU requests at every point of the fused V/F curve, it does not pin a voltage. The bench ends with sixteen recorded counts, an effective-clock check proving none stretches the clock, and a day of validation weighted toward idle and resume rather than load.
The fused curve, and what a count moves§
Every core leaves the fab with its own voltage/frequency table burned into fuses. The SMU never reads a voltage from your setup screen: it takes a frequency target from the boost algorithm, looks up the VID this core was characterised to need there, then adds terms for temperature, for the reliability governor and for regulator telemetry. Curve Optimizer edits one input to that sum — the lookup.
A count is a signed integer, -30 to +30 on current AGESA. Negative means request less than the fused table says, at every frequency point on this core. The step is widely quoted as three to five millivolts, but AMD has never documented it and it is constant neither between parts nor along one part's curve. Treat -30 as roughly a hundred millivolts below fused, not as arithmetic.
The distinction most advice elides: a count is an offset applied to a request, not a voltage. Set -20 and there is no frequency at which the core runs at a fixed voltage. Under all-core load it requests a mid-curve VID minus the offset; two seconds later, idle with one thread awake, it requests its highest fused VID minus the same offset. Two very different margins to the floor, from one number.
The offset is uniform in a second sense: one count covers the whole curve on Zen 3 and Zen 4. Zen 5 addresses this. Curve Shaper, added in AGESA 1.2.0.2, splits the curve into five frequency and three temperature bands, taking a count in each of the fifteen cells, so the top can be left near fused while the mid-frequency region is pulled down hard. Where you have it, run the procedure below per band as well as per core.
Binning, CPPC ranking and the preferred core§
Cores on one die are not equivalent. Production test grades them, the ordering is fused, and it reaches the OS through ACPI CPPC as a performance value. Windows uses it to park light work on the best core; Linux exposes it through amd_pstate.
lscpu -e=CPU,CORE,NODE,MAXMHZ
cat /sys/devices/system/cpu/amd_pstate/prefcore # enabled | disabled
cat /sys/devices/system/cpu/amd_pstate/status # active | guided | passive
grep . /sys/devices/system/cpu/cpu*/cpufreq/amd_pstate_prefcore_ranking
grep . /sys/devices/system/cpu/cpu*/cpufreq/amd_pstate_highest_perf
grep . /sys/devices/system/cpu/cpu*/acpi_cppc/highest_perf
/sys/devices/system/cpu/cpu*/cpufreq/amd_pstate_prefcore_ranking
/sys/devices/system/cpu/cpu0/cpufreq/amd_pstate_prefcore_ranking:180
/sys/devices/system/cpu/cpu1/cpufreq/amd_pstate_prefcore_ranking:166
/sys/devices/system/cpu/cpu2/cpufreq/amd_pstate_prefcore_ranking:159
/sys/devices/system/cpu/cpu5/cpufreq/amd_pstate_prefcore_ranking:196
Read the field for what it is: a performance value, not a placing. Higher is better; sort descending for an ordering. amd_pstate_prefcore_ranking arrived in 6.9; on older kernels use acpi_cppc/highest_perf. It is only useful once mapped: the firmware menu is indexed by physical core, Linux by logical CPU. lscpu -e maps the two, and assuming cpu0 and cpu1 are different cores has you testing one core twice and never another.
The advice to "just run -30 all core" fails structurally. The best-ranked core boosts highest, which is to say it is characterised closest to the edge of its own curve; empirically it is usually the core tolerating the least negative offset, sometimes by twenty counts against its weakest sibling. A flat offset is therefore set by the strongest core whether you intended it or not, and the reset it produces names no core.
PPT, TDC, EDC, scalar and boost override§
Curve Optimizer does not raise a clock. It lowers the power drawn at a given clock, the boost algorithm spends the difference, and which limit it spends against decides how much you get back.
- PPT
- Package power in watts, against telemetry the regulator reports. Binds first on sustained all-core work, and returns the most frequency to a negative offset.
- TDC
- Sustained current into the socket, thermally weighted. Binds on long all-core loads once the regulator is warm.
- EDC
- Peak current over a short window. Binds on bursty transitions and AVX entry, and is the limit most often misreported.
All three are enforced against numbers the regulator reports over SVI3, not a current anyone measured. A board whose telemetry reference suits a different stage count reports current wrong by a fixed ratio, and the SMU throttles on that. Confirm reported against measured before trusting any limit utilisation figure; the loadline and thermal note covers the method.
PBO Scalar, 1X to 10X, relaxes the reliability governor so the part may sit at elevated voltage longer than the stock lifetime model permits — it moves voltage up. Running 10X alongside a large negative count is two settings pulling against each other, and it is the scalar that carries the degradation risk. We bench at 1X until every count is final.
Max CPU Boost Clock Override extends the top of the frequency range by up to +200 MHz. It adds no headroom and promises no clock; it only lets the algorithm request a point past the end of the table, moving the operating point further into the region where the offset has least margin. Hold it at 0 while you find the floors.
The idle crash, and why load tests validate nothing§
The signature that makes this hard: a too-negative count is stable under heavy all-core load and crashes at idle or under light single-threaded work. That inverts everything a reader knows about stress testing, and it is why most counts in circulation are unvalidated.
The mechanism is in Fig 1. Under all-core AVX load the cores are held by PPT and die temperature at a mid-curve frequency, where the fused request sits well clear of the core's floor; take a hundred millivolts off and margin remains. Under one light thread, package cool and no limit near binding, the algorithm requests the highest fused point on the highest-ranked core — a point that exists precisely because production test found the edge there. The fused request is barely above the floor by construction, and the same hundred millivolts now exceeds the whole margin.
So the failure appears when the machine has nothing to do: at the desktop, in a browser tab, on a timer wakeup, during resume. Idle is not a low-stress state for a curve offset; it is the highest-voltage state the core ever asks for, and the transitions into it are fast enough that the regulator has not settled when the request lands. Resume is worse again, working the low-frequency end where a fixed offset is a far larger fraction of a smaller request.
The corollary is blunt: twelve hours of all-core Prime95 tells you nothing about a count. It is a valid test of cooling and of the PPT and EDC configuration, but passing it is not evidence of CO stability.
There is also a failure that never crashes. When the delivered supply falls below what the core needs, Zen's droop detector stretches the clock rather than faulting: reported frequency holds, work per second drops, the machine stays up. An offset tuned by watching clocks and waiting for a crash settles here and looks like a success.
Bench§
Ryzen 9 7950X, ASRock X670E Taichi, BIOS 3.10 (AGESA ComboAM5PI 1.2.0.3c), Thermalright Peerless Assassin, open bench at 22 °C ambient measured 30 cm from the intake. Debian 13, kernel 6.12, amd_pstate in active mode, schedutil, rasdaemon running. The Intel comparison was taken on a Core i9-14900K and Z790 on the same bench, microcode 0x12B.
Preconditions. Every item below produces the same symptom as a CO fault seen from userspace, so each must be excluded first:
- memory at a validated profile, then left alone. A marginal
tRCDor under-settRFCproduces idle resets indistinguishable from a curve fault; settle it first, per the DDR5 subtiming note. - SoC and VDDIO fixed at a value you chose, never auto.
- PBO limits at motherboard or a recorded manual triple, scalar 1X, boost override 0.
- all counts at 0 and one full validation pass at stock, recorded. This is the control, and the baseline for the effective-clock check.
- ambient logged. A floor found at 22 °C is not a floor at 30 °C: the curve carries a temperature term and the offset does not.
Finding each core's floor§
Per-core counts live in firmware, under the AMD Overclocking tree rather than the vendor's own page. Magnitude and sign are separate fields — a common source of an accidental positive offset.
Advanced
AMD Overclocking -> (accept the warning)
Precision Boost Overdrive
Precision Boost Overdrive Advanced
PBO Limits Motherboard | Manual (PPT/TDC/EDC)
Precision Boost Overdrive Scalar Manual -> 1X
Max CPU Boost Clock Override Enabled (Positive) -> 0 MHz
Platform Thermal Throttle Limit 85
Curve Optimizer
Curve Optimizer Per Core
Core 0 Curve Optimizer Sign Negative
Core 0 Curve Optimizer Magnitude 20
Bisecting one core§
- Start at 0 and step that core down in fives, running the single-thread test at each step until it fails. Record the first failing magnitude; nothing else changes meanwhile.
- Bisect between the last pass and the first failure; two or three iterations resolves to a single count. That value is the floor for this core, at this ambient, under this test — not the setting.
- Back off from it. We take five counts off the floor: the test does not visit every state the machine will, the room will be warmer in August, and the part ages. Shipping at the measured floor fails later.
- Move to the next core. One core deviates from its established value at a time; change two and a crash tells you nothing.
On sixteen cores this takes days, not an evening, and order matters. Do the two highest-ranked cores first: they carry the least margin, and getting them wrong produces the unattributable reset that costs the most time.
The tests that count§
- CoreCycler with Prime95
- Pins one core at a time by affinity, runs small FFTs, and logs which core faulted rather than taking the machine down. Six minutes per core per iteration, three iterations, once per instruction-set profile: SSE finds low-voltage faults, AVX2 and AVX512 others.
- y-cruncher, one core
- The component stress test pinned with
tasksetto both SMT siblings of one physical core, twenty minutes each. It exercises arithmetic paths the FFT loop does not, and is the fixed-work timing reference. - Idle soak
- The machine at the desktop, nothing running but the logger, twelve hours minimum. This is the test that finds what the others miss; anything keeping a core busy invalidates it.
- Resume
- Thirty suspend and resume cycles under
rtcwake. Check/sys/power/mem_sleepfirst; on most AM5 boards onlys2idleis offered, and that is the path to test.
[General]
stressTestProgram = PRIME95
runtimePerCore = 360
numberOfThreads = 1
coreTestOrder = Alternate
skipCoreOnError = 1
maxIterations = 3
[Prime95]
mode = SSE
FFTSize = Smallest
cat /sys/power/mem_sleep
sudo systemctl enable --now rasdaemon
for i in $(seq 1 30); do
sudo rtcwake -m mem -s 45
sleep 45
done
sudo ras-mc-ctl --errors
journalctl -k -p warning --since '12 hours ago' | grep -Ei 'mce|hardware error'
Reading it on Linux, honestly§
Four things get recorded at every candidate count.
Effective clock. turbostat's Bzy_MHz derives from APERF and MPERF, which count cycles actually delivered; the frequency the SMU reports is a request. Pin a single-thread load to one core, hold Busy% near 100, and compare against the count-0 control. This is what Windows tuners read as effective clock, off the same counters.
sudo turbostat --quiet --interval 2 \
--show Core,CPU,Avg_MHz,Busy%,Bzy_MHz,PkgWatt,CorWatt,CoreTmp
taskset -c 5,21 ./y-cruncher # both SMT siblings of one core, per lscpu -e
sudo perf stat -a -e power/energy-pkg/ -- sleep 30
cpupower monitor -m Mperf
Work per second. Effective clock alone is not proof. Record a fixed-work result — the same y-cruncher digit count, wall time — at count 0 and at the candidate. Slower at lower voltage means stretching, whatever the clock says.
Package power. perf stat -a -e power/energy-pkg/, or turbostat's PkgWatt and CorWatt, both from the RAPL energy MSRs. A working negative offset shows up as fewer watts for identical work; if the watts do not move, the offset is not reaching the SMU.
Errors. rasdaemon and ras-mc-ctl --errors, plus the kernel log. A machine check carries an APIC ID, which names the core; a reset carries nothing, so the logger runs before the soak.
Where Linux is worse:
- There is no Ryzen Master; set counts in firmware.
ryzen_smuexposes the SMU mailbox and PM table at/sys/kernel/ryzen_smu_drv/, andryzen_monitordecodes it into per-core VID, power and effective frequency — but the layout changes with AGESA, and an unrecognised revision is unsupported. - Per-core VID is otherwise invisible.
k10tempgives Tctl and per-CCD Tccd, nothing electrical;zenpower3restores SVI telemetry as hwmon, but is out-of-tree and conflicts withk10temp, which must be blacklisted first.amd_energyis not in mainline at all. - There is no CoreCycler. The affinity rotation and per-core fault logging must be scripted around a Linux Prime95 build — the weakest part of this procedure.
- Governor choice changes what you certify. Run load tests under
performance, but soak under the governor you actually boot with.
Intel: adaptive, offset and per-V/F-point§
The vocabulary does not map across. Intel exposes three voltage modes, and they are not three flavours of one idea.
- Override
- A fixed VID at all frequencies. This genuinely sets a voltage, and it is wrong for anything that idles: the idle points receive the load voltage.
- Adaptive
- The fused curve is kept and a turbo voltage target applies above a break point, with an optional offset on top. Closest in spirit to Curve Optimizer, but reaching the turbo region only.
- Offset
- A signed global adjustment across the range: the direct analogue of a count, minus the per-core part.
From 12th generation onward desktop firmware exposes per-core ratio limits, but the voltage adjustment stays package-level: there is no per-core equivalent of CO. What exists instead is V/F point editing — eleven points, each taking its own offset — far finer along the frequency axis than one count, and closer to Zen 5's Curve Shaper than to Zen 4's. It is per-curve, not per-core, so the weakest core sets the limit for all.
Undervolt protection is the other difference. After the Plundervolt work (CVE-2019-11157), Intel shipped firmware blocking runtime writes to MSR 0x150, the mechanism intel-undervolt relied on; enabling SGX forces the protection on. Where a toggle exists it appears as Undervolt Protection or Overclocking Lock, and on many mobile platforms it does not exist. On a locked platform, offsets are set in firmware or not at all.
One habit transfers. Microcode changes the requested curve — Raptor Lake's 0x12B revision raised requested voltages in response to a degradation mechanism — so an offset validated before a microcode update is not validated after. The same holds on AM5 across an AGESA change.
Failure modes§
The corrective action is almost always narrower than the instinct: raise one core, not sixteen.
| Symptom | Root cause | Corrective action |
|---|---|---|
| Reset or freeze at the desktop, nothing logged | One core below its floor at the top of the curve; nothing is recorded | Raise the two highest-ranked cores by 5 and re-soak — not all sixteen, which hides the culprit |
| Passes 12 h of all-core Prime95, dies opening a browser | Validated at the wrong point of the curve | Discard the result. Re-validate per core, single-thread, plus the idle soak |
| Machine check against one APIC ID under light load | That core's request is below its floor | Raise that core alone by 3 to 5 counts |
| Clocks at full boost, fixed-work benchmark slower than control | Clock stretching; the core runs below its stable voltage without faulting | Back off until work per second beats the count-0 control; reported clock is not evidence |
| Stable for a week, then resets on resume only | The low-frequency end of the curve, plus SMU re-initialisation | Reduce magnitude, or shape the low-frequency band on Zen 5. Retest with 30 rtcwake cycles |
| Instability returns after a firmware update | AGESA revised the curve and reset the reliability model | Revalidate from 0. Counts do not carry across AGESA revisions |