Skip to content

soc: apple: clpc: hold the T8140 GPU floor only for streaming GPU work - #464

Merged
DjDeveloperr merged 5 commits into
integration/aurorafrom
neo/t8140-clpc-gpu-streaming-20261025
Oct 27, 2026
Merged

DjDeveloperr merged 5 commits into
integration/aurorafrom
neo/t8140-clpc-gpu-streaming-20261025

Conversation

@DjDeveloperr

Copy link
Copy Markdown
Contributor

Summary

The T8140 GPU floor from #440 (DCS 4, fabric 3) is held whenever graphics is powered. On J700 that costs power where it buys nothing: +0.25 W (5%) during 1080p video in Firefox, +0.11 W during light browsing. Under sustained heavy rendering, the floor also ends slower and hotter than no floor. Two changes restrict it to what it is for, streams of light GPU submissions:

  1. hold the T8140 GPU floor only while GPU work streams.
    • I sampled the graphics power state every millisecond. 1080p video kept graphics powered 56% of the time and light browsing 34%, but never for more than 63 ms at a time. The firmware powers graphics off a few milliseconds after the GPU idles, so frame-paced loads power it up and down with every frame.
    • Light compute and render submission streams kept it powered throughout.
    • So graphics counts as GPU demand only after staying powered for 64 ms, with no sample finding it off and no sampling gap long enough to have missed that. CLPC samples on time while graphics is powered.
  2. release the T8140 GPU floor while the PMP limits the DCS.
    • I read each PMC agent's floor every millisecond. The PMP (agent 0) votes the DCS to state 4 in every sample of a light submission stream.
    • Under sustained heavy rendering, the PMP votes state 3 in 62% of samples, holding the GPU to its power budget.
    • So while GPU work streams and the PMP votes the DCS below the floor's state, the floor is released, and it stays released for 1 s after the PMP last did so.

The same change is omacom/linux-aurora#226, where maralcbr asked for it. The first commit is adapted to this tree, which has no graphics read-error counter: a failed read still counts as powered.

Measurements

Neo (J700), one boot per kernel in a single session, on battery. These were taken on the 7.2.11 omacom base of #226 (19f4965 + #226 + #227 + #228), where the CLPC driver differs only in that read-error counter. "Powered" is the floor as merged in #440; "this" is these two commits.

leg no floor powered this
idle 3.569 W 3.505 W 3.541 W
1080p video 4.903 W 5.151 W 4.975 W
light browsing 3.971 W 4.085 W 3.970 W
render.heavy d4 at the end of 90 s 659.5 µs, 88.8 °C 671.1 µs, 90.3 °C 656.8 µs, 88.7 °C
compute.light d1 373.7 µs 320.5 µs 319.3 µs
compute.light d2 190.7 µs 166.0 µs 165.8 µs
render.light d2 87.2 µs 65.6 µs 66.6 µs
render.light d32 67.8 µs 61.4 µs 61.8 µs

The fabric floor was held for 21% of the video leg (was 100%), 2% of browsing (was 51%) and 5% of the heavy leg (was 100%). Light submissions keep the floor's gain.

Testing

  • Builds with W=1 against the 7.3 tree. checkpatch --strict is clean.
  • The debugfs status line now shows how long graphics has been powered and when the PMP last limited the DCS.

The T8140 GPU floor was held whenever graphics was powered. On J700 that
costs 0.38 W (about 8%) during 1080p video playback in Firefox and
0.16 W during light browsing, where the floor gains nothing.

The firmware powers graphics off a few milliseconds after the GPU idles.
Sampled every millisecond, 1080p video powered graphics for 56% of the
time and light browsing for 34%, but never for more than 63 ms at a
time, while light compute and render submission streams kept it powered
throughout.

Count graphics as GPU demand only once it has stayed powered for 64 ms,
with no sample finding it off and no gap in sampling long enough to have
missed that, and sample on time while it is powered. A failed read still
counts as powered. The debugfs status shows how long graphics has been
powered.
…e DCS

Under sustained heavy rendering on J700, the GPU floor ended 1.8%
slower and 1.3 C hotter than no floor over 90 s, for 0.14 W more.

The PMP, voter agent 0, votes the DCS to its top state for streams of
light GPU submissions: it did for every millisecond sample of light
compute and render streams. Under heavy rendering it lowers the DCS,
to state 3 for 62% of the samples, to stay within the GPU's power
budget. The floor then only overrides that and takes headroom from the
GPU. macOS also lets the fabric drop for heavy GPU work.

While GPU work streams, read the PMP's floor word. When it votes the
DCS below the GPU floor's state, release the GPU floor and keep it
released for 1 s after the PMP last did so. The debugfs status shows
when that was.

Measured on J700 with both changes, one boot each on battery, against
no GPU floor and the floor held whenever graphics is powered:

                               no floor   powered   this
  1080p video, W                 4.903     5.151    4.975
  light browsing, W              3.971     4.085    3.970
  render.heavy d4 at 90 s, us    659.5     671.1    656.8
  compute.light d1, us           373.7     320.5    319.3
  render.light d2, us             87.2      65.6     66.6
The GPU floor used the top entry of each rail's CPU table, so raising
the fabric for GPU work also raised it for a CPU cluster in its top
frequency quartile. On J700 that costs 0.21 W during a single-thread
load (6.738 -> 6.948 W), and no CPU benefit has been measured.

Give each SoC a GPU target separate from its CPU table. T8140 keeps DCS 4
and fabric 3 for streaming GPU work, and its CPU table no longer touches
the fabric, as before the GPU floor. T8132's GPU target is its CPU
table's top entry, so it is unchanged.
The 64 ms streaming gate and the PMP back-off were measured on T8140 but
sat on the shared graphics path, so they also changed T8132. That SoC
had no DT consumer yet, but it would have lost its floor for bursts under
64 ms, against what was qualified there.

Make the time graphics must stay powered a per-SoC parameter: 64 ms on
T8140, and 0 on T8132, which keeps that SoC's floor whenever graphics is
powered, as before. The PMP back-off was already T8140 only.

The debugfs status now says "never" when the PMP has not limited the DCS,
rather than 0 ms ago. The comment no longer says a failed read cannot
starve a graphics consumer: a failed read counts as powered, so read
errors alone do not end a stream that is already under way.
@DjDeveloperr

Copy link
Copy Markdown
Contributor Author

Updated with the two follow-ups from the omacom review (omacom/linux-aurora#226):

  • fca758c, "give the GPU floor its own states". The GPU target is separate from the CPU table. T8140's CPU table no longer raises the fabric: it's back to {0, 0, 0, 0}, as before soc: apple: clpc: raise the T8140 fabric floor for GPU work #440. The top-quartile CPU fabric raise cost 0.21 W on a single-thread load with no measured CPU benefit. T8140's GPU target stays DCS 4 / fabric 3, and T8132's GPU target is its table's top entry, so it is unchanged.
  • 6f9ffbe, "limit the GPU streaming gate to T8140". The 64 ms gate is a per-SoC gpu_sustain_ms, and T8132 keeps its floor whenever graphics is powered. The debugfs status prints "never" when the PMP hasn't limited the DCS.

Both build with W=1 against the 7.3 tree, and checkpatch is clean.

@DjDeveloperr

Copy link
Copy Markdown
Contributor Author

Qualification on Neo (J700) of integration/aurora 501e607 with this PR merged, as a full kernel (7.3.0-ARCH-neo464, build b60d9067, own modules). The A/B baseline was the current perf core with the same asahi source. Both arms ran in one session; the machine was charging throughout, so absolute watts are higher than on battery.

leg floor as merged (#440) this PR
idle 5.129 W 5.120 W
1080p video 7.046 W 6.694 W (−5.0%)
light browsing 5.899 W 5.711 W (−3.2%)
CPU top quartile 8.672 W 8.553 W
render.heavy d4 after 90 s 679.9 µs 666.5 µs
  • It boots, and the regression gate passes (PERFORMANCE-REGRESSION-PASS).
  • Targeted CTS: 4687 pass, 681 not supported, 0 fail. No CLPC or asahi kernel errors.
  • render.light d2/d32 and compute.light d2 are unchanged.

One regression blocks the merge: light work right after the PMP limited the DCS. compute.light d1, per 250 ms run:

  • floor as merged: 102.2–103.1 µs in every run;
  • this PR: 150.7, 151.1, 117.2, 102.7, 102.8 (round 1) and 148.5, 150.8, 135.6, 102.7, 102.8 (round 3).

The first 0.5–0.75 s of light submissions run about 47% slower before the floor comes back. That's the 1 s hold after the PMP last limited the DCS (rule 2). The rows that came after heavier work start with the floor released.

Proposed fix: end the PMP backoff when the PMP stops limiting, after a short hysteresis of tens of ms rather than a fixed 1 s. Heavy sustained work keeps the benefit, and light work that follows gets the floor back within the streaming gate. I'll requalify it the same way before merging, and the same change goes to omacom/linux-aurora#226.

…stops limiting

The GPU floor stayed released for a second after the PMP last limited the
DCS. Light GPU work that followed heavier work therefore ran without the
floor for up to that second: on Neo, compute.light d1 took 148-151 us
instead of 102.7 us for the first 0.5-0.75 s of a run, before the floor
came back.

Under sustained heavy rendering the PMP limits the DCS again within much
less than that, so a 100 ms hold keeps the floor released there while the
light work that follows gets it back almost at once.
@DjDeveloperr

Copy link
Copy Markdown
Contributor Author

Requalified with 51350a0 (hand the T8140 GPU floor back 100 ms after the PMP stops limiting, instead of 1 s). Full kernel: integration/aurora 501e607 + this PR, kernel build 976a26ad, on Neo (J700), same session as the baseline (default core + the same asahi source).

The blocker from the first run is gone: compute.light d1 is 102.75 µs [102.59–103.05] across rounds vs 102.80 [102.71–102.84] baseline. Run 1 with the 1 s hold showed 117.2 [103.0–135.6], the first 0.5–0.75 s after heavier rows.

leg baseline W this PR W
idle 5.281 5.264
video 1080p 7.310 7.026 (−3.9%)
browse 6.093 5.952 (−2.3%)
cpu_top 9.088 8.990 (−1.1%)
gpu_thermal (render.heavy d4, last run) 689.2 µs 672.4 µs

Light rows are unchanged (µs/submit): compute.light d2 34.04 → 33.75, render.light d2 46.04 → 46.09, render.light d32 27.12 → 27.13.

Gate: PERFORMANCE-REGRESSION-PASS. Targeted CTS: 4687 pass / 681 not supported / 0 fail. No CLPC/asahi kernel errors. Merging.

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