Adjust power management settings for Ryzen CPUs on Linux, Windows, and macOS
Project description
ZenMaster
Overview
ZenMaster adjusts power management settings for AMD Ryzen CPUs and APUs on Linux, Windows, and macOS (AMD Hackintosh). It speaks the same CLI as RyzenAdj, so your existing commands and presets keep working, but you install it with pip and never touch a compiler. Set power limits, temperature limits, VRM currents, clocks, voltages and Curve Optimizer offsets without going near the BIOS.
pip install zenmaster
Reasons to reach for this instead of RyzenAdj:
- Installs with
pip— no cmake, libpci, or building from source - Same
--name=valuesyntax, so scripts and presets carry over unchanged - Uses PawnIO on Windows instead of WinRing0, which has known CVEs
--helplists only the arguments your CPU supports, not every possible option--tableshows a labeled sensor table;--jsonmakes the output scriptable--reapply=Nkeeps your settings applied so other software can't undo them- Works as a Python library —
import zenmaster— on Linux, Windows, and macOS - No mandatory third-party dependencies on any platform
Documentation
Full documentation lives in the Wiki:
| Page | What's in it |
|---|---|
| Installation | Linux, Windows, and macOS setup — ryzen_smu, PawnIO, DirectHW |
| CLI Usage | Every option, examples, JSON output |
| Tuning Arguments | Full argument reference with units |
| PM Table and Monitoring | --table / --dump-table |
| Library API | Embedding ZenMaster in Python |
| Architecture | Internals, SMU protocol, opcode tables |
| Troubleshooting | Fixes for the common problems |
| FAQ | Short answers |
Compatibility
| Platform | Status |
|---|---|
| Linux, Python 3.10+, root | Supported — ryzen_smu module or PCI direct access |
| Windows, Python 3.10+, Administrator | Supported — PawnIO driver |
| macOS (AMD Hackintosh), Python 3.10+, root | Supported — DirectHW.kext, or IOPCIBridge for tuning only |
| Intel | Not supported |
[!NOTE] On Linux, PCI direct access works on most systems without any kernel module.
ryzen_smuis only required when Secure Boot is enabled, since kernel lockdown blocks raw PCI access. Install ryzen_smu ≥ 0.1.7 and enroll the signing key in that case.
[!WARNING] This tool writes directly to the CPU's System Management Unit. Wrong values can cause instability, throttling, or a hard lock. Use at your own risk.
How it compares to RyzenAdj
ZenMaster keeps RyzenAdj's argument names and SMU opcode semantics, so it's a drop-in replacement for most use cases, minus the build step and the WinRing0 driver.
| RyzenAdj | ZenMaster | |
|---|---|---|
| Install | Build from source (cmake, pkg-config, libpci) | pip install zenmaster |
| Language | C | Pure Python 3.10+ |
| Windows driver | WinRing0 ⚠️ | PawnIO ✅ |
--help |
Static — lists every argument | Dynamic — only your CPU's arguments |
| Output | Plain text | Plain text or --json |
| PM table | Raw float dump | Labeled fields with units (--table) |
| Use as a library | Link the C libryzenadj / shell out |
import zenmaster |
| Build dependencies | cmake, make, libpci | None |
| Focus | "Ryzen Mobile Processors" | Ryzen mobile and desktop |
On WinRing0
RyzenAdj's Windows backend uses WinRing0 (OlsApi / OpenLibSys), a driver with well-documented vulnerabilities (CVE-2020-14979, CVE-2021-41285). It hands any unprivileged process full read/write access to physical memory, PCI config space, and I/O ports, and several AV vendors flag it outright.
ZenMaster uses PawnIO instead — a purpose-built, Microsoft-signed kernel driver with a narrow IOCTL interface. No raw physical-memory access, no known CVEs.
Installation
Linux
pip install zenmaster
Requires root, and either the ryzen_smu kernel module or PCI direct access (used automatically when available).
Install ryzen_smu (only needed when Secure Boot is on):
git clone https://github.com/amkillam/ryzen_smu
cd ryzen_smu && make && sudo make install
sudo modprobe ryzen_smu
Apply a preset:
sudo zenmaster --stapm-limit=15000 --fast-limit=20000 --tctl-temp=90
Re-apply every 30 seconds:
sudo zenmaster --stapm-limit=15000 --reapply=30
Windows
- Install PawnIO and reboot.
- Open an Administrator terminal.
pip install zenmaster
zenmaster --stapm-limit=15000 --fast-limit=20000 --tctl-temp=90
macOS (AMD Hackintosh)
Requires root, and one of two access paths:
- DirectHW.kext — full support, including
--table/--sensors. Needs SIP set to allow unsigned kexts (csr-active-config=03080000in Clover/OpenCore). - IOPCIBridge (
--iopci) — no kext, but tuning only, no PM table. Needs thedebug=0x144boot-arg.
pip3 install zenmaster
sudo python3 -m zenmaster --stapm-limit=15000 --fast-limit=20000 --tctl-temp=90
If neither access path is set up, ZenMaster's error tells you which one to add.
sudo zenmaster can fail with "command not found" if the zenmaster script lands somewhere root's PATH doesn't reach (common with pip3 install --user or Homebrew Python). sudo python3 -m zenmaster sidesteps that by going straight through the interpreter.
CLI
zenmaster [OPTIONS] [TUNING ARGS...]
| Option | Description |
|---|---|
--help |
Show the tuning arguments supported by your CPU |
--info |
Detected CPU name, family, socket, active backend, and driver status |
--table |
Live PM table with labeled values |
--sensors |
Key live sensors only — temp, load, power, clocks (compact; structured under --json) |
--dump-table |
Raw PM table floats with hex offsets |
--json |
Machine-readable JSON output |
--reapply=N |
Re-apply settings every N seconds |
--version |
Show the installed version and check PyPI for a newer release |
--iopci |
macOS only: force the kext-free IOPCIBridge path (tuning only) |
Tuning arguments use the same --name=value form as RyzenAdj. Arguments that take no value (--enable-oc, --power-saving, --get-*, …) are passed as bare flags. The --get-* query commands print the value the SMU returns:
$ sudo zenmaster --get-pbo-scalar
get-pbo-scalar [RSMU 0x6D] -> OK = 42 (0x0000002A)
Check what your CPU supports:
$ zenmaster --help
ZenMaster — Ryzen Power Management Tool
Usage: zenmaster [OPTIONS] [TUNING ARGS...]
Tuning arguments for AMD Ryzen 9 7950X (Raphael, AM5_V1):
Power limits:
--stapm-limit=<mW> Sustained Power Limit — STAPM LIMIT
--fast-limit=<mW> Actual Power Limit — PPT LIMIT FAST
--slow-limit=<mW> Average Power Limit — PPT LIMIT SLOW
...
Live PM table (APU / mobile):
$ sudo zenmaster --table
PM Table Version: 0x00450005
+-------------------------+-----------+------------------------+
| STAPM LIMIT | 15.000 | stapm-limit |
| STAPM VALUE | 12.441 | |
| PPT LIMIT FAST | 20.000 | fast-limit |
| THM LIMIT CORE | 90.000 | tctl-temp |
| THM VALUE CORE | 67.125 | |
+-------------------------+-----------+------------------------+
Compact live sensors — --sensors (add --json for a structured object a monitoring script can read directly):
$ sudo zenmaster --sensors
CPU Temp : 64.0 °C
CPU Load : 38.5 %
Socket Power: 41.2 W
iGPU Clock : 2400.0 MHz
Mem Clock : 2400.0 MHz
Library usage
ZenMaster is built to be embedded in tuning utilities, dashboards, and automation tools — including from non-Python apps via the --json CLI.
import zenmaster
from zenmaster import detect, apply, smu
print(zenmaster.__version__)
info = detect()
print(info.name, info.family)
try:
backend = smu.init()
print(backend)
except RuntimeError as e:
print(f"SMU unavailable: {e}")
raise SystemExit(1)
results, rejected = apply("--stapm-limit=15000 --tctl-temp=90", info.family)
for r in results:
print(r["arg"], smu.status_name(r["status"]))
apply("--enable-oc", info.family)
if smu.pm_table_supported(info.family):
data = smu.read_pm_table(info.family)
ver = smu.read_pm_table_version(info.family)
smu.send_mp1(info.family, 0x05, 15000)
smu.send_rsmu(info.family, 0x31, 90)
Checking backend readiness or reading sensors doesn't require hand-rolling PM table decoding:
from zenmaster import smu
smu.ensure_backend() # init(), but returns the backend str or None — never raises
smu.unavailable_reason() # None if usable, else a ready-to-show "why not" message
smu.module_status() # driver verdict: .ok / .version / .min_version / .reason
s = smu.read_pm_sensors(info.family) # read + decode the PM table -> PmSensors | None
if s:
print(s.tctl_temp, s.cclk_busy, s.socket_power, s.gfx_clk)
All of this works the same on Linux, Windows, and macOS, and is re-exported at the top level (from zenmaster import module_status, read_pm_sensors, ...) so apps never need to import from zenmaster.linux directly.
Looking up supported args for a CPU needs no privileges:
from zenmaster import runner
print(runner.get_supported_args("Renoir"))
print(runner.lookup("Renoir", "stapm-limit"))
print(runner.is_flag_arg("enable-oc"))
print(runner.is_flag_arg("stapm-limit"))
Runnable examples live in examples/:
demo.py— a tour of detection, apply, queries, and raw SMU accessmonitor_pmtable.py— live PM-table dump (ported from RyzenAdj'sget_table_values)reapply_loop.py— hold a preset against drift viaread_pm_sensors+apply(ported from RyzenAdj'sget_fast_limitloop)
A few things worth knowing if you're integrating this into your own tool:
- The package ships type hints (
py.typed), so your type checker sees the full API smu.init()raisesBackendUnavailableon failure; SMU calls before init raiseSMUNotInitialized. Both subclassZenMasterError, which subclassesRuntimeError, so you can catch as narrowly or broadly as you likeapply()returnslist[ApplyResult]— each result is a dict with the stable keysarg, value, mailbox, opcode, status, error, returned.errorisNoneon success;returnedholds the value from aget-*query- SMU status codes are
smu.SmuStatus(anIntEnum);smu.status_name(code)gives you a label
from zenmaster import smu, BackendUnavailable
try:
smu.init()
except BackendUnavailable as e:
... # no driver / not root / Secure Boot — the message explains which
Updating
ZenMaster updates through pip:
pip install -U zenmaster
To check whether a newer release is on PyPI without updating:
zenmaster --version
From code, zenmaster.check_update() returns the newer version string, or None if you're already current.
Supported CPUs
Covers first-gen Ryzen (Summit Ridge / Zen 1) through Ryzen 9000 and Strix Halo. Run zenmaster --info to confirm detection and socket mapping.
PM table support (--table): Raven Ridge, Picasso, Dali, Pollock, Renoir, Lucienne, Cezanne/Barcelo, Van Gogh (Steam Deck), Mendocino, Rembrandt, Phoenix Point, Hawk Point, Strix Point, Krackan Point, Strix Halo. On Linux it's read through the ryzen_smu module when loaded, otherwise over PCI direct (/dev/mem, subject to kernel CONFIG_STRICT_DEVMEM). On macOS it needs DirectHW.kext — the kext-free --iopci path can tune but can't read the table.
Requirements
| Linux | Windows | macOS | |
|---|---|---|---|
| Python | 3.10+ | 3.10+ | 3.10+ |
| Privileges | root | Administrator | root (sudo) |
| Driver | ryzen_smu module or PCI direct |
PawnIO | DirectHW.kext, or IOPCIBridge (--iopci, tuning only) |
| Extra deps | None | None | None |
Acknowledgments
| Project | Contribution |
|---|---|
| RyzenAdj | Canonical argument names and SMU opcode semantics |
| UXTU4Linux | SMU opcode tables and Linux backend reference |
| Universal x86 Tuning Utility | Windows PawnIO path and CPU detection approach |
| ryzen_smu | Linux kernel module for SMU access |
| PawnIO | Modern signed Windows kernel driver |
| DirectHW | macOS kext for PCI config and physical memory access (joevt) |
| pciutils | The darwin2 IOPCIBridge method behind the kext-free --iopci path (joevt) |
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file zenmaster-0.5.0.tar.gz.
File metadata
- Download URL: zenmaster-0.5.0.tar.gz
- Upload date:
- Size: 61.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6a30300035cb026a580b24ea6973d4967b65ac18e5e9e32d4c6947379c6e6e2a
|
|
| MD5 |
bed29d7cafe5d9fcfc48d39cb127cc97
|
|
| BLAKE2b-256 |
0b01d3681c455de68e133f18728b1c746c9581fdd598c96752ff653692ba82a9
|
Provenance
The following attestation bundles were made for zenmaster-0.5.0.tar.gz:
Publisher:
publish.yml on HorizonUnix/ZenMaster
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
zenmaster-0.5.0.tar.gz -
Subject digest:
6a30300035cb026a580b24ea6973d4967b65ac18e5e9e32d4c6947379c6e6e2a - Sigstore transparency entry: 2030563112
- Sigstore integration time:
-
Permalink:
HorizonUnix/ZenMaster@f365b39611b13be76b1ecf2d420cbe8c57a7a2f5 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/HorizonUnix
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@f365b39611b13be76b1ecf2d420cbe8c57a7a2f5 -
Trigger Event:
workflow_dispatch
-
Statement type:
File details
Details for the file zenmaster-0.5.0-py3-none-any.whl.
File metadata
- Download URL: zenmaster-0.5.0-py3-none-any.whl
- Upload date:
- Size: 63.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
246ee4cdce628667f3f861eadb25854a0f24434baa62f55951221dc5eeedd36f
|
|
| MD5 |
7d979b12235a7bd1c2747fe07ac95619
|
|
| BLAKE2b-256 |
908eb03cd8e5b8ed5248d8f3469fa0e1471b571d9bc728372944ee959a60e1e1
|
Provenance
The following attestation bundles were made for zenmaster-0.5.0-py3-none-any.whl:
Publisher:
publish.yml on HorizonUnix/ZenMaster
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
zenmaster-0.5.0-py3-none-any.whl -
Subject digest:
246ee4cdce628667f3f861eadb25854a0f24434baa62f55951221dc5eeedd36f - Sigstore transparency entry: 2030563257
- Sigstore integration time:
-
Permalink:
HorizonUnix/ZenMaster@f365b39611b13be76b1ecf2d420cbe8c57a7a2f5 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/HorizonUnix
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@f365b39611b13be76b1ecf2d420cbe8c57a7a2f5 -
Trigger Event:
workflow_dispatch
-
Statement type: