Skip to content

Commit 350628a

Browse files
MTSistemiclaude
andcommitted
Tuner: GPU governor Performance mode + safe multi-point voltage curve
apps/tuner/skillfish-tuner(+helper) 26.06.5: - New "Governor mode: Balanced / Performance" toggle in the GPU section. Performance lowers the cyan-skillfish-governor load-target (0.08-0.20) + uses a snappier ramp so the GPU holds its top safe-point under any gaming load (still idles to 350 MHz). Default stays Balanced. Wukong benchmark 1080p: 100 -> 111 FPS avg (+11%), 92 -> 102 FPS 5%-low. - Fix BC-250 GPU hard-freeze on clock transitions: write a SMOOTH multi-point voltage curve (350/700, 1500/900, 2000/1000, 2200/1000) instead of a 2-point line; cap GPU max at the validated-stable 2200 MHz @ 1000 mV (2230 @ 1000 mV is undervolted and can hang the box); reload the governor gently (stop -> settle -> start). Helper safe-point parsing is comment-robust + de-duplicated. system/etc/cyan-skillfish-governor/config.toml: new safe default (multi-point, max 2200). docs/OPTIMIZATIONS.md + CHANGELOG: corrected the 2000=2230 claim with the new GPU-bound benchmark data and documented the voltage-curve root cause. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 89a15ed commit 350628a

5 files changed

Lines changed: 120 additions & 10 deletions

File tree

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ All notable changes to SkillFishOS. Dates are ISO-8601.
99
- **Discover-style app pages**: a hero (96 px icon, title, developer, summary, star rating, Install / Remove / Open) over a metadata strip (source · version · size · licence · sandbox + website link), a full-width **screenshot carousel** with arrows and dots, then description, "What's new", permissions and ODRS reviews.
1010
- **Sidebar sub-categories** that expand/collapse under each top category (with a disclosure caret and an "All" entry), matching Discover; clicking the open category collapses it again.
1111
- **In-distro app catalogue (MetaInfo)**: each SkillFishOS app now ships its own `/usr/share/metainfo/*.metainfo.xml` plus local screenshots, so the Hub (and any AppStream client) shows full app pages for **Tuner, AI, Monitor, Kernel Manager and Hub** themselves — bundled into `skillfish-tuner` 26.06.4, `skillfish-ai-panel` 26.06.4, `skillfish-monitor` 26.06.4, `skillfish-kernel-manager` 26.06.1 and `skillfish-hub` 26.06.7.
12+
- **GPU governor "Performance" mode** in the **Tuner** (`skillfish-tuner` 26.06.5): a *Balanced / Performance* toggle in the GPU section. Performance lowers the `cyan-skillfish-governor` load-target so the GPU **holds its top safe-point under any gaming load** (still idling to 350 MHz on the desktop). Measured on the Black Myth: Wukong benchmark (1080p): **100 → 111 FPS average (+11%)**, 92 → 102 FPS on the 5% slowest frames. Default stays Balanced.
1213

1314
### Changed
1415
- **Kernel Switch → Kernel Manager** (`skillfish-kernel-manager`, replaces `skillfish-kernel-switch`): besides choosing the boot kernel (default / boot-once), it now lists every installed kernel with flavour, size and running/default badges, and can **completely uninstall** a kernel (`apt purge` image + headers + modules) so kernels don't pile up. Guardrails: never removes the running kernel or the last remaining one, and moves the GRUB default off a kernel before removing it; a confirmation dialog shows the packages removed and the space freed.
@@ -18,6 +19,7 @@ All notable changes to SkillFishOS. Dates are ISO-8601.
1819
- Hub: three async view-clobber races fixed with a per-view token — a slow **search**, **updates check** or **snap-category** fetch can no longer overwrite the view after the user has navigated elsewhere (e.g. searching right after opening a category now shows the search results, not the category).
1920
- Hub: starting a search now clears any selected category/sub-category highlight (Discover behaviour), and duplicate Flatpak remotes (system + user) are de-duplicated in Sources.
2021
- Cleared all CodeQL code-scanning alerts (file-not-closed, empty-except, unused-import, a duplicate `closeEvent`, and two overly-permissive `chmod`s) across the native apps.
22+
- **GPU hard-freeze on clock transitions** (BC-250): the default governor voltage curve used a 2-point line topping out at **2230 MHz @ 1000 mV**, which is *undervolted* — abrupt clock transitions there could hard-hang the whole machine. The Tuner now writes a **smooth multi-point curve** (`350/700, 1500/900, 2000/1000, 2200/1000`), caps the GPU max at the validated-stable **2200 MHz @ 1000 mV**, and reloads the governor gently (stop → settle → start) to avoid the abrupt SMU jump.
2123

2224
## [26.06 "Aetherium"] — 2026-06-07
2325

apps/tuner/skillfish-tuner

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ from PyQt6.QtCore import Qt, QThread, pyqtSignal, QTimer
77
from PyQt6.QtGui import QIcon, QPainter, QColor, QPen, QBrush, QLinearGradient, QPainterPath, QFont
88
from PyQt6.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
99
QLabel, QGroupBox, QSlider, QPushButton, QToolButton, QCheckBox,
10-
QComboBox, QScrollArea, QMessageBox, QFrame, QGridLayout)
10+
QComboBox, QScrollArea, QMessageBox, QFrame, QGridLayout, QButtonGroup)
1111

1212
HELPER = "/usr/local/bin/skillfish-tuner-helper"
1313
PRESETS = "/usr/share/skillfish/tuner-presets.json"
@@ -40,11 +40,14 @@ HELP = {
4040
L("Soglia termica oltre la quale l'SMU limita la frequenza.\n\nConsigliato 85 °C. Oltre, il sistema riduce automaticamente il clock per proteggere l'hardware.",
4141
"Thermal threshold above which the SMU throttles frequency.\n\nRecommended 85 °C. Above it, the system automatically lowers the clock to protect the hardware.")),
4242
"gpu_freq": (L("Frequenza massima GPU", "Max GPU frequency"),
43-
L("Clock massimo della GPU (governor SMU).\n\n• 1500 = risparmio\n• 2000 = bilanciato\n2230 = massimo supportato\n\nLa GPU sale a questa frequenza solo sotto carico, poi torna a 350 MHz a riposo.",
44-
"Maximum GPU clock (SMU governor).\n\n• 1500 = power saving\n• 2000 = balanced\n2230 = maximum supported\n\nThe GPU reaches this frequency only under load, then returns to 350 MHz at idle.")),
43+
L("Clock massimo della GPU (governor SMU).\n\n• 1500 = risparmio\n• 2000 = bilanciato (sicuro su tutte le schede)\n2200 = massimo stabile a 1000 mV su raffreddamento stock\n\nLa GPU sale a questa frequenza solo sotto carico, poi torna a 350 MHz a riposo. Oltre i 2200 MHz a 1000 mV il BC-250 può bloccarsi: serve più voltaggio e la lotteria del silicio.",
44+
"Maximum GPU clock (SMU governor).\n\n• 1500 = power saving\n• 2000 = balanced (safe on every board)\n2200 = max stable at 1000 mV on stock cooling\n\nThe GPU reaches this frequency only under load, then returns to 350 MHz at idle. Above 2200 MHz at 1000 mV the BC-250 can hard-freeze: it needs more voltage and depends on the silicon lottery.")),
4545
"gpu_volt": (L("Voltaggio massimo GPU", "Max GPU voltage"),
4646
L("Voltaggio associato alla frequenza massima GPU (mV).\n\nPiù alto = più stabile ad alte frequenze ma più caldo. Range 700-1129 mV.",
4747
"Voltage tied to the max GPU frequency (mV).\n\nHigher = more stable at high frequency but hotter. Range 700-1129 mV.")),
48+
"gov_mode": (L("Modalità governor GPU", "GPU governor mode"),
49+
L("Come il governor sceglie il clock GPU sotto carico:\n\n• Bilanciato: alza il clock solo quanto serve — più fresco e silenzioso (default).\n• Performance: tiene la GPU al clock massimo sotto qualsiasi carico di gioco (massimi FPS nei giochi GPU-bound, più calore e consumo). A riposo torna comunque a 350 MHz.\n\nNel benchmark di Black Myth: Wukong, Performance dà circa +12% di FPS medi e +13% sui frame più lenti, senza scendere a compromessi sulla stabilità (usa la via SMU sicura del governor).",
50+
"How the governor picks the GPU clock under load:\n\n• Balanced: raises the clock only as much as needed — cooler and quieter (default).\n• Performance: holds the GPU at max clock under any gaming load (best FPS in GPU-bound games, more heat and power). It still returns to 350 MHz at idle.\n\nIn the Black Myth: Wukong benchmark, Performance gives about +12% average FPS and +13% on the slowest frames, with no stability trade-off (it uses the governor's safe SMU path).")),
4851
"fan": (L("Ventola", "Fan"),
4952
L("Controllo manuale: imposti tu la velocità in %.\nAutomatico: la curva del chip gestisce la velocità in base alla temperatura.\n\nUsa «Test» per sentire la ventola alla velocità scelta per qualche secondo.",
5053
"Manual control: you set the speed in %.\nAutomatic: the chip's curve manages the speed based on temperature.\n\nUse «Test» to hear the fan at the chosen speed for a few seconds.")),
@@ -411,10 +414,24 @@ class TunerWindow(QMainWindow):
411414
# ---------- GPU ----------
412415
def _gpu(self):
413416
g, v = self._section("GPU"); gp = self.cfg.get("gpu", {})
414-
self.gf = Stepper(350, 2230, gp.get("max_mhz", 2230), "MHz")
417+
self.gf = Stepper(350, 2200, min(gp.get("max_mhz", 2200), 2200), "MHz")
415418
self.gv = Stepper(700, 1129, gp.get("max_mv", 1000), "mV")
416419
v.addWidget(self._row(L("Frequenza massima", "Max frequency"), self.gf, "gpu_freq"))
417420
v.addWidget(self._row(L("Voltaggio massimo", "Max voltage"), self.gv, "gpu_volt"))
421+
# governor mode: Balanced (default) / Performance
422+
self.gov_bal = QPushButton(L("Bilanciato", "Balanced"))
423+
self.gov_perf = QPushButton(L("Performance", "Performance"))
424+
_gmg = QButtonGroup(self); _gmg.setExclusive(True)
425+
for _b in (self.gov_bal, self.gov_perf):
426+
_b.setCheckable(True); _b.setCursor(Qt.CursorShape.PointingHandCursor); _gmg.addButton(_b)
427+
(self.gov_perf if gp.get("gov_mode") == "performance" else self.gov_bal).setChecked(True)
428+
self.gov_bal.clicked.connect(lambda: (self.d.cmd(cmd="gov-mode", mode="balanced"),
429+
self.toast(L("Governor: Bilanciato", "Governor: Balanced"))))
430+
self.gov_perf.clicked.connect(lambda: (self.d.cmd(cmd="gov-mode", mode="performance"),
431+
self.toast(L("Governor: Performance — 2230 MHz sotto carico", "Governor: Performance — 2230 MHz under load"))))
432+
_gmw = QWidget(); _gmh = QHBoxLayout(_gmw); _gmh.setContentsMargins(0, 0, 0, 0); _gmh.setSpacing(6)
433+
_gmh.addWidget(self.gov_bal); _gmh.addWidget(self.gov_perf)
434+
v.addWidget(self._row(L("Modalità governor", "Governor mode"), _gmw, "gov_mode"))
418435
vals = lambda: (self.gf.value(), self.gv.value())
419436
v.addWidget(self._btns(
420437
(L("Test (benchmark)", "Test (benchmark)"), lambda: self._test_gpu(*vals()), False),

apps/tuner/skillfish-tuner-helper

Lines changed: 67 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ def get():
7171
gpu["min_mhz"],gpu["min_mv"]=int(pts[0][0]),int(pts[0][1])
7272
gpu["max_mhz"],gpu["max_mv"]=int(pts[-1][0]),int(pts[-1][1])
7373
except Exception: pass # best-effort: governor conf may be missing -> keep defaults
74+
gpu["gov_mode"]=current_gov_mode()
7475
out["gpu"]=gpu
7576
vram={"uma_mb":0}
7677
if os.path.exists(MEMCFG):
@@ -105,12 +106,74 @@ def apply_cpu(mhz,scale,tmp):
105106
def persist_cpu():
106107
sh("python3 %s/bc250_apply.py --install %s"%(OC_DIR,OC_CONF)); sh("systemctl enable bc250-smu-oc.service")
107108

109+
def _gpu_curve(minmhz,minmv,maxmhz,maxmv):
110+
# Build a SMOOTH multi-point voltage curve (gentle clock/voltage transitions).
111+
# The BC-250 SMU can hard-hang on abrupt jumps, so insert validated mid-points
112+
# (1500/900, 2000/1000) between idle and the requested max instead of a 2-point line.
113+
pts=[(int(minmhz),int(minmv))]
114+
for f,v in ((1500,900),(2000,1000)):
115+
if minmhz < f < maxmhz: pts.append((f,v))
116+
pts.append((int(maxmhz),int(maxmv)))
117+
# de-dup by frequency, keep ascending
118+
seen=set(); out=[]
119+
for f,v in pts:
120+
if f in seen: continue
121+
seen.add(f); out.append((f,v))
122+
return out
123+
108124
def apply_gpu(minmhz,minmv,maxmhz,maxmv):
109125
txt=_rd(GOV_CONF)
110-
txt=re.sub(r'(\[\[safe-points\]\]\s*\nfrequency\s*=\s*\d+\s*\nvoltage\s*=\s*\d+\s*\n?)+','',txt)
111-
txt=txt.rstrip()+"\n[[safe-points]]\nfrequency = %d\nvoltage = %d\n[[safe-points]]\nfrequency = %d\nvoltage = %d\n"%(minmhz,minmv,maxmhz,maxmv)
126+
txt=re.sub(r'(\[\[safe-points\]\]\s*\nfrequency[^\n]*\nvoltage[^\n]*\n?)+','',txt)
127+
txt=txt.rstrip()+"\n"
128+
for f,v in _gpu_curve(minmhz,minmv,maxmhz,maxmv):
129+
txt+="[[safe-points]]\nfrequency = %d\nvoltage = %d\n"%(f,v)
112130
_wr(GOV_CONF,txt)
113-
return sh("systemctl restart cyan-skillfish-governor").returncode==0
131+
# gentle reload (stop, settle, start) — avoids SMU hard-hang on abrupt transition
132+
sh("systemctl stop cyan-skillfish-governor"); sh("sleep 2")
133+
return sh("systemctl start cyan-skillfish-governor").returncode==0
134+
135+
# ---------- GPU governor mode (balanced / performance) ----------
136+
# Balanced = upstream load-target (cooler, only clocks up as needed).
137+
# Performance = low load-target band + snappier ramp -> holds the top safe-point
138+
# under any real gaming load (best FPS in GPU-bound titles); still idles to 350.
139+
GOV_BAL = {"sample":2000,"adjust":20000,"normal":1,"burst":200,"bsamples":48,"fadj":100,"upper":"0.95","lower":"0.7"}
140+
GOV_PERF = {"sample":1000,"adjust":10000,"normal":6,"burst":250,"bsamples":16,"fadj":50, "upper":"0.20","lower":"0.08"}
141+
142+
def _gov_safepoints():
143+
try:
144+
pts=re.findall(r'frequency\s*=\s*(\d+)[^\n]*\n\s*voltage\s*=\s*(\d+)', _rd(GOV_CONF))
145+
if len(pts)>=2:
146+
d={}
147+
for f,v in pts: d[int(f)]=int(v) # de-dup by frequency
148+
return sorted(d.items()) # ascending
149+
except Exception: pass # best-effort: keep safe defaults
150+
return [(350,700),(1500,900),(2000,1000),(2200,1000)]
151+
152+
def current_gov_mode():
153+
try:
154+
m=re.search(r'\[load-target\][^\[]*?upper\s*=\s*([0-9.]+)', _rd(GOV_CONF), re.S)
155+
if m and float(m.group(1))<=0.5: return "performance"
156+
except Exception: pass # best-effort
157+
return "balanced"
158+
159+
def gov_mode(mode):
160+
p = GOV_PERF if mode=="performance" else GOV_BAL
161+
sp = _gov_safepoints() # preserve the user's max-freq/voltage safe-points
162+
txt = ("# SkillFishOS Tuner - cyan-skillfish-governor (mode: %s). Managed via the Tuner.\n"
163+
"[timing.intervals]\nsample = %d\nadjust = %d\nfinetune = 1000000000\n\n"
164+
"[timing.ramp-rates]\nnormal = %d\nburst = %d\n\n"
165+
"[timing]\nburst-samples = %d\n\n"
166+
"[frequency-thresholds]\nadjust = %d\nfinetune = 10\n\n"
167+
"[load-target]\nupper = %s\nlower = %s\n"
168+
) % (mode, p["sample"], p["adjust"], p["normal"], p["burst"], p["bsamples"], p["fadj"], p["upper"], p["lower"])
169+
for f,v in sp:
170+
txt += "\n[[safe-points]]\nfrequency = %d\nvoltage = %d\n" % (f,v)
171+
_wr(GOV_CONF, txt)
172+
# Gentle reload: the BC-250 SMU can hard-hang on an abrupt clock transition,
173+
# so stop the governor, let the GPU settle to idle, then start with the new config.
174+
sh("systemctl stop cyan-skillfish-governor")
175+
sh("sleep 2")
176+
return sh("systemctl start cyan-skillfish-governor").returncode==0
114177

115178
def fan_set(mode,pct):
116179
nd=nct_dir()
@@ -295,6 +358,7 @@ def handle(req):
295358
if c=="apply-cpu": return {"ok":apply_cpu(req["mhz"],req["scale"],req["temp"])}
296359
if c=="persist-cpu": apply_cpu(req["mhz"],req["scale"],req["temp"]); persist_cpu(); return {"ok":True}
297360
if c=="apply-gpu": return {"ok":apply_gpu(req["minmhz"],req["minmv"],req["maxmhz"],req["maxmv"])}
361+
if c=="gov-mode": return {"ok":gov_mode(req["mode"]),"mode":current_gov_mode()}
298362
if c=="apply-fan":
299363
ok,rpm=fan_set(req["mode"],req.get("pct",50)); return {"ok":ok,"rpm":rpm}
300364
if c=="set-vram": return {"ok":set_vram(req["mb"]),"reboot":True}

docs/OPTIMIZATIONS.md

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,16 @@ Recipe and patches: [`kernel-build/`](../kernel-build/). Build instructions: [BU
2424

2525
## 2. GPU clock control — the governor
2626

27-
⚠️ The standard amdgpu sysfs (`power_dpm_force_performance_level`) **does not control the BC‑250** — only the SMU does. SkillFishOS uses the [`cyan-skillfish-governor`](https://github.com/Magnap/cyan-skillfish-governor) (Rust), installed as a systemd service with safe‑points in `/etc/cyan-skillfish-governor/config.toml`:
27+
⚠️ The standard amdgpu sysfs (`power_dpm_force_performance_level`) **does not control the BC‑250** — only the SMU does, through the OD voltage curve. SkillFishOS uses the [`cyan-skillfish-governor`](https://github.com/Magnap/cyan-skillfish-governor) (Rust), installed as a systemd service with safe‑points in `/etc/cyan-skillfish-governor/config.toml`.
2828

29-
- Idle **350 MHz**, ramp to a **2000 MHz** safe‑point under load.
30-
- **Why 2000 and not 2230?** In real games (benchmarked with *Black Myth: Wukong*), 2000 and 2230 MHz produce the **same FPS** while 2000 runs cooler with better frame‑time minimums — the GPU clock is **not** the bottleneck (the engine is CPU/draw‑call bound). 2230 is reserved for pure compute (vkpeak/LLM), where it does help.
29+
It is **load‑based**: it settles on the *lowest* frequency that keeps GPU utilisation inside the `[load-target]` band, idling to **350 MHz** when the GPU is unused. Two profiles, switchable from the **SkillFishOS Tuner** (GPU → *Governor mode*):
30+
31+
- **Balanced** (default, band `0.70–0.95`): raises the clock only as much as the workload needs — cooler and quieter.
32+
- **Performance** (band `0.08–0.20` + snappier ramp): holds the **top safe‑point under any gaming load**, for the best FPS in GPU‑bound titles. Still idles to 350 MHz on the desktop.
33+
34+
**Measured (Black Myth: Wukong Benchmark Tool, 1080p):** Balanced ≈ **100 FPS** avg / 92 FPS 5%‑low; Performance ≈ **111 FPS** avg / 102 FPS 5%‑low — **+11%**. (An earlier note claimed 2000 ≈ 2230 MHz gave identical FPS; that held for *gameplay*, which is more CPU/draw‑call bound. The benchmark **flythrough** is heavier and *is* GPU‑bound, so holding a high clock clearly helps there.)
35+
36+
⚠️ **The voltage curve must be a smooth multi‑point ladder — `350/700, 1500/900, 2000/1000, 2200/1000`.** On the BC‑250, **1000 mV is the practical stable ceiling at ~2150–2200 MHz**; **2230 MHz @ 1000 mV is undervolted**, and an abrupt clock transition there can **hard‑freeze the whole machine** (reproduced: a 2‑point `350/700 → 2230/1000` curve hung the box on the load→idle transition, with nothing in the logs). The Tuner therefore caps the max at **2200 MHz** and inserts the mid‑points so transitions are gentle; governor reloads stop → settle → start to avoid the abrupt SMU jump. 2230 needs 1000–1060 mV and the silicon lottery.
3137

3238
Memory bandwidth was *measured* (clpeak/OpenCL) at **~350–367 GB/s** — healthy, not a bottleneck. The `Memory Clock 450 MHz` the driver reports is a reporting convention, not a 1/4 clock. Memory clock is **not** adjustable on the BC‑250.
3339

system/etc/cyan-skillfish-governor/config.toml

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,27 @@
1+
# SkillFishOS default cyan-skillfish-governor config (Balanced).
2+
# Switch to Performance from the SkillFishOS Tuner (GPU section -> Governor mode):
3+
# Performance lowers the load-target so the GPU holds its top safe-point under any
4+
# gaming load (best FPS in GPU-bound titles), while still idling to 350 MHz.
5+
#
6+
# The voltage curve is a SMOOTH multi-point ladder. Do NOT use a 2-point line or a
7+
# 2230 MHz / 1000 mV top point: on the BC-250 (Cyan Skillfish) 1000 mV is the
8+
# practical stable ceiling at ~2150-2200 MHz; 2230 @ 1000 mV is undervolted and an
9+
# abrupt clock transition there can hard-freeze the machine. 2200 @ 1000 mV is the
10+
# validated safe maximum on stock cooling.
11+
12+
# us
113
[timing.intervals]
214
sample = 2000
315
adjust = 20_000
416
finetune = 1_000_000_000
17+
# MHz/ms
518
[timing.ramp-rates]
619
normal = 1
720
burst = 200
21+
# number of samples
822
[timing]
923
burst-samples = 48
24+
# MHz
1025
[frequency-thresholds]
1126
adjust = 100
1227
finetune = 10
@@ -17,5 +32,11 @@ lower = 0.7
1732
frequency = 350
1833
voltage = 700
1934
[[safe-points]]
35+
frequency = 1500
36+
voltage = 900
37+
[[safe-points]]
2038
frequency = 2000
2139
voltage = 1000
40+
[[safe-points]]
41+
frequency = 2200
42+
voltage = 1000

0 commit comments

Comments
 (0)