Skip to content

GPU clock: trace which NVPCF branch the 620 nvidia-powerd takes at start #3

Closed

Description

@seankd3

Task

Find out which NVPCF branch the unmodified 620 nvidia-powerd (sha256 eb9e0371…) takes when it starts on this laptop. Use one bounded bpftrace start on the 620 boot. The probes only read registers and memory. The traced powerd is not read-only: it makes its normal vendor calls, and these can include requests that set platform values. The trace forces and overrides nothing.

Context

  • The GPU clock cap is not fixed. Under sustained FP32 and BF16 GEMM, the reported SM clock settles at about 721-754 MHz with reason 0x400 (GPU diagnostic: run the exact GEMM check with BF16 inputs as well as FP32 #1). This issue does not unlock the GPU. It is one diagnostic step.
  • Observed on the 620 driver: nvidia-powerd exits 0 after about 0.25 s and logs its quit message. Which route it takes is not measured.
  • In the static code, two conditional routes end in a quit with no worker:
    • (a) the NVPCF 3x init returns true, and that factory branch creates no worker;
    • (b) 3x and 2x/1x both report not supported.
      Which route runs on this laptop is unmeasured. The platform's 3x GET_SUPPORTED query returns 0x3f. That closes only the ACPI capability binding. It does not rank (a) over (b).
  • A trace design exists, with 11 read-only probes under bpftrace -c. The review found three corrections before any run:
    1. The "primitive load" result carries no information. The wrapper at 0x1521b0 returns 0 whether it calls 0x14dc70 or skips it because the pointer at +0x17f0 is NULL. The binary logs "ObjPrimitiveLinux loaded successfully" on both paths. The probe must read the pointer at 0x1521bc instead.
    2. Exit reporting. bpftrace's exit code is not powerd's. The child's exit code or signal is logged only with -v. A timeout or -k kill has its own meaning.
    3. Binary pin. The start step must check the binary hash, so the 71 build can never start under the trace.

Status

The design is under review. There is no source and no run yet. The trace needs the 620 boot, which does not happen while other work on the laptop needs the current boot.

Activity

  1. seankd3 commented on Nov 6, 2026

    @seankd3
    OwnerAuthor

    Shape card, revision 2: one balanced → performance request on the matched @nv620 stack (separate experiment; not run)

    Revision 2 applies the elegance and bugs findings in one batch. The two proposed helpers (job.sh 95338b80…, write.sh 1739d34d…) never ran. They were hash-checked and deleted, together with their /tmp rendezvous design. No source exists for this experiment. The existing tools are unchanged.

    Problem. No retained run measures a balanced → performance transition on a matched driver stack with the key. The only sweep (FINDINGS-ec-thermal-policy.md §4) ran on @ with 615.78.08, no key and 2 s windows. nv71-reassert-1105-162425 was a same-value write.

    The thing. A documented operator procedure around the existing tools: scripts/gemm.py (d0701272…, one 240 s FP32 process) and the existing sampler (625bd379…, with coord.py 30738d21…, which stops on its A-gemm.done sentinel). There are two immediate manual powerprofilesctl requests, and each records rc, CLOCK_MONOTONIC stamps and a getter readback. Cleanup is limited to this run. Standard profile commands do route vendor writes: ppd → platform_profile → asus-wmi → WMI → EC (FINDINGS §4). ppd also switches its CPU driver profile.

    Procedure (the root runs it in one interactive shell; each step is a literal block):

    # 0. Facts only (read-only). Stop here if any pin or hash differs; nothing has been written yet.
    G=/home/sean/.floor-poller/gpu-clock; H=$G/opus-linux/reassert; COORD=$G/opus-linux/dc-key-exp/coord.py
    W=/home/sean/.t3/worktrees/asus-proart-p16-rtx-spark-linux/t3-n1x-bf16-gemm; LIB=/home/sean/.local/lib/voxtype/cuda-13/cuda
    R=$(mktemp -d "$G/runs/nv620-transition-XXXXXX"); cd "$R"; umask 077; echo "R=$R"
    mono() { python3 -I -c 'import time; print(f"{time.monotonic():.3f}")'; }
    snap() { echo "$1 mono=$(mono) profile=$(cat /sys/firmware/acpi/platform_profile) ppd=$(powerprofilesctl get)"; }
    { cat /proc/sys/kernel/random/boot_id; findmnt -no FSROOT /; grep -o ' 620\.70\.06 ' /proc/driver/nvidia/version
      grep -o 'RMSkipACPIBattCap=1' /proc/driver/nvidia/params; cat /sys/class/power_supply/AC0/online; pgrep -xc nvidia-powerd; snap pre; } > A-pre.txt
    ps -eo pid,comm,%cpu --sort=-%cpu | head -20 > A-procs.txt          # metadata only, not a gate
    sha256sum $W/scripts/gemm.py $H/sampler.py $COORD > A-hashes.txt; cat A-pre.txt A-hashes.txt
    # Expect: 416d3e1b-…, /@nv620, 620.70.06, the key, AC 1, powerd 0, profile and ppd performance; d0701272…, 625bd379…, 30738d21….
    
    # 1. Request balanced. Go on only if rc=0 and the after line reads balanced; otherwise go to step 4.
    { snap before-balanced; powerprofilesctl set balanced; echo "rc=$?"; snap after-balanced; } > A-req-balanced.txt 2>&1; cat A-req-balanced.txt
    
    # 2. Start the existing tools (unchanged commands).
    timeout 360 python3 -I $H/sampler.py $COORD "$R" > A-sampler.out 2>&1 & SP=$!
    timeout 300 env LD_LIBRARY_PATH=$LIB python3 -I $W/scripts/gemm.py 240 fp32 > A-gemm.txt 2>&1 & GP=$!
    echo "$GP $SP" > A-pids.txt
    
    # 3. About 100 s after the first window line in A-gemm.txt, request performance once. Then wait for both tools.
    { snap before-performance; powerprofilesctl set performance; echo "rc=$?"; snap after-performance; } > A-req-performance.txt 2>&1; cat A-req-performance.txt
    wait $GP; echo "gemm rc=$?" >> A-rc.txt; touch A-gemm.done; wait $SP; echo "sampler rc=$?" >> A-rc.txt
    
    # 4. Cleanup, always (also after an abort or a lost shell: set R, cd "$R", then run this block).
    if [ ! -e A-gemm.done ]; then kill $(cat A-pids.txt 2>/dev/null) 2>/dev/null; echo "aborted $(date -u +%FT%T.%NZ)" >> A-rc.txt; touch A-gemm.done; fi
    if [ "$(cat /sys/firmware/acpi/platform_profile)" != performance ]; then powerprofilesctl set performance; echo "cleanup-restore rc=$?" >> A-rc.txt; fi
    { echo "post profile=$(cat /sys/firmware/acpi/platform_profile) ppd=$(powerprofilesctl get)"; cat /proc/sys/kernel/random/boot_id
      pgrep -fc '[g]emm\.py|[s]ampler\.py'; } > A-post.txt; cat A-rc.txt A-post.txt

    Facts stored.

    • the GEMM window lines and sampler rows, on one CLOCK_MONOTONIC timeline;
    • each request's rc, its before/after stamps and its readback;
    • the tool rc values, any aborted or cleanup-restore line;
    • the verified post state.

    Computed (rules fixed before the run):

    • Run valid only if all of these hold:
      • GEMM rc 0, both checks report 0 mismatches, sampler rc 0;
      • both requests have rc=0 and their after readback is the requested profile;
      • the boot is unchanged and there is no aborted line;
      • sampler coverage holds in both spans, [t0, before-performance] and [after-performance, t_end]: at least 0.8 × span / 0.5 s rows, no gap over 1.5 s between rows, and every row with AC0 1, source 0 and SM rc 0. Sampler rc 0 alone does not count as coverage.
      • at least 60 s of windows in [t0+30, before-performance] and at least 90 s in [after-performance, t_end].
    • Cleanup is reported in every verdict: the cleanup-restore rc if it ran, and the verified post profile and ppd. A post state other than performance, or a failed restore, is reported as a failure in the verdict.
    • Report for B = [t0+30, before-performance], P15 = [after-performance, +15 s] and P = [after-performance+30, t_end]:
      • SM clock median, min and max;
      • the set of reasons;
      • raw power, utilization and temperature;
      • window rates;
      • the getter snapshots.
    • Wording:
      • If P median >= 1800 MHz while B <= 760: "rise observed after the request".
      • If B and P are both <= 760 with reason 0x400: "no rise observed after the request".
      • Anything else: raw values only.

    Not proved. This run does not show:

    • the profile between the snapshots;
    • any policy the EC applied but did not report;
    • the cause of a change;
    • that a change lasts.

    A rise is a reported observation only. It would need a separate confirmation before anyone calls it a fix. A flat result means only that no rise was observed after this request.

    Stopped for a fresh review. Nothing was written or run. The GPU clock goal is not fixed.

  2. seankd3 commented on Nov 6, 2026

    @seankd3
    OwnerAuthor

    Shape card, revision 3: one balanced → performance request on the matched @nv620 stack (separate experiment; not run)

    Revision 3 applies the five findings from the review of revision 2 in one batch, and one more found while applying them.

    Problem. No retained run measures a balanced → performance transition on a matched driver stack with the key. The only sweep (FINDINGS-ec-thermal-policy.md §4) ran on @, with 615.78.08, without the key, and with 2 s windows. nv71-reassert-1105-162425 was a same-value write.

    The thing. One script, opus-linux/profile-transition/transition.sh (e7355ad3…), runs as the main process of a transient systemd unit. It drives the existing tools unchanged: scripts/gemm.py (d0701272…, one 280 s FP32 process) and the sampler (625bd379…, with coord.py 30738d21…).

    G=/home/sean/.floor-poller/gpu-clock; T=$G/opus-linux/profile-transition/transition.sh
    R=$(mktemp -d "$G/runs/nv620-transition-XXXXXX")
    sudo systemd-run --unit=nv620-transition --collect --wait -p RuntimeMaxSec=420 \
      -p ExecStopPost="$T restore $R" "$T" run "$R"

    The door. systemd owns the run's lifetime. The script does not.

    • RuntimeMaxSec is the only time bound, so the old per-tool timeouts are deleted.
    • KillMode=control-group (the default) stops and reaps every child on every exit. There is no PID file and no kill of saved PIDs.
    • ExecStopPost runs restore after all unit processes are gone, on every exit: normal exit, error, signal, SIGKILL of the main process, or a lost terminal. It also runs after a refused pin.
    • restore reads both getters, platform_profile and powerprofilesctl get. If either one is not performance or cannot be read, it requests performance through ppd. Then it verifies both and writes A-post.txt with systemd's SERVICE_RESULT, EXIT_CODE and EXIT_STATUS.

    Sequence.

    1. Check the pins and hashes before any write (boot 416d3e1b…, /@nv620, 620.70.06, key, AC0 1, no powerd, both getters performance). Any failure exits 10. Nothing has been written at that point.
    2. Request balanced. A failure exits 11. A request passes only if it has rc=0 and both getters read the requested profile afterward.
    3. Start the sampler and the GEMM.
    4. t0 is the first GEMM window's mono_start. At t0 + 150 s the script requests performance once, while GEMM runs. If GEMM dies first, the script exits 12 or 13.
    5. Wait for GEMM, write A-gemm.done, then wait for the sampler. The sampler stops on that file.

    Change found while applying the review. The retained 620 run (nv620-stab-1105-140857/S-summary.txt) held its fast window for 81 s. With revision 2's B = [t0+30, …], the fast window falls inside B. B now starts at t0+90, so the write moves to t0+150.

    Facts stored.

    • GEMM window lines and sampler rows (fields sm, rs, pw, tmp, ut, ac0, src with each _rc), on one CLOCK_MONOTONIC timeline.
    • Each request's before/after snapshots and rc.
    • A-rc.txt, A-post.txt, A-pre.txt and A-hashes.txt.

    Computed (rules fixed before the run):

    • Times.
      • t0 is the first window's mono_start, and t_end is the last window's mono_end.
      • w1 and w2 are the mono values in before-performance and after-performance.
    • Intervals.
      • B = [t0+90, w1]
      • P15 = [w2, w2+15]
      • P = [w2+30, t_end]
    • Run valid only if all of these hold:
      • GEMM rc 0, both checks report 0 mismatches, and sampler rc 0;
      • both requests pass;
      • performance-request ok=0;
      • systemd SERVICE_RESULT=success;
      • A-post.txt reads performance for both getters, and the boot is unchanged;
      • in each of B, P15 and P, every row has ac0 1, src 0, and sm_rc, rs_rc and src_rc all 0;
      • in each interval, the first row is at most 1.5 s after the start, the last row at most 1.5 s before the end, and no gap between rows is over 1.5 s;
      • the GEMM windows fully inside B sum to at least 55 s, and inside P to at least 90 s.
    • Clock statistic. The median, min and max of the sampler sm (NVML SM clock, MHz) over the rows in each interval.
    • Reason predicate. "0x400 only" means every row in the interval has rs == 1024. A failed read already makes the run invalid.
    • Wording.
      • B median <= 760 and P median >= 1800: "the late-window median clock rose after the request".
      • B and P medians both <= 760, with 0x400 only in B and P: "the late-window median clock stayed at or below 760 MHz after the request".
      • Anything else: raw values only.
    • Transients, reported in every verdict and independent of the wording: max sm and the number of rows above 760 in P15 and in P. A transient rise never changes the wording rule.
    • Also reported: raw power, temperature and utilization per interval, window rates (compared only inside this run), and the request snapshots.

    Impossible states.

    • The unit cannot end without restore running.
    • restore cannot skip the ppd request while either getter reads something other than performance.
    • A child cannot outlive the unit.
    • A run with an unobserved edge of any interval cannot be valid.

    Limits.

    • Power loss or a hard reset skips restore. ppd stores its profile in /var/lib/power-profiles-daemon/state.ini, so the next boot would come up balanced. Check powerprofilesctl get after any such event.
    • ppd also switches its CPU driver profile (nvidia_rtx_spark), so a change can come from the CPU side. Thermal history is another possible cause.
    • A rise is an observation, not a fix.
    • No EC, MMIO, RM, voltage or protection write, and no powerd start.

    Deletes. The manual operator blocks of revision 2, the PID file, the saved-PID kill, the per-tool timeouts and the row-count coverage rule (the edge-and-gap rule covers it).

    Only bash -n was run on the script. Nothing was written or run. Stopped for a fresh review. The GPU clock goal is not fixed.

  3. seankd3 commented on Nov 6, 2026

    @seankd3
    OwnerAuthor

    Shape card, revision 3a: amendment to revision 3 (not run)

    The review of the #7 boost card found a defect that revision 3 shares: ExecStopPost also runs after a refused start. Revision 3's restore then requests performance even when the run never owned the state. An example is a refused pin because someone else set balanced.

    Change. After every pin and hash passes, and before the first write, the script writes A-owned and syncs it. restore writes nothing unless A-owned exists. A refused run (exit 10) changes no state. restore still records A-post.txt in every case.

    Impossible state added. A run that did not pass its pins cannot change the profile.

    The script is now transition.sh b69c68c6…. Revision 3's hash e7355ad3… is superseded. All other text of revision 3 stands. Only bash -n was run. Nothing was written or run.

  4. seankd3 commented on Nov 6, 2026

    @seankd3
    OwnerAuthor

    Shape card, revision 4: delta to revisions 3 and 3a (not run)

    Revision 4 applies the five findings from the review of revision 3 in one batch. Text not changed here stands as written in revision 3. The script is now transition.sh 5a09880f…, which supersedes e7355ad3… and b69c68c6….

    Launch (replaces revision 3's):

    G=/home/sean/.floor-poller/gpu-clock; T=$G/opus-linux/profile-transition/transition.sh
    R=$(mktemp -d "$G/runs/nv620-transition-XXXXXX")
    sudo systemd-run --unit=nv620-transition --collect --wait -p RuntimeMaxSec=420 -p TimeoutStopSec=60 \
      -p ExecStopPost="$T restore $R" "$T" run "$R"; echo "unit rc=$?" | sudo tee "$R/A-unit-rc.txt"

    Changes.

    1. A refused run changes nothing (from 3a). After every pin and hash passes, and before the first profile request, the script writes and syncs A-owned. restore writes only if A-owned exists. Interruption during the balanced request is still covered.
    2. Sampler start. The sampler now starts after the first GEMM window. Its own 340 s cap starts at t0, so it ends after GEMM's 280 s. B starts at t0+90, so the sampler has time to cover B's leading edge. Revision 3 said "RuntimeMaxSec is the only time bound". That is withdrawn. The bounds are:
      • RuntimeMaxSec=420 for the run;
      • TimeoutStopSec=60 for the stop and restore (this machine's default is 5 s, too short for a ppd call);
      • the sampler's 340 s cap from t0.
    3. Cleanup proof.
      • restore checks both getters once more after any request. It writes verified=0|1 into A-post.txt and exits with that status.
      • The script also exits nonzero when the performance request, GEMM or the sampler fails.
      • The launching shell stores the systemd-run --wait status in A-unit-rc.txt.
      • SERVICE_RESULT describes the service before ExecStopPost, so it is metadata only.
    4. Write scope, corrected. "No EC, MMIO, RM, voltage or protection write" was wrong. The two powerprofilesctl requests are standard vendor profile requests. They route ppd → platform_profile → asus-wmi → WMI → the EC mailbox (FINDINGS-ec-thermal-policy.md, lines 26 and 59). The run makes no direct EC, MMIO, RM, voltage or protection write, and starts no powerd.
    5. Kill guarantee, narrowed. systemd stops and reaps every child on ordinary stops. A process stuck in an unkillable kernel wait can survive SIGKILL, and ExecStopPost then runs with that process still present. A-post.txt records the state when the check runs.

    Run valid (additions to revision 3's list):

    • A-unit-rc.txt exists and reads unit rc=0;
    • A-post.txt reads verified=0;
    • A-owned exists.
      A missing file makes the run invalid.

    Impossible states. A run that did not pass its pins cannot change the profile. A cleanup failure cannot read as a valid run.

    Deletes. The claim that one time bound exists, and the use of SERVICE_RESULT as cleanup evidence.

    Only bash -n was run. Nothing was written or run. Stopped for a fresh review. The GPU clock goal is not fixed.

  5. seankd3 commented on Nov 6, 2026

    @seankd3
    OwnerAuthor

    Shape card, revision 4a: amendment to revision 4 (not run)

    This applies the one finding from the review of revision 4 (elegance PASS, bugs FAIL), plus an idle precondition found from live state. The script is now transition.sh eda420a3…, which supersedes 5a09880f….

    1. The ownership record must succeed. { echo … > A-owned && sync A-owned; } || exit 14. A failure stops the run before the first profile request. The run can no longer request balanced without the record that lets restore undo it.
    2. Idle precondition. The owner's video renders (ffmpeg-gpu, up to about 1275% CPU) run on this machine. The pins now also require that less than 10% of all CPU time is busy over 10 s, from two /proc/stat reads. The result is written to A-idle.txt, and a busy machine exits 10 before any write. A read-only dry run of this check just now gave busy=0.9420 and rc 1, so it refuses as intended. The run can then neither measure the owner's load nor slow it.

    Run valid (addition): A-idle.txt exists and reads busy < 0.10.

    All other text of revisions 3, 3a and 4 stands. Only bash -n and the read-only idle check were run. Nothing was written. Stopped for a fresh review.

  6. seankd3 commented on Nov 6, 2026

    @seankd3
    OwnerAuthor

    Shape card, revision 4b: amendment to revision 4a (not run)

    This applies the two findings from the review of 4a (elegance PASS, bugs FAIL), plus one found from the #7 review that applies to this run as well. The script is now transition.sh 3ca43d9e…, which supersedes eda420a3….

    1. Idle sum. The sum now covers only the first eight /proc/stat fields: user, nice, system, idle, iowait, irq, softirq and steal. guest and guest_nice are already counted inside user and nice, so the old sum double-counted them.
    2. Idle record. A-idle.txt writes busy= with Python's round-trip repr, which is the exact value the admission test used. The record can no longer contradict the decision.
    3. Outside work over the whole run (new). The start check alone cannot see a render that starts during the run. The script now writes two lines to A-cpu.txt, start (after the idle check) and end (after the sampler exits). Each line holds mono, the system busy ticks (user + nice + system + irq + softirq + steal, at USER_HZ 100) and the unit cgroup's usage_usec. Outside CPU time is busy ticks / 100 minus usage_usec / 1e6. This also counts processes that start and exit inside the run. A read-only dry run of both readers worked: busy_ticks=12146841 cg_usec=1243745561.

    Run valid (additions):

    • A-idle.txt busy < 0.10, evaluated on the recorded value;
    • A-cpu.txt has both lines;
    • outside CPU time divided by (end.mono − start.mono) is less than 2.0 CPUs, which is 10% of the 20 CPUs and the same bound as the start check.

    Limit. GPU work from another process is caught only through its CPU time. A GPU job with almost no CPU time would pass. The sampler's ut (GPU utilization) during the measured intervals is reported, and it is the cross-check.

    All other text of revisions 3, 3a, 4 and 4a stands. Only bash -n and the read-only readers were run. Nothing was written. Stopped for a fresh review.

  7. seankd3 commented on Nov 6, 2026

    @seankd3
    OwnerAuthor

    Shape card, revision 4c: amendment to revision 4b (text only; not run)

    This applies the two findings from the review of 4b (elegance PASS, bugs FAIL). The script is unchanged: transition.sh 3ca43d9e….

    1. Name and meaning of the whole-run quantity. "Outside CPU time" is renamed CPU time not charged to the unit. It includes kernel work that the test itself causes:

      • NVIDIA worker and IRQ threads run in the root cgroup (0::/), for example PIDs 260, 266 and 274;
      • this kernel has CONFIG_IRQ_TIME_ACCOUNTING off, so interrupt time is not reliably split.

      The 2.0-CPU bound stays as it is. A breach makes the run inconclusive. It is not evidence that another workload ran. Nothing is subtracted for IRQ or driver time, because that time can also serve other jobs.

    2. GPU exclusivity is unverified. 4b's claim that ut is a cross-check for a competing GPU job is withdrawn. NVML utilization measures device activity, not who owns it, so another job can overlap GEMM while ut reads 100%. ut stays as activity metadata only. A GPU job from another process with almost no CPU time cannot be detected by this run. This is stated under Limits.

    Ruling order (addition): "inconclusive: CPU time not charged to the unit ≥ 2.0 CPUs" comes after every validity failure and before the wording rules.

    All other text of revisions 3, 3a, 4, 4a and 4b stands. Nothing was written or run.

  8. seankd3 commented on Nov 6, 2026

    @seankd3
    OwnerAuthor

    Prior art

    Search date: 2026-11-05. No working fix for the AC cap exists in any public source. One other N1X machine on Linux is not capped, and that is the best lead. Tags: [S] means a linked source says it. [I] means it is my inference.

    Who else has the cap

    Machine OS, driver What happens Source
    MSI Prestige N16 Flip AI+ Omarchy, version not given About 780 to 790 MHz on AC under load. About 1800 MHz on battery. Windows holds 2.0 to 2.2 GHz. With "NVIDIA Platform Controllers and Framework" turned off in Windows, the GPU drops to 786 MHz, and turning it on again restores full clocks at once. Temperatures were 40 to 47 C. [S] MindStudio, 2026-11-01, benchmark post, 2026-11-01
    Microsoft Surface Laptop Ultra Omarchy, kernel 7.3.0, open 615.78.08 Not capped. About 2400 MHz and 50 W under sustained load. NPCF also answers only revision 0x300, and Dynamic Boost reads "Not Supported". The problem there is the opposite: Linux applies no platform power limit, so the owner caps the SoC from user space. The post does not say AC or battery. [S] #1521 comment, 2026-11-04, surface-ultra-misc thermal/
    NVIDIA's own N1X Linux image Ubuntu 24.04, kernel 7.0.0-1015-nvidia, driver 615.69 QA logs show about 15 tok/s decode for Qwen3.6-35B-A3B-NVFP4 in vLLM. [S] That rate looks low for a model with 3B active parameters and fits a capped GPU, but the issues give no clock data. [I, weak] NemoClaw#10955, 2026-09-03, NemoClaw#11805, 2026-09-15
    ASUS ProArt P14, Dell XPS 16 Omarchy No clock reports found. Omarchy's N1X notes and patches have no GPU clock or NVPCF item. [S] omarchy-iso N1X.md, omarchy-pkgs pkgbuilds
    Surface, Omarchy discussion Omarchy No GPU clock complaint in the hardware thread. [S] omarchy discussion 15885, omarchy-pkgs#1275, linux-surface#2307

    A Hacker News thread repeats the MSI numbers. One Surface owner there says the Surface also throttles on Windows. Nobody there gives a fix, a driver version or an NVIDIA statement. [S] HN 50362905, 2026-11-04

    The GB10 "latch" is a different signature

    • DGX Spark and ASUS GX10 units get stuck at 513 to 950 MHz. In every report, no clock event reason is active. [S]
    • A cold power drain fixes most of them: unplug the brick for 30 s to 10 min, or hold the power button. One 721 MHz unit did not recover. [S]
    • One stuck GB10 read SPBM pl1 20 W and syspl1 30 W. [S] The P16 reads PL1 80 W and SysPL1 131 W. [lab]
    • Sources: 721 MHz, 2026-07-08, 513 MHz, 2026-02-22, 650 MHz, 2026-05-16, 5 min drain, 2026-07-09, latch doc, 2026-09-10.
    • The P16 shows reason 0x400 and the cap also on the MSI. So the GB10 latch is probably not this bug. [I]

    Fixes and workarounds

    • WSL on Windows keeps full clocks on AC and on battery on the MSI. [S, MindStudio] It is a workaround, not a Linux fix.
    • Battery lifts the cap on the MSI. [S] It does not lift the cap on the P16. [lab, #1521]
    • MindStudio tried to extract the key from the Windows driver. It failed and caused one kernel panic. [S]
    • Nothing else is published.

    Driver, kernel and BIOS versions

    • Driver. 620.70.06 is the newest driver for both aarch64 and x86_64 on download.nvidia.com, and the newest open-gpu-kernel-modules tag, 2026-11-05. [S] Its changelog has no entry for N1X, power, NVPCF or powerd. [S, local NVIDIA_Changelog] Its aarch64 README lists 2E03, and it says Dynamic Boost "is not available on this architecture". [S] The 615.71.10 tag of 2026-10-21 changes 5 files, none of them power code. [S]
    • NVIDIA response. #1521 has no NVIDIA reply, label or assignee. For the Surface carveout bug #1458, NVIDIA filed an internal bug within one day. [S]
    • Kernel. NVIDIA's NV-Kernels N1X branches (24.04 7.0-next, 26.04, bos-7.0-next) have no GPU power, NVPCF or SPBM commit. [S] Their power_wrap patch requests AC_BESTPERF at boot, and the lab already matched that register. [S, lab notes]
    • FF-A fix. A new FF-A fix landed in the 26.04 branch on 2026-11-03: hash collisions between notification IDs dropped EC events (92fe016c80, LP#2171426). [S] This boot logs no "Failed to register callback". [S, journalctl] So no colliding ID was registered, and this bug does not apply to the P16. [I]
    • No N1X power work found on Launchpad, in Ubuntu packaging or on Phoronix. [S, searches]
    • BIOS. No public ASUS BIOS after H7607BA.300. The ASUS support API returns FAIL for this model. [S]

    Top lead

    Find out why the Surface is not capped. [I]

    • The Surface has the same chip and the same 0x300-only NPCF. Its Linux driver family is the same, and it runs at 2400 MHz.
    • So the cap is not a direct result of N1X plus Linux plus missing NVPCF 3. Some OEM firmware input decides it, and the MSI shows that the power source can change it.
    • The question to its owner, on #1521: was the 2400 MHz run on AC? Also attach nvidia-smi -q -d PERFORMANCE,POWER,CLOCK under load, and the acpidump. Then we diff the Surface NPCF, EC and power-source methods against the P16 tables, read-only.
    • Cost: one public comment, which needs Sean's yes, and some wait. The table diff is lab-side and takes about 1 hour.

    Second lead, cheaper but less likely:

    • Do an ASUS EC reset: AC out, hold power 40 s, then cold boot. Then repeat the battery capture once.
    • The MSI lifts its cap on battery and the P16 does not. A latched P16 EC state could explain that difference.
    • The lab notes show no drain or EC reset so far.
    • Cost: about 10 minutes of Sean at the machine, and no software change.

    The GPU clock goal is not fixed.

  9. seankd3 commented on Nov 6, 2026

    @seankd3
    OwnerAuthor

    Question: May we post one comment on NVIDIA/open-gpu-kernel-modules#1521 that asks the Surface Laptop Ultra owner, who already commented there, for three things: AC state under load, nvidia-smi -q, and an acpidump?

    Context: prior art (comment 6677010091) found that the Surface Laptop Ultra is not capped. It has the same N1X, the same NPCF that answers only revision 0x300, and the 615 driver, and it runs at about 2400 MHz. So an OEM firmware input sets the cap. A read-only table diff against the P16 tables is the cheapest test of that. linux-surface/acpidumps has no Ultra dump, and the public Surface gist has no tables.

    Recommendation: yes. The comment is public, under the owner's name, and it notifies that user.

  10. added
    needs-seanWaits on a decision from Sean
    and removed
    needs-seanWaits on a decision from Sean
    on Nov 6, 2026
  11. seankd3 commented on Nov 6, 2026

    @seankd3
    OwnerAuthor

    Answered yes. The request to the Surface owner is posted on NVIDIA/open-gpu-kernel-modules#1521 (comment 6677315996).

    Also found: the main checkout's local main holds two README commits that were never pushed. They are d5f12b8 ("power table is encrypted and nvidia-powerd cannot read it") and 7a23158. Both make GPU-clock claims older than the 2026-11-05 verified notes. Review them against the CURRENT VERIFIED notes before anyone pushes them. Do not push them as they are.

  12. seankd3 commented on Nov 6, 2026

    @seankd3
    OwnerAuthor

    Surface vs P16 table diff

    Read-only diff, 2026-11-06. Nothing changed on the P16. Tags: [M] I read it in the tables or source files named. [S] a linked source or another comment says it. [I] my inference.

    Result. The Surface has no _PSR object at all. So its NVIDIA driver never sends a power-source event to GSP. The P16 has one, so its driver sends AC at every load. That is the top lead. The NPCF buffers differ too, but no Linux code reads them on either machine.

    Inputs

    • Surface: gist 930a5624…, the seven raw .dat tables from its tarball, run through iasl -d 20260408 in a fresh directory under /tmp. Paths below are those .dsl files.
    • P16: ~/.floor-poller/gpu-clock/acpi/*.dsl (firmware H7607BA.300). The loaded SSDT2 override changes only the 6 FF-A methods. It keeps AC0._PSR [M, acpi/power-method-changes.json].
    • Driver source: /usr/src/nvidia-620.70.06. The 615.78.08 tag was checked on GitHub for the two points that matter.

    Differences found

    # Object P16 Surface Who reads it on Linux
    1 Power source _PSR \_SB.AC0._PSR, SSDT2.dsl:5584-5591 [M] None. Zero _PSR, no ACPI0003, no PNP0C0A battery in all 7 tables [M] nvidia.ko, at init and on each AC notify [S, below]
    2 NPCF subfunction 3 (READ_PLATFORM_WRITE_DYNAMIC_PRIMITIVES_DATA) 6 zero bytes, DSDT.dsl:15924-15930 [M] DDPD, 41 bytes, ssdt3.dsl:3868-3878, 3944-3946 [M] Nobody (see below)
    3 NPCF subfunction 1 (INIT_DATA) 0x80 bytes, high entropy, DSDT.dsl:15894-15915 [M] 0x130 bytes, high entropy, ssdt3.dsl:3892-3935 [M] Nobody
    4 PEP table DPMT (_DSM 64b66b51, func 1) 0x4D entries, no GPU entry, DSDT.dsl:7167 [M] 0x4E entries, adds \_SB.PCIF.RP0.IGPU "igpu" {1, 3, 0}, control type 0, dsdt.dsl:7103, 8143 [M] power_wrap parses it. Control type 0 is CTRL_NONE (power_wrap/inc/power_wrap.h:28), so no SCMI call is made for the GPU [S]
    5 GPU NBCI _DSM SSDT4.dsl:2044. Empty GSV1/GSDR (1 byte each), backlight table at 2207 [M] ssdt3.dsl:2229. Filled VRR keys GSV1-3, a 0xC5-byte GSDR display record, another backlight curve at 2283 [M] Display and backlight only. Same subfunctions (0, 0x10, 0x14) [M]

    Same on both [M]:

    • NPCF _HID, _STA, _DSM GUID, the revision-0x300-only check, subfunction 0 = Unicode("?") (0x3f), subfunction 2 = 6 zero bytes, subfunctions 4 and 5 = Return (Zero). The P16 does not have the HP subfunction-4 CondRefOf/Mid bug.
    • No JT, NVOP, GPS, NVHG or MXM _DSM GUID on either. So the ACPI data the RM hands to GSP at boot (kernel_gsp.c:6147-6175: DOD, JT, MUX, Optimus caps) has the same empty JT/caps on both [I].
    • The PCIF root port and IGPU blocks, _PR0/_PR3 and the PEPD _DEP match apart from row 5 [M, block diff].
    • The 10 SoC thermal zones in the DSDT have the same _CRT methods. No _PSL on either machine names the GPU. The P16 ASUS THRM zone lists CPUs only (SSDT2.dsl:5655-5721) [M].
    • The NVDA HID sets match, except camera devices NVDA28xx in the Surface's ssdt6 [M].

    The Surface has no ACPI EC, FF-A transport (ARML0002), lid or power button. Its battery and AC run through the Surface Aggregator, outside ACPI [M for the absence; I for the reason].

    Why the NPCF buffers cannot explain the Linux difference

    • 615.78.08, which the Surface ran at 2400 MHz, defines NVPCF DSM revisions 0x100 and 0x200 only (acpidsmguids.h:74-76 at tag 615.78.08). It never sends revision 0x300, so it never reads any NPCF buffer on either machine [S].
    • In 620, the CPU RM only passes 3.x calls through for a user-space client (client_resource.c:3277-3393). It reads nothing at init [S]. No 3.x client stays running on either machine [S, #1521].
    • The Surface DDPD holds two 32-bit fields, DCL1 = 11800 at 0x1D and DCL2 = 15800 at 0x25. Units are not known [M]. Only the Windows device MPIO (MSFT000B) writes them, with Notify (\_SB.NPCF, 0xC0) (ssdt3.dsl:4924-4962) [M]. They matter for Windows and for a future powerd, not for this cap [I].

    Ranked candidates

    1. The power-source event, present on the P16 and absent on the Surface. Strength: medium.
      • Code path [S]: nv_acpi_find_methods records any _PSR in the namespace (kernel-open/nvidia/nv-acpi.c:355-358). At init, if mobile config is on, RmInitAdapter evaluates it and calls RmPowerSourceChangeEvent (osinit.c:2432-2450). That sends NV2080_CTRL_CMD_PERF_SET_POWERSTATE AC or BATTERY (osapi.c:1331-1346, kern_perf_pwr.c:293). Each ACPI 0x80 notify sends it again (nv-acpi.c:128-145). Without _PSR, nv_acpi_get_powersource returns an error at line 111, and nothing is sent. The 615.78.08 tag has the same code at the same lines.
      • So the Surface GSP has never received a power-source event. The P16 GSP gets AC at every driver load [I].
      • In GSP, that event runs handler 0x1ae7908. Its AC branch sets mode 4, clears limit 0xc1 and calls 0x1666004(…,1,1) [lab static, opus-linux/FINDINGS-RMSkipACPIBattCap.md]. The RMSkipACPIBattCap test changed only the DC branch, so it did not test this [S, same file].
      • Fits the behaviour [S, #1521]. The P16 runs fast for a while after load, then drops to 754 MHz. The HP is fast for about 10 minutes after each boot or AC/battery switch, then settles. On the MSI, battery lifts the cap. On the P16, a battery event gave about 2 s at 2340 MHz. Every capped machine gets these events. The one machine that never gets one never drops [I].
      • Against: no run has removed the event entirely. NVML reports AC (src 0) on the P16, but that may be a default, not proof the event arrived [I]. The Surface also differs in EC and SoC firmware.
    2. EC or SoC firmware input, not visible in ACPI. The P16 uses an ASUS EC over FF-A. The Surface uses the Surface Aggregator. The lab's limit trace ends in a firmware-to-firmware input (opus-linux/FINDINGS.md, conclusion). A table diff cannot rank this further [I].
    3. NPCF 3.x content (rows 2 and 3). Real differences, but not read on Linux today [S above]. Low for this cap.
    4. DPMT "igpu" constraint (row 4). A Windows PEP idle constraint. No Linux consumer [S]. Low.
    5. NBCI display data (row 5). Not power related. Very low.

    Cheapest test for candidate 1

    On the P16: make the driver skip _PSR, so it never sends a power-source event. That copies the Surface case.

    • Change: one line in the open module. In nv-acpi.c:355-358, do not record psr_handle/psr_device_handle. No ACPI or firmware change. The kernel still sees AC0.
    • Run: build nvidia.ko for the candidate root, boot it once, then run the unchanged 180 s SGEMM with the smi and PMUMON capture.
    • Pass: the SM clock stays above the 923 MHz fast-window level, about 1560 GFLOP/s, for all 180 s, and PMUMON gpc moves toward activeGpc. Fail: it falls to 754 MHz as before. A fail closes candidate 1.
    • Cost: one module build and one reboot, about 30 minutes. Undo: boot the normal entry.
    • Risk: GSP gets no AC or DC events. On battery the GPU's own DC actions do not run. That is the Surface's state today, and the SoC and EC limits still apply [I]. Do not run long battery loads on this build.
    • Why not a one-object SSDT override: removing AC0._PSR also hides AC from the kernel. It also stops the \_SB.ACPF update that _PSR does (SSDT2.dsl:5589). That changes more than one input.

    Free reverse check, if the Surface owner agrees. Send one AC event with the existing NV0000 POWER_SOURCE control (bin/nv-power-source.py ac). It reaches the same PERF_SET_POWERSTATE (client_resource.c:1290-1300). Then repeat their 300 s load. If the Surface drops toward 754 MHz, candidate 1 is confirmed from the other side. A reboot clears it.

    Third data point. On #1521 the HP owner offered their tables. If the HP has a _PSR (likely), that fits candidate 1 [I].

    The GPU clock goal is not fixed.

  13. seankd3 commented on Nov 6, 2026

    @seankd3
    OwnerAuthor

    Shape card: one boot with the _PSR lookup skipped (candidate 1 of the table diff; not run)

    Problem. The table diff (comment 6683830316) ranks one difference first: the P16 has _PSR, so its driver sends GSP a power-source event at every load. The uncapped Surface has none. No run has removed that event on the P16.

    The thing. The power-source event the open module sends to GSP. Its class is "platform input the CPU-side driver hands GSP". This card removes the only automatic source of that one input and changes nothing else. Same-cause sweep: #1, #3 and the #7 boost card all chase the same cap. No other open issue in this repo depends on _PSR.

    Smallest driver change

    No existing parameter covers it. Searched in the 620.70.06 source and firmware:

    • The 53 NVreg_* module parameters of the loaded module: none touches ACPI, power source or mobile config. The four with "power" in the name are runtime D3 and S0ix settings.
    • The RM registry header (src/nvidia/interface/nvrm_registry.h): no power-source key. RMPowerFeature is a vGPU key.
    • The init path is gated by b_mobile_config_enabled (osinit.c:731), which comes from IsMobile() (subdevice_ctrl_gpu_kernel.c:396-406). No override exists.
    • The 620 GSP image (gsp_ga10x.bin) holds 442 RM* key strings. None names power source, battery or AC, except RMSkipACPIBattCap. That key gates only two actions in the BATTERY branch (FINDINGS-RMSkipACPIBattCap.md), and the boot already sets it.

    The change is one line, kernel-open/nvidia/nv-acpi.c:355:

    -    if (!acpi_get_handle(handle, "_PSR", &method_handle))
    +    if (0 && !acpi_get_handle(handle, "_PSR", &method_handle))

    A module parameter would need a new nv-reg.h entry and a check, so it is larger. With this line, psr_handle and psr_device_handle stay NULL, and the kernel still sees AC0.

    What the driver tells GSP without _PSR

    Every CPU-side route to NV2080_CTRL_CMD_PERF_SET_POWERSTATE:

    # Route With the skip
    1 Init: RmInitAdapter calls nv_acpi_get_powersource, then RmPowerSourceChangeEvent (osinit.c:2432-2450, osapi.c:1331-1346) The getter returns NV_ERR_INVALID_ARGUMENT at nv-acpi.c:111. Nothing is sent.
    2 ACPI 0x80/0x00 notify: nv_acpi_powersource_hotplug_event (nv-acpi.c:129-145) The notifier is installed only when psr_handle is set (nv-acpi.c:317-326). It is never installed.
    3 Resume from suspend (dynamic-power.c:2384-2396) Same getter. Nothing is sent. No suspend on this boot.
    4 User space: NV0000_CTRL_SYSTEM_EVENT_TYPE_POWER_SOURCE (client_resource.c:1290-1330) Still works. Only nvidia-powerd and the lab's bin/nv-power-source.py call it. Neither runs; the pins check powerd.
    5 User space: NV2080_CTRL_CMD_PERF_SET_POWERSTATE directly (kern_perf_pwr.c:350-361) Still works. No client calls it.
    6 Inside GSP: a second caller of handler 0x1ae7908 at 0x1039a9e (615.78.08 static) Not traced and not checked in the 620 image. It may apply a default at GSP init. This card cannot remove it.

    What GSP assumes by default. Unknown for GB20B. Upstream PR #1468 measured one case: on a Surface Laptop Studio (Ampere, 615.71.09) with no _PSR, PERF_GET_POWERSTATE returns BATTERY while on AC. If GB20B does the same, NVML src reads 1 on the test boot. The sampler records src in every row, so the run answers this. The P16's src 0 on normal boots may then be the event's doing, not a default.

    Scope of a ruling. The skip removes routes 1 to 3. A result speaks for "the event the CPU driver sends from _PSR". It does not separate the AC branch in GSP from route 6.

    How the module is built and installed

    Kept apart. The normal entry boots root @ with 615.78.08 from its own UKI. Nothing here touches @, its UKI or its menu lines. The existing GPU test entry, the @nv620 DKMS install (620.70.06, srcversion 5B0BEF94704B2BE1D8BB68B) and /usr/lib/modules/7.2.8-24-omarchy-p16-asus also stay unchanged. nvidia.ko loads from the UKI's initramfs (/etc/mkinitcpio.conf.d/nvidia.conf). So the test needs a new UKI, not a new root.

    Shape.

    1. DKMS builds a second version, nvidia/620.70.06nopsr, from a reflink copy of the package source with three lines changed: the skip, the version, and AUTOINSTALL removed. It runs dkms build only, never dkms install.
    2. A module root under the lab dir is a reflink copy of the kernel's module tree. Only nvidia.ko is replaced, then depmod -b. The other four modules do not contain nv-acpi.c.
    3. mkinitcpio -r <module root> -U builds the UKI with the unchanged GPU-test cmdline (out620/p16-nv620.cmdline, same @nv620 root, same RMSkipACPIBattCap=1 key).
    4. The UKI goes onto the ESP under a new name. The menu gets one new 5-line entry after /GPU test. The existing bytes stay unchanged.

    Bounds. Every heavy step runs as taskset -c 0,1 nice -n19 ionice -c3 sudo …, so make gets -j2 (the P3 rule of PLAN-nv620.md). The build refuses below 10 GB free on /home (46 GB now; / and /home share the filesystem). The build tree size is not measured here; the gate bounds it. The reflink copies take up to 1.1 GB if reflink falls back to a full copy. The UKI is about 190 MB. The ESP has 549 MB free.

    Build block (root session, on the live @nv620 boot; only bash -n was run). sha256 7fe5c416…:

    nopsr-build.sh
    ( set -euo pipefail
    K=7.2.8-24-omarchy-p16-asus; V=620.70.06nopsr; S=/usr/src/nvidia-620.70.06; N=/usr/src/nvidia-$V
    DC=/home/sean/.floor-poller/gpu-clock/opus-linux/driver-candidate; O=$DC/nopsr; M=$O/modroot
    KO=/var/lib/dkms/nvidia/$V/$K/$(uname -m)/module/nvidia.ko; MK=$M/lib/modules/$K
    RUN=/home/sean/.t3/scratch/2026-11-04-there-s-an-issue-with-3d0ef841/nopsr-run.sh
    B="taskset -c 0,1 nice -n19 ionice -c3"
    need() { [ "$1" = "$2" ] || { echo "FAIL $3: [$1] want [$2]" >&2; exit 9; }; }
    need "$(findmnt -no FSROOT /)" /@nv620 root
    need "$(uname -r)" $K kernel
    need "$(cat /sys/module/nvidia/srcversion)" 5B0BEF94704B2BE1D8BB68B "running module"
    need "$(pacman -Qkk nvidia-open-dkms 2>&1 | tail -1)" "nvidia-open-dkms: 4703 total files, 0 altered files" "package source"
    need "$(sha256sum < $S/kernel-open/nvidia/nv-acpi.c | cut -c1-64)" 6c94f07f147794d07f52717dab24f1f8e1aefefdc69e7e8f9e1f160890aab662 "nv-acpi.c"
    [ "$(df --output=avail -BG /home | tail -1 | tr -dc 0-9)" -ge 10 ] || { echo "FAIL: under 10 GB free" >&2; exit 9; }
    for p in $N $O /var/lib/dkms/nvidia/$V; do [ ! -e $p ] || { echo "FAIL: $p exists" >&2; exit 9; }; done
    need "$(sha256sum < $RUN | cut -c1-64)" b0c385fc741c44bab8e1635236b8c74cf0e46867c079df616aa76f8c200c5f5e "reviewed run script"
    mkdir -m700 $O
    install -m700 $RUN $O/nopsr-run.sh
    
    # 1. Source: a reflink copy, three lines changed.
    sudo cp -a --reflink=auto $S $N
    sudo sed -i 's/^PACKAGE_VERSION="620.70.06"$/PACKAGE_VERSION="'$V'"/; /^AUTOINSTALL="yes"$/d' $N/dkms.conf
    sudo sed -i '355s/^    if (!acpi_get_handle(handle, "_PSR", &method_handle))$/    if (0 \&\& !acpi_get_handle(handle, "_PSR", \&method_handle))/' $N/kernel-open/nvidia/nv-acpi.c
    need "$(sha256sum < $N/kernel-open/nvidia/nv-acpi.c | cut -c1-64)" 0fde0ee4450410deeacd9042d0adad27af7d38af66c472c1f3ce7088bf388965 "patched nv-acpi.c"
    need "$(diff -r $S $N | grep -c '^[<>]')" 5 "diff lines"
    
    # 2. DKMS build only. Bounds outside sudo, so make sees nproc 2 (-j2), nice 19, idle IO.
    sudo dkms add -m nvidia -v $V
    $B sudo dkms build -m nvidia -v $V -k $K 2>&1 | tee $O/dkms-build.log
    need "$(dkms status -m nvidia -v $V -k $K | grep -c ': built$')" 1 "dkms built, not installed"
    need "$(modinfo -F vermagic $KO | cut -d' ' -f1)" $K vermagic
    SV=$(modinfo -F srcversion $KO); [ "$SV" != 5B0BEF94704B2BE1D8BB68B ] || { echo "FAIL: srcversion unchanged" >&2; exit 9; }
    echo "$SV" > $O/srcversion.txt
    
    # 3. Module root: a reflink copy of this kernel's tree, with only nvidia.ko replaced.
    mkdir -p $M/lib/modules
    sudo cp -a --reflink=auto /usr/lib/modules/$K $M/lib/modules/
    sudo install -m644 $KO $MK/updates/dkms/nvidia.ko
    sudo depmod -b $M $K
    need "$(sudo diff -rq /usr/lib/modules/$K $MK | grep -v -e "updates/dkms/nvidia.ko " -e "/modules\.[a-z.]* and " | wc -l)" 0 "module root differs only in nvidia.ko and depmod files"
    
    # 4. UKI with the GPU-test cmdline. No post hooks exist (/etc/initcpio/post and /usr/lib/initcpio/post are empty).
    $B sudo mkinitcpio -k $K -r $M -U $O/omarchy_linux-p16-asus-24-nv620-nopsr.efi --cmdline $DC/out620/p16-nv620.cmdline --nopost \
      2>&1 | tee $O/mkinitcpio.log
    sudo chown "$(id -u):$(id -g)" $O/omarchy_linux-p16-asus-24-nv620-nopsr.efi
    sha256sum $O/omarchy_linux-p16-asus-24-nv620-nopsr.efi > $O/SHA256SUMS; b2sum $O/omarchy_linux-p16-asus-24-nv620-nopsr.efi >> $O/SHA256SUMS
    echo "BUILD PASS srcversion=$SV" )

    Gate before the ESP (read-only, root session, after BUILD PASS). Parse both UKIs' PE sections in Python, as P6 did. .linux, .osrel, .cmdline and .uname must be byte-identical to the reviewed GPU test image (out620/omarchy_linux-p16-asus-24-nv620.efi, sha256 0d48c2e2…). Unpack both .initrd archives and compare per-file sha256. The only allowed differences are usr/lib/modules/<K>/updates/dkms/nvidia.ko and the depmod modules.* files. The new nvidia.ko must carry the recorded nopsr srcversion. Any other difference stops the test before the ESP. Then report the file list.

    ESP and menu. The apply block reuses the executed atomic pattern of boot-menu-nv620-repoint/apply-block.sh (0900b05a…). Only names and pins change:

    • live menu pin a13de7a8…;
    • the new image at /boot/EFI/Linux/omarchy_linux-p16-asus-24-nv620-nopsr.efi, checked by sha256 and b2 against the gated copy;
    • the battcap and nv620 recovery images are checked unchanged before any ESP write;
    • the new names limine.conf.pre-nopsr and limine.conf.nopsr-new must not exist yet;
    • the proposed menu differs from the live one only by five added lines after line 32 (diff prints 32a33,37):
    /GPU test nopsr
    comment: Test only. Root @nv620, NVIDIA 620.70.06 with the _PSR lookup skipped. Stay on AC. Pick by hand; the normal boot stays default.
    protocol: efi
    path: boot():/EFI/Linux/omarchy_linux-p16-asus-24-nv620-nopsr.efi#<b2 of the gated image>
    cmdline: <line 32's cmdline, unchanged>
    
    • no limine-update, hooks, BootOrder, one-shot or NVRAM write.

    The literal block depends on the b2, which exists only after the build. It gets its own bugs pass then.

    The measurement

    Tools are reused unchanged and pinned: scripts/gemm.py d0701272… (one 180 fp32 process), reassert/sampler.py 625bd379… with coord.py 30738d21…, and the voxtype cuBLAS (libcublas.so.13 970293e9…, libcublasLt.so.13 8aeceb9b…), which the profile-transition run also uses. GEMM windows (mono_start/mono_end) and sampler rows (mono) share one CLOCK_MONOTONIC timeline. The idle and CPU readers are revision 4b's, unchanged.

    Run (no sudo; started by the agent after login):

    G=/home/sean/.floor-poller/gpu-clock; O=$G/opus-linux/driver-candidate/nopsr; T=$O/nopsr-run.sh
    R=$(mktemp -d "$G/runs/nv620-nopsr-XXXXXX")
    systemd-run --user --unit=nv620-nopsr --collect --wait -p RuntimeMaxSec=300 "$T" "$R" "$(cat $O/srcversion.txt)"
    echo "unit rc=$?" > "$R/A-unit-rc.txt"
    nopsr-run.sh, sha256 b0c385fc… (only bash -n was run)
    #!/bin/bash
    # One 180 s FP32 GEMM with the NVML sampler, on the @nv620 boot that runs the nopsr nvidia.ko. Writes only in RUN_DIR.
    # Runs as the main process of a transient systemd user unit: RuntimeMaxSec bounds it and the unit reaps every child.
    # Usage: nopsr-run.sh RUN_DIR NOPSR_SRCVERSION
    set -u
    G=/home/sean/.floor-poller/gpu-clock; H=$G/opus-linux/reassert; COORD=$G/opus-linux/dc-key-exp/coord.py
    W=/home/sean/.t3/worktrees/asus-proart-p16-rtx-spark-linux/t3-n1x-bf16-gemm; LIB=/home/sean/.local/lib/voxtype/cuda-13/cuda
    R=$1; SV=$2; cd "$R" || exit 1; umask 077
    mono() { python3 -I -c 'import time; print(f"{time.monotonic():.3f}")'; }
    state() { cat /proc/sys/kernel/random/boot_id; findmnt -no FSROOT /; grep -o ' 620\.70\.06 ' /proc/driver/nvidia/version
      echo "srcversion=$(cat /sys/module/nvidia/srcversion)"; grep -o 'RMSkipACPIBattCap=1' /proc/driver/nvidia/params
      echo "ac0=$(cat /sys/class/power_supply/AC0/online)"; echo "powerd=$(pgrep -xc nvidia-powerd)"
      echo "uptime=$(cut -d' ' -f1 /proc/uptime) mono=$(mono)"; }
    
    # Pins and hashes. A refused run exits 10 and has written nothing outside RUN_DIR.
    state > A-pre.txt
    ps -eo pid,comm,%cpu --sort=-%cpu | head -20 > A-procs.txt
    sha256sum "$0" $W/scripts/gemm.py $H/sampler.py $COORD $LIB/libcublas.so.13 $LIB/libcublasLt.so.13 > A-hashes.txt
    [ "$(findmnt -no FSROOT /)" = /@nv620 ] && grep -q 620.70.06 A-pre.txt && grep -qx "srcversion=$SV" A-pre.txt \
      && grep -q RMSkipACPIBattCap=1 A-pre.txt && grep -qx ac0=1 A-pre.txt && grep -qx powerd=0 A-pre.txt \
      && sha256sum -c --quiet - <<EOF || exit 10
    d07012725742cd46fcafd1594ba6e2ba9260e49b0539465fed9eecb2605ec59e  $W/scripts/gemm.py
    625bd379703bcdc32e04b6d080fb634c3b1e12e5948deb30c4677a7303578592  $H/sampler.py
    30738d21e82ee9163d8140afe183670d9b196c5a9bf082446aa57f085d3845e5  $COORD
    970293e94e0609185c71f74f3a943b5dd29e0337d56ca398dab68329a43bf26a  $LIB/libcublas.so.13
    8aeceb9b535e4f956e998f0c01977352806c15f7d10aee2f24ac81a3aba4be40  $LIB/libcublasLt.so.13
    EOF
    # Idle: under 10% of all CPU time busy over 10 s (revision 4b reader, unchanged).
    python3 -I -c '
    import time
    def t(): v = list(map(int, open("/proc/stat").readline().split()[1:9])); return sum(v), v[3] + v[4]  # guest is in user
    a, ia = t(); time.sleep(10); b, ib = t(); busy = 1 - (ib - ia) / (b - a)
    print(f"busy={busy!r}"); raise SystemExit(busy >= 0.10)' > A-idle.txt || exit 10
    # CPU time not charged to the unit (revision 4b/4c reader, unchanged).
    cpu() { echo "$1 mono=$(mono) busy_ticks=$(awk '/^cpu /{print $2+$3+$4+$7+$8+$9}' /proc/stat)" \
      "cg_usec=$(awk '/^usage_usec/{print $2}' /sys/fs/cgroup$(cut -d: -f3 /proc/self/cgroup)/cpu.stat)"; }
    cpu start > A-cpu.txt
    
    # The sampler starts first, so it sees the GEMM's first window. Its own 340 s cap outlasts the 180 s GEMM.
    python3 -I $H/sampler.py $COORD "$R" > A-sampler.out 2>&1 & SP=$!
    env LD_LIBRARY_PATH=$LIB python3 -I $W/scripts/gemm.py 180 fp32 > A-gemm.txt 2>&1; GRC=$?
    echo "gemm rc=$GRC" >> A-rc.txt; touch A-gemm.done
    wait $SP; SRC=$?; echo "sampler rc=$SRC" >> A-rc.txt
    cpu end >> A-cpu.txt
    state > A-post.txt
    [ $GRC = 0 ] && [ $SRC = 0 ]

    No platform state is written, so the run needs no ownership record, restore or ExecStopPost. Start the run only after 300 s of uptime. If the idle gate refuses (exit 10, nothing changed), retry once 5 minutes later. Then stop.

    Computed (rules fixed before the run):

    • t0 = first window's mono_start; t_end = last window's mono_end.
    • Late window L = [t0+100, t_end], about 80 s. It starts after the longest fast window seen on 620 (81 s, nv620-stab-1105-140857).
    • Valid only if all of these hold:
      • A-unit-rc.txt reads unit rc=0, A-rc.txt reads gemm rc=0 and sampler rc=0, and both GEMM checks report 0 mismatches;
      • A-pre.txt and A-post.txt show the same boot, /@nv620, the nopsr srcversion, ac0=1 and powerd=0;
      • A-idle.txt busy < 0.10;
      • every row in the whole GEMM span [t0, t_end] has ac0 "1";
      • in the whole span and in L, the first row is at most 1.5 s after the start, the last row at most 1.5 s before the end, and no gap between rows is over 1.5 s;
      • every row in L has sm_rc 0 and rs_rc 0;
      • GEMM windows fully inside L sum to at least 60 s.
        A missing file makes the run invalid.
    • src and src_rc are recorded facts, not validity rules. The expected value is unknown (see above).

    Ruling, in order:

    1. Any validity failure: invalid, no ruling.
    2. CPU time not charged to the unit ≥ 2.0 CPUs over the run (4c definition): inconclusive.
    3. L median sm ≥ 1800 MHz and L median event_gflops ≥ 2500: "_PSR path implicated". The cap needs the power-source event the driver sends from _PSR.
    4. L median sm ≤ 760 MHz, every L row has rs == 1024, and L median event_gflops ≤ 1815: "the cap does not need the _PSR event". Candidate 1 is closed. 1815 GFLOP/s is the best FP32 window ever recorded with this gemm.py (fast window), so a capped run sits below it.
    5. Anything else: raw values only.

    Always reported: L and early-window ([t0, t0+100)) median, min and max of sm, the rows above 760 in L, src per interval, power, temperature, utilization and window rates.

    Why these numbers.

    • On AC, no retained P16 run has shown a reported SM clock above 1007 MHz. Under full load the fast window is 923-942 MHz and the cap is 754-760 MHz, with reason 0x400. The highest values, 988-1007 MHz, came at idle and under the lighter chunkclock load (nv620-1105-134833, nv620-stab-1105-140857, nv71-gemm-1105-154121). So neither a fast window nor a light phase reaches 1800. The MSI holds about 1800 on battery, and the Surface about 2400.
    • The NVML clock is not a proven physical clock (NOTES "CURRENT VERIFIED"). So rule 3 also needs throughput. The best FP32 rate ever recorded with this gemm.py is a median of 1773 and a max of 1815 GFLOP/s (bf16-abi-1105-171127/fp32.txt, 30 s, fast window). 2500 is 38% above that max.
    • No same-boot control run. The cap reproduced on both driver stacks and both tools, and the ruling thresholds sit outside anything a control could show.

    Not included.

    • AC unplug/replug check. It needs the owner at the machine at a set time, and it puts the test boot on battery with no GSP signal. Routes 1 to 3 are proven dead by the srcversion pin and the code.
    • PMUMON. It needs root, and no rule uses it.

    Risk

    • No battery signal to the GPU on this boot. GSP never hears AC or battery from the driver. The owner must stay on AC. If AC drops at any point of the GEMM span, the ac0 rule makes the run invalid.
    • On battery. GSP is not told. Its DC actions do not run. The key already skips two of them, and PR #1468 shows a no-_PSR GPU may already sit in its battery default. The SoC and EC limits still apply on battery (lab: PL values drop to 65/81/112/130 W). The GPU may draw its AC-level power from the battery, so the battery drains faster. Do not run GPU loads on battery and do not suspend on this boot.
    • Side effect that helps. No notifier also means no power-source notify during GSP init, the deadlock class in #1421.
    • Build on the live root. dkms build installs nothing, and the module tree in /usr/lib/modules is untouched. A failed build leaves only /usr/src/nvidia-620.70.06nopsr, /var/lib/dkms/nvidia/620.70.06nopsr and the lab dir. While booted into @nv620, no pacman.
    • Rollback. A plain reboot boots default_entry 1, the unchanged normal entry. Removing the test entry later is the same atomic pattern in reverse.

    Owner steps

    1. Say go for the build, the ESP write and one reboot.
    2. Before the reboot, stop renders and other GPU work.
    3. Reboot. Type the disk password. In Limine, pick GPU test nopsr by hand.
    4. Keep the charger plugged in. Do not close the lid or suspend. Start nothing heavy for about 10 minutes after login.
    5. When the result is posted, reboot normally. The normal entry is the default.

    Autologin and the saved desktop restore T3 on this root (comment 6673246331). A one-time T3 continuation on this thread runs the run block after login. That is the same pattern as the trace boot.

    Prior art (searched 2026-11-06)

    • NVIDIA/open-gpu-kernel-modules PR #1468 (open, 2026-10-20). "nv-acpi: Get AC state from power_supply when there is no _PSR." On a Surface Laptop Studio with no _PSR, GSP reports BATTERY on AC and Dynamic Boost never engages. The patch feeds AC from power_supply and defaults RMSkipACPIBattCap=1. Without that default, a battery event pins that GPU at 210 MHz. This is the opposite direction: it adds an event where _PSR is missing. Nobody has published a test that removes the event where _PSR exists.
    • #1421: a power-source notify during GSP boot can leave the RM API lock held (a comment says 615.78.08 changed this code). #1372 and #1209: PERF_SET_POWERSTATE fails with 0x11 around s2idle. None of them is about clocks.
    • #1521 (this cap): no NVIDIA reply yet.
    • Searches with no hit: psr_handle, PERF_SET_POWERSTATE with clocks, and any NVreg or registry key that disables power-source handling.
    • Forums:

    Deletes

    • This shape needs none of these: a profile write, the ownership record, restore and ExecStopPost (from revisions 3 to 4c), the t0 poll loop (the sampler starts first, and its 340 s cap outlasts 180 s), a new root snapshot, fstab edits and hook masks (only nvidia.ko changes), a control run and an unplug check. No new measurement tool is written.
    • If "implicated":
      • candidates 2 to 5 of the table diff need no more work for this cap;
      • the profile-transition run (revisions 3 to 4c) and the Surface reverse check are no longer needed to locate the cap;
      • the fix gets its own issue.
    • If "not needed": candidate 1 closes. Then the root session deletes /usr/src/nvidia-620.70.06nopsr, the DKMS entry (dkms remove -m nvidia -v 620.70.06nopsr --all), the module root, the UKI and the menu entry.

    Acceptance of this card's run: BUILD PASS, then the initrd gate, then APPLY PASS, then one valid run read only by the rules above.

    Nothing was built, installed or run. Only bash -n was run on the two blocks. The GPU clock goal is not fixed.

  14. seankd3 commented on Nov 6, 2026

    @seankd3
    OwnerAuthor

    Review record: _PSR-skip shape card (comment 6684296579)

    The reviewer was a fresh, read-only gpt-6.1-sol (high effort) session, started through T3. It ran both passes of the review skill. Nothing was built, installed, booted or run.

    Round 1 (card ce077a66…):

    • Elegance: PASS. No findings. The single-UKI shape keeps rollback and reuses the existing tools. It drops the profile writes, the restore machinery and a second root snapshot.
    • Bugs: FAIL, with 3 blocking findings and 1 non-blocking finding:
      1. The build never installed the run script at its launch path.
      2. AC validity covered only the late window, so an early AC loss could still give a valid ruling.
      3. The negative rule ignored contradictory throughput.
      4. (Non-blocking.) The 942 MHz premise missed the 988-1007 MHz rows under the lighter chunkclock load.

    Fixes (one batch):

    1. The build now pins the run script by its full sha256 (b0c385fc…) before any write and installs it at $O/nopsr-run.sh with mode 0700.
    2. Every row of the whole GEMM span must have ac0 1. The edge-and-gap rule now covers both the span and L.
    3. Rule 4 now also needs a late-window median of ≤ 1815 GFLOP/s, the best FP32 window this gemm.py has recorded.
    4. The premise now reads "no retained run above 1007 MHz on AC".

    Round 2 (card 33de016a…, build block 7fe5c416…, run block b0c385fc…):

    • Elegance: PASS.
    • Bugs: PASS. All 4 findings are closed, with no new findings. The embedded blocks match their files, the hashes match, and bash -n passes.

    Still to come, as the card states:

    • After BUILD PASS, the initrd gate runs.
    • The literal ESP and menu apply block gets its own bugs pass once the UKI's b2 is known.
    • The reboot needs the owner's go.

    The GPU clock goal is not fixed.

  15. seankd3 commented on Nov 6, 2026

    @seankd3
    OwnerAuthor

    _PSR-skip boot: built and on the menu, not booted

    The card (comment 6684296579, PASS 6684300626) is built as written. The boot entry GPU test nopsr is on the menu. Nothing was booted or run. The GPU clock goal is not fixed.

    What was built

    • Source. /usr/src/nvidia-620.70.06nopsr is a reflink copy of the package source with the 3 card lines changed. diff -r shows only these:
      • dkms.conf: PACKAGE_VERSION="620.70.06nopsr", and AUTOINSTALL is removed.
      • kernel-open/nvidia/nv-acpi.c:355: if (0 && !acpi_get_handle(handle, "_PSR", &method_handle)). The patched file's sha256 is 0fde0ee4…, as the card pins.
    • Build. The card's build block (7fe5c416…) ran unchanged at nice 19, idle IO and -j2. It printed BUILD PASS srcversion=907BC2852F3D0751EE8801C. DKMS state is built, not installed. The vermagic is 7.2.8-24-omarchy-p16-asus.
    • UKI. omarchy_linux-p16-asus-24-nv620-nopsr.efi is 177,079,296 bytes.
      • sha256 66f0ef447ab60988fc2b346c43ad1d2eb61ed99bcc24a040a22d49ceed9c37ef
      • b2 94d1fb4e3e516e672312471066f8e920e53198ae67cd8ef71ba2eac2ca59a1e650e7f2f3dd7c23a4f40f93844888d337f443c2ac00c4f8c25722c94385b98a04
    • New disk use. 183 MiB on /home (btrfs exclusive) and 170 MiB on the ESP. The ESP has 380 MiB free.

    Initrd gate (the card's gate before the ESP write)

    The gate compared the new UKI with the reviewed GPU test image (0d48c2e2…).

    • All PE bytes outside the .initrd data are identical, including headers, the entry point, .linux, .osrel, .cmdline and .uname. Only the 5 size fields that follow the initrd's length are masked, and the gate first checks each one against the section table and the PE checksum.
    • Both initrds hold 1581 cpio members in the same order. All headers match.
    • The only difference is usr/lib/modules/7.2.8-24-omarchy-p16-asus/updates/dkms/nvidia.ko. Its sha256 is ea7f1ea6…, its srcversion is 907BC2852F3D0751EE8801C and its version is 620.70.06. The depmod files are identical.
    • Result: GATE PASS.

    scripts/verify-boot.py --image <new UKI> accepts the image. It checks the kernel, the ACPI overrides, the modules and the cmdline.

    Bugs review (the separate pass the card calls for)

    The reviewer was gpt-6.1-sol at high effort, read-only, started through T3. There were 3 rounds.

    • Round 1: FAIL.
      1. The gate ignored cpio headers and member order.
      2. The gate ignored PE load fields. A changed entry point would have passed.
      3. The state script could pass after a read failure.
      4. The new entry's cmdline has no trailing space, but the image's embedded cmdline has one. This is kept on purpose: the card says "line 32's cmdline, unchanged", the existing GPU test entry has the same form, and the arguments are the same.
    • Round 2: FAIL.
      1. The gate masked the PE size fields without checking them. SizeOfImage=0 passed.
      2. One || true in the state script could hide a failed menu read.
    • Round 3: PASS. No findings. All probes fail as they should: a changed uid, a changed entry point and 5 bad PE size fields. The apply block had no finding in any round.

    Boot partition and menu

    The apply block uses the same atomic pattern as boot-menu-nv620-repoint. It printed APPLY PASS.

    • The menu changed from a13de7a8… to 5191b40a….
    • diff prints only 32a33,37, the 5 card lines after /GPU test. The path is pinned by the gated b2. The cmdline line is a copy of line 32.
    • There was no limine-update, no hook, no BootOrder change, no BootNext and no NVRAM write.
    • The new entry is the third entry. The default stays at entry 1.

    sudo python3 scripts/verify-boot.py still exits 1 with "P16-Eintrag fehlt im Limine-Menü". This failure is old: it is the same before and after this change. The script requires an entry that points at omarchy_linux-p16-asus-24.efi, and the normal entry has pointed at -battcap.efi since the battcap repoint. The script checks all b2 pins before that line, and all 5 pins match, including the new one.

    Normal boot: proof that it did not change

    nopsr-state.sh records the state. It is read-only and stops on any read error. These values are the same before the build and after the ESP write:

    Item Value
    limine.conf lines 1-27 (header and /Omarchy (normal)) fcc58d9f…
    default_entry 1
    remember_last_entry absent
    Normal UKI -battcap.efi, sha256 a31ef89ef5e4873cab482bada6145757dac2aa54695448fe9b528fbf7b824ce5
    Normal UKI -battcap.efi, b2 0471e71f…01bdb (matches its menu pin)
    nvidia.ko in the normal UKI sha256 f8e65cfc…, 615.78.08, srcversion 4200B1C66D4D12038311105
    Root @ (normal root, not mounted) btrfs generation 46601
    Every other ESP file (10 files) same sha256
    /usr/lib/modules/7.2.8-24-omarchy-p16-asus on @nv620 (30,885 files) manifest 4ebff624…
    BootOrder 0002,0000,… (efivar sha256 1df4ab20…)
    BootNext, LoaderEntryDefault, LoaderEntryOneShot absent

    The expected differences:

    • The whole-menu hash changed.
    • The new UKI was added on the ESP.
    • The live @nv620 generation increased.
    • DKMS lists nvidia/620.70.06nopsr … built.

    Staged for the run (not run)

    All files are in ~/.floor-poller/gpu-clock/opus-linux/driver-candidate/nopsr/. bash -n passes on both scripts.

    • nopsr-run.sh: sha256 b0c385fc…, installed by the build.
    • nopsr-launch.sh: sha256 030caee4…. It holds the card's run block, unchanged.
    • srcversion.txt: 907BC2852F3D0751EE8801C.

    The pinned tools match their card hashes: gemm.py, sampler.py, coord.py, libcublas.so.13 and libcublasLt.so.13. No T3 continuation is scheduled yet, so the run does not start by itself.

    Owner steps

    1. Before the reboot, stop renders and other GPU work.
    2. Reboot. Type the disk password. In Limine, pick GPU test nopsr (the third entry) by hand.
    3. Keep the charger plugged in. Do not close the lid and do not suspend. Start nothing heavy.
    4. After 5 minutes of uptime, run ~/.floor-poller/gpu-clock/opus-linux/driver-candidate/nopsr/nopsr-launch.sh with no sudo, or tell this thread to run it. The run takes about 4 minutes. The results go to ~/.floor-poller/gpu-clock/runs/nv620-nopsr-*. If the idle gate refuses (exit 10), try once more 5 minutes later, then stop.
    5. When the result is posted, reboot normally. The normal entry is the default.

    Rollback is a plain reboot. To remove the test later, use the card's cleanup and the same atomic menu pattern in reverse.

  16. seankd3 commented on Nov 7, 2026

    @seankd3
    OwnerAuthor

    Post-reboot _PSR test, first attempt: refused before GPU load.

    Boot 4cf963bd-ae0c-4352-bd05-415a7e8411b9 runs 7.2.8-24-omarchy-p16-asus, root /@nv620, NVIDIA 620.70.06, srcversion 907BC2852F3D0751EE8801C, and RMSkipACPIBattCap=1. AC0 is 1. nvidia-powerd is inactive with no process. The run script and all five dependency hashes match the reviewed card.

    The approved launcher ran once. The 10-second idle sample measured busy=0.13902681231380343, above the card's 0.10 limit. The service exited 10. A-unit-rc.txt records unit rc=10. No GEMM or NVML sampler started. There is no performance ruling.

    Raw files: /home/sean/.floor-poller/gpu-clock/runs/nv620-nopsr-N90c4z/.

    The card permits one retry after five minutes. The threshold stays unchanged. The GPU cap remains unresolved.

  17. seankd3 commented on Nov 7, 2026

    @seankd3
    OwnerAuthor

    _PSR-skip run: measurement validity PASS, causal ruling INCONCLUSIVE.

    Purpose: test whether removing the automatic _PSR power-source event removes the sustained GPU cap. This is the single retry allowed by card 6684296579 after the refused idle check. No further run started.

    Raw directory: /home/sean/.floor-poller/gpu-clock/runs/nv620-nopsr-B17yRB/.

    Boot and pins match before and after: boot 4cf963bd-ae0c-4352-bd05-415a7e8411b9, /@nv620, kernel 7.2.8-24-omarchy-p16-asus, NVIDIA 620.70.06, srcversion 907BC2852F3D0751EE8801C, RMSkipACPIBattCap=1, AC0=1, powerd=0. The service, GEMM and sampler each exited 0. Both GEMM checks sampled 66 values and found 0 mismatches. The idle sample was 0.0777711561382598, below 0.10.

    Coverage passes the card. GEMM spans monotonic 1939.659 to 2120.295, 180.636 seconds. All 357 rows inside that span show AC0=1. First/last lags are 0.241/0.454 seconds. Maximum gap is 0.520 seconds. Late window L starts at 2039.659 and has 159 rows, first/last lags 0.228/0.454 seconds, maximum gap 0.520 seconds, and 80.521 seconds of complete GEMM windows. All L clock and reason reads succeeded.

    Raw interval results, minimum / median / maximum:

    Quantity Early, first 100 seconds Late L
    Reported SM MHz 754 / 923 / 962 754 / 754 / 754
    Event GFLOP/s, complete windows 796.9 / 1109.55 / 1581.7 791.8 / 955.3 / 1072.4
    Host GFLOP/s, complete windows 795.1 / 1108.1 / 1581.8 791.1 / 955.85 / 1073.7
    NVML power, mW 1324 / 2924 / 3706 2137 / 2437 / 2543
    Temperature, C 37 / 39 / 40 39 / 40 / 40
    GPU utilization, percent 64 / 95 / 96 94 / 95 / 96

    Zero late rows exceed 760 MHz. Every late reason value is 1024 (0x400). All 360 sampler rows report source 1 with source read status 0, including the rows just outside the GEMM span. The sampler source interval is monotonic 1938.892 to 2120.347. AC0 stays 1. Source 1 is a recorded result, not an extra validity rule.

    The ordered ruling stops at rule 2. Over 182.433 seconds, host busy time increased by 554.52 seconds and unit CPU time increased by 186.573907 seconds. CPU time not charged to the unit is (554.52 - 186.573907) / 182.433 = 2.016883420214547 CPUs, above the 2.0 bound. This quantity includes test-caused kernel work. It does not prove another workload ran. Do not apply rule 4 or close candidate 1 from this run.

    The experimental change did not demonstrate a sustained improvement. The GPU cap remains unresolved. The unit is inactive and neither GEMM nor its sampler remains. The test boot is still active. No driver, firmware, menu or protection setting changed during this continuation.

    Raw SHA-256:

    • A-gemm.txt: 9cc3e40c9941528dab07b92cdaf9f67826571ce11d6bd98ce2231d9a7e69e450
    • A-nvml.jsonl: e06076698edc4126b9dc47c27129b0123b37b0417bf5b7fef8e14787d2074168
    • A-cpu.txt: 569105831cff498b1934f06b8eb3c521031d4d8c82e2a9949d29d544228f1bd8

    A separate read-only nvidia-smi query after the run reports maximum graphics and SM clocks of 3003 MHz each. That ceiling is not a promise of a sustained operating clock.

  18. seankd3 commented on Nov 7, 2026

    @seankd3
    OwnerAuthor

    Next-step status after the _PSR run:

    The current experiment has no sustained improvement and remains inconclusive under its ordered CPU rule. Its allowed retry is used. No additional run or protection override started.

    The HP owner's upstream result is relevant: their _PSR-skip build also retained the cap, and charger transitions still changed GPU behavior. This is evidence about the HP, not proof of the P16's remaining input path: NVIDIA/open-gpu-kernel-modules#1521 (comment).

    A bounded research/design task was sent to Claude Opus 5.5 at high effort under the required author workflow. It failed before producing a result: Claude API rate limit reached. No substitute author or new experiment design was used.

    The existing approved kernel milestone remains ready: PR #18's installed linux-p16-asus-26 entry under +Omarchy, followed by its existing hardware checks. It is not a proven fix for the GPU cap. The owner must select the entry and unlock the disk. The current _PSR test boot remains active on AC.

  19. seankd3 commented on Nov 7, 2026

    @seankd3
    OwnerAuthor

    Kernel 7.3 follow-up: the one-shot boot succeeded. Boot 4ac8cfa6-310b-44ee-8026-7bcc8180235f now runs 7.3.0-26-omarchy-p16-asus on /@nv620 with NVIDIA 620.70.06, stock srcversion 5B0BEF94704B2BE1D8BB68B, RMSkipACPIBattCap=1, and physical AC. The _PSR-skip experiment module is no longer loaded.

    The existing scripts/gemm.py 30 fp32 ran unchanged with LD_LIBRARY_PATH=/home/sean/.local/lib/voxtype/cuda-13/cuda. It exited 0 and both warm-up/end checks sampled 66 values with zero mismatches. A read-only nvidia-smi query immediately afterward reported SM754MHz, active reason0x400, temperature39.21C. This is a short functional check with a post-run clock sample. It is not a repeat of the reviewed180-second experiment or a new causal ruling.

    CUDA execution works on the new kernel. The compiler-based check separately cannot run because /opt/cuda/bin/nvcc is missing. That missing compiler is not evidence for the cause of the low-clock state. No toolkit, driver, firmware or power setting was installed or changed during these post-boot checks.

    The GPU goal remains unresolved. Kernel hardware acceptance is recorded on issue#15.

  20. seankd3 commented on Nov 7, 2026

    @seankd3
    OwnerAuthor

    Runtime route established with GDB on kernel 7.3 / matched 620.

    The bpftrace attempt failed before attachment because it could not determine instruction boundaries in the stripped ARM64 executable. A GDB observer now checks the executable SHA-256 and verifies each breakpoint's live instruction bytes against the file before continuing. It reads the same branch points. Return values are read at verified call-return sites, so no return-address probe is needed. ASLR stays enabled. No target registers or application data are changed by the observer.

    Accepted run: /home/sean/.floor-poller/gpu-clock/runs/powerd620-gdb-WrEWfC/. GDB rc=0, empty stderr, inferior exit=0, BEGIN/END present, and no remaining nvidia-powerd or GDB process. The journal records Quit successfully.

    Observed events in order:

    • factory_create_entry
    • nvpcf3x_init_entry, w1=0xf0100
    • nvpcf3x_query_raw, has_value=1. The raw u64 is a pointer-shaped value, not interpreted as capabilities.
    • nvpcf3x_objprim_ptr, x0 nonzero, so the primitive load call path is taken.
    • nvpcf3x_init_ret, raw=1, bool=1.
    • factory_3x_true_branch
    • main_no_worker
    • quit_entry, w0=2.
    • Normal process exit 0.

    The 2x fallback and MOBILE-clear probes were armed and not hit. This establishes route (a): 3.x init succeeds, the factory adds no Topps worker, and main exits normally. It does not establish an ongoing controller or a GPU fix. A subsequent clock query remains 754MHz with 0x400.

    There were two GDB launches. The first, powerd620-gdb-ZXbvti, captured the same branch but stopped on the application's SIGUSR1 shutdown signal. Batch GDB then ended the inferior. That run is not accepted as a normal exit. The second explicitly passes SIGUSR1/SIGUSR2 without stopping; both original trace and corrected launch are retained.

    SHA-256:

    • powerd: eb9e0371f73667f242e8d4742e17a21a68470e08cdc6438a84c263a4c3b6f4d2
    • observer: 6ccc3d69a1671738e26bad5151437edadfa5844f965a76979dc0e93e220db372
    • accepted trace: 2d9108187c349c532d46547fe39ce17d509c7c7fe6056782cfb3b7cdb227b14a

    Next causal question: whether the primitive scheduler has unfinished periodic work that main's empty-Topps-worker exit tears down. This is a hypothesis to inspect, not a demonstrated defect.

  21. seankd3 commented on Nov 7, 2026

    @seankd3
    OwnerAuthor

    Bounded lifetime experiment, prepared before execution.

    Question: does main's empty-worker exit tear down useful NVPCF 3.x work before it can maintain the platform policy? The clean GDB run establishes successful 3.x initialization and immediate no-worker exit, but not causality.

    A private executable copy changes only ARM64 instruction at file offset 0x5500c: cbz x0,0x55174 (400b00b4) to nop (1f2003d5). Every other byte matches the pinned vendor executable. This keeps main's existing polling/sleep loop alive. It does not change any clock, voltage, power-limit value or vendor call payload. The installed executable and service are untouched. The copy is an experiment, not a distributable fix.

    Private copy SHA-256: 47cede318d6ae1507f0f35a75ff982222ef6e7d7ad18f5fd20b590823dc0670f. Source SHA-256: eb9e0371f73667f242e8d4742e17a21a68470e08cdc6438a84c263a4c3b6f4d2.

    Run directory: /home/sean/.floor-poller/gpu-clock/runs/nvpcf-lifetime-JK1dPh/. The root transient unit is capped at 300 seconds with control-group cleanup and a five-second stop bound. The wrapper retains all statuses and stops its known child. Because removing this exit can also prevent graceful exit through the same main loop, the wrapper escalates termination of that exact child after two seconds. No system service is enabled.

    Phases reuse the pinned exact FP32 GEMM and NVML sampler unchanged: A, 30 seconds before the daemon; B, 180 seconds with the private daemon alive; C, 30 seconds after it stops. AC and correctness must pass. A late B window begins 100 seconds after its first GEMM window. It needs at least 60 seconds of complete windows, full-span AC=1 and sampling edge/gap coverage within 1.5 seconds.

    A candidate improvement requires late-B median reported SM clock >=1800MHz and median event throughput >=max(2500GFLOP/s, twice the larger A/C median), with successful calculations and the daemon alive throughout B. Reason0x400 is recorded, not required to disappear. CPU activity and resident desktop processes are metadata. A negative result is reported as no improvement observed. No architectural-peak or physical-clock conversion is claimed.

  22. seankd3 commented on Nov 7, 2026

    @seankd3
    OwnerAuthor

    Lifetime experiment completed: no sustained improvement observed.

    Raw: /home/sean/.floor-poller/gpu-clock/runs/nvpcf-lifetime-JK1dPh/; computed facts: summary.json. The transient unit exited0 after4min10.216s. A/B/C GEMM and samplers all exited0. All six warm-up/final checks sampled66values with0mismatches. AC remained1 in every workload row, all SM/reason/source queries succeeded, and sampling gaps stayed below0.521s.

    A, before: SM min/median/max754/754/754MHz, event throughput median613.7GFLOP/s.
    B, private daemon alive for the180-second run: late window has159rows and79.417s of complete GEMM windows; SM747/754/754MHz, event throughput median615.9GFLOP/s, reason1024in all late rows.
    C, after: SM754/754/754MHz, event throughput median632.85GFLOP/s.

    The private daemon had three threads at the beginning and end of B. A brief GDB thread observation during the early part of B confirmed main and event threads alive and detached normally. The daemon exited0 after the bounded stop. No test daemon remains. The installed vendor executable still matches eb9e0371…, and no service was enabled.

    The predeclared improvement criterion was not met. Keeping the existing3.x framework alive by bypassing main's empty-worker exit does not demonstrate a fix on this machine. This does not establish why the original factory omits workers or eliminate all NVPCF implementations as possible causes.

    CPU time not charged to the test unit was2.78/3.49/2.58CPUs across A/B/C. These values include test-caused kernel work and other system work. The throughput figures are observations in this desktop session, not an isolated architectural benchmark. The reported clock remained low throughout.

    Next review: ASUS EC/power-policy state and firmware versions. ASUS's official EC-reset procedure is documented at https://www.asus.com/us/support/faq/1050239/. No EC reset or firmware flash has been performed by this continuation.

  23. seankd3 commented on Nov 7, 2026

    @seankd3
    OwnerAuthor

    Firmware-interface review and next standard-profile experiment.

    The live ASUS status getter DSTS(0x0012006c) returns 0x1000a; the temporary debug selector was restored to0. This confirms a firmware charger-state value but does not establish adapter wattage. UCSI reports USB-C PD online/current5A but zero voltage/max-current fields, so those fields cannot prove negotiated power. ASUS specifies140W,28V/5A for this model: https://www.asus.com/in/laptops/for-creators/proart/proart-p16-h7607/techspec/. Charger identity and prior EC-reset history are pending owner answers.

    The retained H7607BA.300 AML has no handlers for the common ASUS NVIDIA dynamic-boost/TGP device IDs0x001200C0/0x00120098/0x00120099. Its PPT limit getters0x001200A0/A3 return only the constant presence bit. Writing those values without an actual readback would not be a measured fix; no such write was made.

    A distinct, bounded software test will hold the standard power-saver profile with powerprofilesctl launch during180s of the existing exact FP32 GEMM. It will measure30s in performance before and30s after release. The built-in D-Bus profile hold owns the temporary state and releases it when its client exits. No custom firmware/voltage/power-limit setter is used. The unit is capped at300s with control-group cleanup. Actual platform profile and every CPU policy's governor/min/max are captured before and after each phase.

    This tests whether shared CPU/GPU policy selection changes the GPU cap. Earlier retained profile sweeps used2s probes; the long prior same-value performance write did not test a held power-saver workload. A better GPU result under reduced CPU performance would be a diagnostic lead, not by itself proof that full system performance is fixed.

    Run directory /home/sean/.floor-poller/gpu-clock/runs/gpu-profile-hold-gx6WzT/. The predeclared candidate criterion reuses the lifetime test: late B starts100s after its first GEMM window, requires>=60s of complete windows, full-span physical AC and<=1.5s sampling edge/gaps, exact output checks, median SM>=1800MHz and median event GFLOP/s>=max(2500,twice the larger A/C median). Source and reason bits remain metadata. The current performance profile must return after the hold before this experiment is accepted as restored.

  24. seankd3 commented on Nov 7, 2026

    @seankd3
    OwnerAuthor

    Sustained standard profile hold: no unlock

    Completed the bounded test in /home/sean/.floor-poller/gpu-clock/runs/gpu-profile-hold-gx6WzT. Unit exited 0 after 4m25.689s. All three GEMMs and samplers exited 0. All warmup/end checks sampled 66 values with zero mismatches. AC remained online. All NVML status codes were 0.

    • A, performance, 30.216s: median SM 923 MHz, median event throughput 1890.4 GFLOP/s.
    • B, held power-saver, 180.097s: the late window contains 79.564s of complete GEMM windows and 159 NVML samples. SM min/median/max 702/715/734 MHz. Median event throughput 1464.0 GFLOP/s. Maximum sample gap 0.540s in that window.
    • C, after release, 30.553s: balanced, median SM 728 MHz, median event throughput 1481.85 GFLOP/s.

    The profile hold reached the standard drivers: platform profile was quiet, with schedutil governors. The release selected balanced, so this is not a performance A/B/A. I explicitly restored the original performance profile after the run and verified no holds. No test GEMM, sampler, or private powerd process remains.

    B fails the declared sustained >=1800 MHz criterion. This profile setting is not a fix. Reason 0x400 remained present, which alone does not identify the harmful limit. Raw files and summary.json retain the full sample coverage and checks. Firmware/charger investigation continues. No installed binary, service, driver, or firmware changed.

  25. seankd3 commented on Nov 7, 2026

    @seankd3
    OwnerAuthor

    Next bounded test: CPU limit with ASUS performance unchanged

    The power-saver hold changed both the CPU settings and the ASUS platform profile. Its negative result cannot isolate a shared CPU/GPU budget problem.

    Keep powerprofilesctl and ASUS platform profile at performance. Save all CPU policy scaling_max_freq values, then cap only those maxima at 2,000,000 kHz through the standard CPUFreq interface. Run the same FP32 GEMM for A30s/B180s/C30s. Restore all saved maxima before C and on every normal/error/signal exit. A root transient unit bounds the whole job to 300s. No powerd, firmware, voltage, GPU protection, or installed source change.

    The phases retain actual profile, governor and min/max readings. A maximum that does not read back at the intended cap invalidates B. ASUS profile changes invalidate B. AC must stay online, all correctness and status checks must pass, and the late B window starts 100s after GEMM start. A sustained unlock candidate requires at least 60s complete windows, sample gaps and edge coverage <=1.5s, median SM >=1800 MHz, and median event throughput >=max(2500 GFLOP/s, twice the larger A/C median). Lesser improvement is a lead only. No improvement closes this tested CPU-limit setting, not every platform-policy route.

    The installed Omarchy PPD 0.30-5 already reports the NVIDIA nvidia_rtx_spark CPU backend. The current kernel logged DVFSRC and BestPerf initialization. Installing those same components is not a new fix.

  26. seankd3 commented on Nov 7, 2026

    @seankd3
    OwnerAuthor

    Charger telemetry boundary

    Read-only UCSI GET_CAPABILITY through the running kernel's debugfs interface returned 0x000000000000000001300030012000010000000300004146. Decoding the packed struct ucsi_capability gives attributes 0x4146, three connectors, and optional features 0x0000. Thus firmware does not advertise UCSI_CAP_PDO_DETAILS.

    This explains why the standard Linux source-PDO list is empty: ucsi_get_pdos() returns without issuing GET_PDOS when that feature is absent. The empty list and zero voltage telemetry are not evidence of a low-wattage contract. Sysfs reports an online USB-PD source and current_now=5,000,000 uA, but voltage remains unknown. No negotiated wattage is claimed. No role, PDO, power, or charger setting was changed. Raw command and decode are retained as gpu-cpu-cap-25tex9a0/ucsi-capability.json.

    The physical adapter/cable identity and prior EC-reset history still need owner input. ASUS documents a full controller reset separately from a normal reboot: https://www.asus.com/us/support/faq/1050239/ . This is a reversible next diagnostic, not a proven GPU fix.

  27. seankd3 commented on Nov 7, 2026

    @seankd3
    OwnerAuthor

    CPU-only 2 GHz cap: no sustained unlock

    Completed gpu-cpu-cap-25tex9a0 with unit exit 0 after 4m11.653s. Only CPU policy maxima changed. PPD and ASUS platform profile stayed performance in every phase. All 20 maxima read back at 2,000,000 kHz in B, then returned to their original 2,808,000/4,004,000 kHz values. restore_rc=0. All saved maxima still match after completion.

    • A, original CPU limits, 30.034s: median SM 923 MHz, median event throughput 1891.0 GFLOP/s.
    • B, 2 GHz CPU maxima, 180.841s: late window has 80.753s complete GEMM windows and 160 NVML samples. SM min/median/max 734/747/754 MHz, event median 1530.0 GFLOP/s. Maximum late sample gap 0.5074s, edge lags 0.3805/0.2379s.
    • C, original CPU limits restored, 30.135s: median SM 750.5 MHz, event median 1533.25 GFLOP/s.

    All GEMM/sampler exits were 0. All warmup/end correctness checks had zero mismatches among 66 samples. AC remained online. Every recorded NVML status was 0. Maximum GPU temperature was 41 C. Reason 0x400 stayed present.

    B fails the declared sustained unlock gate. C did not return to A throughput, so do not attribute that initial drop solely to the CPU limit. The tested CPU limit is not a fix. No claim is made about every possible shared firmware budget. The requested limits are verified through CPUFreq, not independent physical clock measurements.

    Cleanup verified: original CPU maxima, performance profile, no profile holds, no test GEMM/sampler/private powerd processes. No installed source, GPU protection, or firmware changed. Raw data and summary.json remain in /home/sean/.floor-poller/gpu-clock/runs/gpu-cpu-cap-25tex9a0.

    Next discriminating physical check remains the original ASUS adapter/cable and full EC reset history. The current firmware reports no optional UCSI PDO-details support, so Linux cannot settle adapter wattage from its empty PDO list.

  28. seankd3 commented on Nov 7, 2026

    @seankd3
    OwnerAuthor

    Focused configuration observation

    The boot is unchanged (4ac8cfa6-310b-44ee-8026-7bcc8180235f), so the requested physical controller reset has not occurred. The prior turn completed bounded negative profile/CPU-limit tests.

    A remaining ambiguity is what the successful NVPCF 3.x startup actually loaded. Static disassembly of the pinned stock 620.70.06 powerd shows five configuration-return call sites at 0x72e4c/0x72eec/0x72f14/0x72f3c/0x72f64. The compiled initialization log at 0x73630..0x73650 obtains its object count from the global map at 0x3275b8+0x28. The configuration object layout has the returned size at +8 and a byte-vector range at +0x10/+0x18/+0x20. The following framework load returns at 0x7367c.

    Extend the existing SHA-pinned GDB observer to record these values once during an unmodified normal daemon startup. Read at most 4096 bytes per configuration object, require valid vector bounds, verify every breakpoint instruction against the pinned ELF, and retain normal-exit/cleanup evidence. No branch, register, setting, or installed file is changed. This can distinguish successful wrapper startup from actual populated configuration. It cannot prove a working power controller or fix the GPU.

  29. seankd3 commented on Nov 7, 2026

    @seankd3
    OwnerAuthor

    NVPCF configuration capture completed

    Stock SHA-pinned 620.70.06 powerd loaded six configuration wrapper objects, returned load status 0, returned true from the 3.x initializer, then took the normal no-worker exit. GDB exited 0, stderr was empty, inferior exited 0, and no powerd process remained.

    The observed configuration objects contain:

    • Slot 0x31, initialization data: size field 128 bytes. The backing vector is 4096 bytes, with its remaining bytes zero. A byte-for-byte check confirms that the first 128 bytes match the retained P16 DSDT initialization buffer. SHA-256 of those 128 bytes: be080f9851faa0257f915be627644d61227c31f9817eb2bdf190a142e2ae94b1.
    • Slots 0x32/0x33, subscribed/dynamic primitives: size fields six bytes, each all zero. Backing vectors are 4096 bytes and entirely zero. These match firmware subfunctions 2 and 3.
    • Slot 0x34, write-primitives object: size field and zero vector 4096 bytes. This is an allocated output buffer, not a returned firmware policy. Constructor 0x1569b0 calls allocation helper 0x1553c0 with 4096 at 0x156a00, then returns the object at 0x156b58. It does not fetch DSM subfunction 4 there.
    • Slot 0x35, critical-status write object: size field and zero vector four bytes. Constructor 0x157090 likewise allocates four bytes at 0x1570e4. This observation does not show a DSM subfunction-5 response.

    Correction: the first version of this comment incorrectly attributed the 4096-byte slot to a kernel integer-zero response. The constructor disassembly disproves that explanation. The raw trace is unchanged.

    The six objects are configuration wrappers, not six power controllers. Successful framework load does not prove populated power-budget primitives or control effectiveness. No new clock measurement or fix claim follows from this trace.

    Artifacts: /home/sean/.floor-poller/gpu-clock/runs/powerd620-config-wkugf4kp. Observer SHA-256 fc3b11aa5892f33cb3e7ff13caa9079862f272cd2a9f67b7a39db9323a59e1bd. Trace SHA-256 d3416a7f15c393726738bfd372f2a7396e370ad1ea3a3d4ff6a4a1918bff1479. Raw bytes and concise decoded events retained separately.

    Owner confirmed the original ASUS charger is directly connected and has performed no manual controller reset. Prepare the same-stack measurement after a full physical EC reset. No further daemon startup is needed.

  30. seankd3 commented on Nov 7, 2026

    @seankd3
    OwnerAuthor

    Abrupt restart during one-shot boot preparation

    The prior sudo bootctl set-oneshot @current command is the final sudo entry in boot 4ac8cfa6-310b-44ee-8026-7bcc8180235f. No shutdown sequence was retained. A new boot 39b67962-caed-4247-ae98-2f8bfd6b676c is now running the same 7.3.0-26 kernel, @nv620 root, stock 620.70.06 module and battcap key. I will not repeat that EFI-variable operation. Temporal association is established. Firmware causality and a physical controller reset are not established.

    This boot has no CPUFreq policy entries, no loaded cppc_cpufreq, and PPD exposes only the ASUS platform driver. CPU acpi_cppc metadata still exists. The first measurement job (gpu-newboot-f22nvzt0) failed in the metadata snapshot because its glob assumed at least one CPU policy. It stopped before any GEMM or NVML sampler launched. Its failed result is retained.

    The metadata collector now records a zero policy count and does not treat absent CPU policies as a literal path. A separate bounded 180s observation in gpu-newboot-z4gxukcc will measure the current boot without changing its CPU/GPU settings. This is not a same-CPU-stack comparison or a confirmed EC-reset test. No one-shot boot request or additional reboot is being issued.

  31. seankd3 commented on Nov 7, 2026

    @seankd3
    OwnerAuthor

    Owner now confirms completing the requested physical reset. This means the restart coincided with an attended reset. Do not attribute it to bootctl from temporal ordering alone. No additional boot-selection write is needed.

    The current 180s measurement is running. Early samples show roughly 2.2 GHz SM, about 61 W NVML GPU power, and roughly 13.5 TFLOP/s event throughput with the unchanged FP32 GEMM. These are early observations, not acceptance or a fix claim. Preserve the current state. If the 180s run passes, measure for at least 15 further minutes because other N1X reports describe delayed fallback after several minutes. The missing CPUFreq policies are a separate changed condition that still needs examination before causal attribution.

  32. seankd3 commented on Nov 7, 2026

    @seankd3
    OwnerAuthor

    First high-clock sustained result after the attended reset

    gpu-newboot-z4gxukcc completed on boot 39b67962-caed-4247-ae98-2f8bfd6b676c, with 7.3.0-26, @nv620, stock 620.70.06 srcversion 5B0BEF94704B2BE1D8BB68B, AC online and ASUS performance profile.

    • GEMM span 180.140s. GEMM and sampler exited 0. Unit deactivated successfully.
    • Warmup/end each checked 66 samples with zero mismatches.
    • 359 NVML samples. Maximum gap 0.5120s, edge lags 0.2086/0.3724s. AC online throughout, all NVML statuses zero.
    • Late window after 100s: 79.501s complete GEMM windows, SM min/median/max 2086/2184/2242 MHz, median event throughput 12,204.2 GFLOP/s.
    • Maximum GPU temperature 73 C. Reason 0x400 remained present despite the higher clocks.

    This proves substantially higher measured GPU performance on this boot. It does not prove indefinite persistence or isolate the reset as the cause: CPUFreq policies and the NVIDIA PPD CPU backend are absent on this boot. Static CPPC metadata remains readable, but feedback counter reads fail. Those differences stay explicit.

    A 900s persistence test is now confirmed live as gpu-reset-sustain-2.service, using the same GEMM and getters. The sampler's only operational change is a 1000s stop ceiling, replacing 340s so it covers the whole test. Whole unit runtime is bounded to 960s. No CPU/GPU setting changes during it. Source/input hashes are verified and raw artifacts go to gpu-reset-sustain-h063eda7.

    An initial launch of the long-test unit exited 1 before hash checking or workload launch. No workload or result directory existed. A separate read-only shell trace then verified all preflight gates, and the traced second unit passed every gate and started the one 900s workload. The first setup failure remains in the journal. Do not count it as a performance test.

  33. seankd3 commented on Nov 7, 2026

    @seankd3
    OwnerAuthor

    Correction to the persistence test duration: the pinned GEMM clamps its seconds argument with min(secs, 300). Thus the 900 request in gpu-reset-sustain-h063eda7 completed successfully after about 300s, not 900s. The raw timestamps show this. Both correctness checks passed, all process exits were 0, and GPU clocks remained high through the five-minute run, but it is not 15-minute evidence.

    Use a private copy with only the argument ceiling and its docstring changed from 300 to 900. Arithmetic, data, CUDA calls, timing windows, and correctness checks remain byte-for-byte unchanged. An isolated AST check verifies that the real argument parser returns 900 for 900 and still caps larger requests at 900, without loading CUDA. The new run retains a diff and input hashes. A separate 900s measurement will establish the actual elapsed workload span. This corrects a measurement setup error, not a GPU fault.

  34. seankd3 commented on Nov 7, 2026

    @seankd3
    OwnerAuthor

    Concrete CPU-enumeration regression: stale GNVS address in the DSDT override

    Read the current firmware DSDT from the original FADT Xdsdt address 0x86f10018, using read-only aligned 32-bit accesses and validating signature, length, and checksum. Ordinary byte-copy reads faulted on this device mapping. The aligned read completed with exit 0.

    Current firmware DSDT: 74052 bytes, SHA-256 b6a1ef004b71c54878ca6f1dfa13c776a8621b561988df61495c12ae78173c7b, OEM revision 4. Its GNVS OperationRegion points at 0x86f70000.

    Loaded override: same length, SHA-256 80e0ad4a051704003d09029cb600a9814e1cd370ce774b9749dc09d2232b5aaf, OEM revision 5. It still points at 0x86f50000.

    The current original firmware DSDT differs from the retained original 0b9ed926... in exactly two bytes: its checksum and the GNVS address byte at AML offset 0x4ee (f5 -> f7). No BIOS version change is needed for this address to move.

    Read-only first-word checks: old address = 0x02261201; current firmware address = 0xffffffff. The loaded DSDT's CPU _STA methods read C0CE/C0BE/C1CE/C1BE from those four bytes. ACPI currently marks only CPU device instances 00/06/09/0b/0c/10 present. Only those six CPU devices have CPPC metadata, although all 20 CPUs are online through MADT. acpi_cpc_valid() requires descriptors for every online CPU, so missing descriptors explain why CPUFreq cannot initialize.

    Artifacts: /home/sean/.floor-poller/gpu-clock/runs/acpi-aligned-read-n6wq55q2, including original table, loaded table, validated address comparison, and the two-byte original-table delta. No firmware or ACPI setting changed during these reads.

    Do not call the reset a complete fix while this regression exists. Check whether the DSDT override is redundant with the existing acpi_osi="!Windows 2022" boot option. If so, remove that override and let the firmware supply its current addresses, preserving the independent ASUS SSDT response-buffer correction. This would fix the class of moved firmware-data addresses rather than pinning another physical address.

  35. seankd3 commented on Nov 7, 2026

    @seankd3
    OwnerAuthor

    Actual 15-minute GPU run passed; corrective boot image prepared

    gpu-reset-900s-4vuxv6m_ completed with 900.640s measured GEMM span. The private benchmark copy changes only its duration ceiling/docstring from 300 to 900. Unit exited 0 after 15m2.351s. Both GEMM and sampler exited 0. Warmup and end each checked 66 values with zero mismatches.

    All 1795 NVML samples reported AC online and all status codes zero. Maximum sample gap 0.7496s, edge lags 0.1496/0.3497s. Full-run SM min/median/max 1891/2047/2405 MHz. Median event throughput 16,540.75 GFLOP/s. Last-five-minute SM min/median/max 1891/2041/2060 MHz, median event throughput 16,603.4 GFLOP/s. Maximum temperature 73 C. Reason bits 0x400 and 0x4 both occurred. These are normal protected driver settings, with no voltage or protection override.

    This passes sustained high GPU performance on the current boot. It does not pass full-system acceptance because the stale DSDT hides CPU ACPI devices.

    The actual firmware ASUS SSDT at physical 0x86f60018 is byte-identical to the retained original (SHA-256 46d9ed16af06093cdb849df69646683317d53d7342dfd38f24eab00795d396f0). Its independent response-buffer correction can stay. The DSDT override changes only SSPM's _OSI("Windows 2022") predicate to false, which the existing boot option already does. The running kernel explicitly logs Deleted _OSI(Windows 2022).

    Prepared a separate UKI by removing only the early kernel/firmware/acpi/DSDT.aml cpio member. Every remaining initrd member, in order, has identical headers and contents. The ASUS SSDT remains SHA-256 9b28ffc14c19f8b7ea2278de88ff9ec070f5f70401107d81fef067a916aa9695. Bytes outside the initrd are identical except validated size/checksum fields. PE section sizes, SizeOfImage, initialized-data size and checksum passed the existing verifier's checks. Kernel, GPU modules, userspace selection and cmdline are unchanged.

    Candidate: /boot/EFI/Linux/omarchy_linux-p16-asus-26-fresh-acpi.efi, SHA-256 e3931e65abbb3e439f4405bc526dcc311febc63fbcbbb7b5e61944f6a7da5b03. Prepared menu entry Omarchy.linux-p16-asus-26-fresh-acpi. Existing entries and default remain. Full static evidence, source script and original menu backup: /home/sean/.floor-poller/gpu-clock/runs/gpu-fresh-acpi-image-by0c7h13.

    Not booted. Owner approval for the interrupting reboot is pending. Next verify native DSDT/GNVS, all CPU ACPI devices, all CPUFreq policies, the NVIDIA PPD CPU backend, and sustained GPU performance together. Do not load CPUFreq on the broken ACPI namespace as an attempted fix.

  36. seankd3 commented on Nov 7, 2026

    @seankd3
    OwnerAuthor

    Prepared the durable source correction as draft PR #19, commit 29c045b894f6ed0f51c1607e3f416c637bda871f. All 49 offline tests pass. The new regression checks reproduce the old behavior against main. The full ACPICA build works with only the machine's original ASUS SSDT, and its output still matches the existing SSDT pin.

    The first prepared test UKI preserved its member bytes but omitted the 512-byte boundary adjustment after deleting the DSDT entry. A later full lsinitcpio check caught this before boot. That test image has been replaced. The corrected image preserves the compressed main payload byte-for-byte and adds 464 bytes of padding. Both the private copy and installed image pass the complete updated UKI/module verifier, with release/package identity set to the installed 7.3.0-26 kernel.

    Current candidate: /boot/EFI/Linux/omarchy_linux-p16-asus-26-fresh-acpi.efi, SHA-256 c539c4c7b3b83680f2b6f460e1bf2bb71353c6052682ac106bd04918f5a6356c. Menu entry remains Omarchy.linux-p16-asus-26-fresh-acpi. Normal default and normal images remain available. New menu SHA-256 2b4cf6cb2088ed79434a714e2b5ae157e68f3941d8a046eebb9f1a3e115a86f7. Artifacts: /home/sean/.floor-poller/gpu-clock/runs/gpu-fresh-acpi-aligned-ik5v17mt.

    Reboot approval is still pending. No one-shot selection or reboot was made. Required next proof remains: a new boot using native firmware DSDT, complete CPU ACPI/CPPC enumeration and CPUFreq controls, followed by sustained high GPU performance. The 900.64-second GPU pass on the current boot does not establish that combined acceptance.

  37. seankd3 commented on Nov 7, 2026

    @seankd3
    OwnerAuthor

    Read-only follow-up to the new RATED_TDP lead on upstream #1521. Audited the complete published preload and matched command 0x2080206e and its 12-byte parameters against the installed 620 source. It adds GET_CONTROL queries only. No SET_CONTROL or protection change was used.

    The pinned source blob is 5dda71550c05e537d13d3f4fb2d66f442c1d52ef, source SHA-256 20f466d44ef18c54018a71d736a8daf7a59657384feb00f025e98faa2839185f. On the current post-reset boot, RM, WAR, GLOBAL, PROFILE and OS all read action 0 / DEFAULT, with ioctl rc 0 and RM status 0, both at idle and during a 30.120-second cuBLAS workload. A sampler row before and after the loaded query showed 2281 MHz and 96% utilization. All workload telemetry statuses were zero, AC stayed 1, and both 66-element correctness checks had zero mismatches. Workload, sampler and outer service exited 0.

    This is the fast-state baseline only. We have no corresponding RATED_TDP read from the earlier affected boot, so it does not rule out this arbitration path as the original limiter. It adds a specific read-only comparison to the corrected-image acceptance if low clocks return.

    Artifacts: /home/sean/.floor-poller/gpu-clock/runs/gpu-ratedtdp-read-lutcvw6z. Boot remains 39b67962-caed-4247-ae98-2f8bfd6b676c; reboot approval is pending. No further load is running.

  38. seankd3 commented on Nov 7, 2026

    @seankd3
    OwnerAuthor

    Corrected image: 900 s run passed; made default; PR #19 merged

    Boot adcbe679-07d3-41f7-b303-a701e160eade, entry Omarchy.linux-p16-asus-26-fresh-acpi, kernel 7.3.0-26, stock NVIDIA 620.70.06, firmware DSDT OEM revision 4, retained ASUS SSDT 9b28ffc1….

    CPU controls are back. All 20 ACPI CPU devices report status 15 with a physical node. All 20 CPPC directories and 20 CPUFreq policies exist, before and after the run. power-profiles-daemon and the platform profile both read performance.

    900 s FP32 cuBLAS run (gpu-fresh-acpi-accept-89t0okec/measurement900): actual span 900.691 s. 1793 NVML samples cover the span, with a largest gap of 0.645 s. All seven NVML status codes are 0 and AC stayed online. Warmup and end checks found 0 mismatches in 66 sampled outputs each. GEMM and sampler exited 0. SM min/median/max is 1430/2034/2424 MHz. Median throughput is 11,025 GFLOP/s. Maximum GPU temperature is 73 C.

    Throughput follows the reason state, not the SM clock. The same binary and inputs on both boots:

    Run Reason Time SM median Power median GFLOP/s median
    Previous boot (copied DSDT) 0x400 0-296 s 2177 MHz 60.1 W 11,953
    Previous boot (copied DSDT) 0x4 296-901 s 2041 MHz 61.6 W 16,632
    This boot (firmware DSDT) 0x400 0-772 s 2047 MHz 49.6 W 10,898
    This boot (firmware DSDT) 0x4 772-901 s 1839 MHz 47.6 W 14,848

    This boot spent 772 s in the 0x400 state, against 296 s on the previous boot. That is why its full-run median is 33% lower than the previous 16,541 GFLOP/s. Both boots stay far above the old 754 MHz / 2.3 W state. These runs do not show what sets either state, and the sampler did not record memory or other clock domains. No run reached the 3003 MHz maximum that the driver reports.

    Persistence (gpu-fresh-acpi-persist-qaO0qSm0/receipt.json):

    • Hook /etc/initcpio/install/p16-acpi: the DSDT add_file_early line is removed. The SSDT line and all eight allowed kernel versions, including 7.3.0-26, stay. SHA-256 is aa2c2189… before and 03d73c74… after. A test initramfs for 7.3.0-26 has the same early archive list as the booted image, with only kernel/firmware/acpi/SSDT.aml at the pinned SHA-256.
    • Menu /boot/limine.conf: default_entry: 1 is now default_entry: Omarchy/linux-p16-asus-26-fresh-acpi. Only comments changed otherwise. SHA-256 is 2b4cf6cb… before and a308e4b3… after. The image (c539c4c7…) and its BLAKE2b pin are unchanged. The edit ran under the boot-partition lock with pinned hashes.
    • bootctl set-default would not work here. Limine reads LoaderEntryDefault only when default_entry is not set.
    • The new default is not yet observed on a boot.
    • Rollback: copy /boot/limine.conf.pre-fresh-acpi-default back to /boot/limine.conf, and reinstall the hook backup at gpu-fresh-acpi-persist-qaO0qSm0/p16-acpi.before. For one boot, pick "Omarchy (normal)" in the menu.

    Source: PR #19 merged at 492d7e5d (head 3809bedf). 48 offline tests pass.

    Prior art: upstream fixed the same defect in f3d7778 (2026-11-06). Its docs/update-safe.md describes the frozen DSDT that declares GNVS at 0x86f50000, and the same six registered CPU devices (0, 6, 9, 11, 12, 16). Its hook loads only the ASUS SSDT and checks for the kernel's arch_alloc_acpi_tables symbol instead of a version list. Because upstream already documents the defect, no new upstream issue was filed.

  39. seankd3 commented on Nov 7, 2026

    @seankd3
    OwnerAuthor

    Closed: the GPU cap is lifted

    On AC with a sustained load, the GPU ran at 754 MHz before the fix. It now holds about 2,000 MHz and peaks near 2,500 MHz. That matches the Surface Laptop Ultra, which has the same N1X chip.

    The fix: RMSkipACPIBattCap=1, and the firmware's own DSDT instead of the stale copy (PR #19, merged). That copy kept an old GNVS address, so ACPI exposed only 6 of 20 CPUs. The corrected image is the default boot: kernel 7.3.0-26 with NVIDIA 620.70.06.

    Evidence: a 900 s FP32 cuBLAS run, comment 6709116632.

    • SM clock: 1430 / 2034 / 2424 MHz (min / median / max).
    • Throughput: 11,025 GFLOP/s median.
    • Maximum GPU temperature: 73 °C.
    • 0 mismatches. All 20 CPUFreq policies were present before and after the run.

    What still holds the clock below the 3003 MHz table maximum:

    • The SW power cap (reason 0x4), at a median of 49 W.
    • Reason 0x400, which the Surface also shows.

    All NVPCF controls read DEFAULT. Sustained 3 GHz is not a goal for this issue.

    Cleanup: I removed the 620.70.06nopsr DKMS source. It has no AUTOINSTALL line, and it made dkms autoinstall build nothing for new kernels. dkms autoinstall -k 7.3.0-26-omarchy-p16-asus now returns 0, with only 620.70.06 installed.

    The _PSR skip test was inconclusive, and it is not needed now. The upstream thread NVIDIA/open-gpu-kernel-modules#1521 has the data.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions