Skip to content

Commit 1e86d72

Browse files
committed
Add workload recommendation engine
1 parent 73873d3 commit 1e86d72

7 files changed

Lines changed: 338 additions & 2 deletions

File tree

README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ Version `v0.2` remains intentionally conservative:
2424
- Reversible sysfs writes with snapshot/restore
2525
- Dry-run support for every profile application
2626
- `doctor`, `explain`, `intel-hwp`, and compare/telemetry-oriented depth commands
27+
- `recommend` advisory engine for workload-aware profile suggestions
2728
- Read-only Intel MSR telemetry decoder with no write path
2829

2930
## Why this exists
@@ -46,6 +47,7 @@ The first release focuses on safe Intel profile application through sysfs:
4647
- `profile` maps high-level policies to existing sysfs knobs
4748
- `doctor` highlights problems, opportunities, and safety constraints before tuning
4849
- `explain` documents profile intent, tradeoffs, and safety limits
50+
- `recommend` suggests a profile, confidence level, warnings, and dry-run next steps without applying changes
4951
- `intel-hwp` reports Intel HWP/EPP exposure and optional read-only telemetry availability
5052
- `msr-read --intel --safe` decodes a small read-only allowlist of Intel MSRs
5153
- `compare` provides a safe benchmark/comparison scaffold without automatic package install
@@ -78,6 +80,10 @@ sudo python3 cpuoptctl/cpuoptctl.py profile latency --allow-idle-tuning
7880
sudo python3 cpuoptctl/cpuoptctl.py profile quiet
7981
sudo python3 cpuoptctl/cpuoptctl.py profile ai-inference
8082
python3 cpuoptctl/cpuoptctl.py doctor
83+
python3 cpuoptctl/cpuoptctl.py recommend
84+
python3 cpuoptctl/cpuoptctl.py recommend --workload kernel-build
85+
python3 cpuoptctl/cpuoptctl.py recommend --workload llama-inference
86+
python3 cpuoptctl/cpuoptctl.py recommend --workload low-latency
8187
python3 cpuoptctl/cpuoptctl.py explain performance
8288
python3 cpuoptctl/cpuoptctl.py intel-hwp
8389
sudo python3 cpuoptctl/cpuoptctl.py msr-read --intel --safe
@@ -113,6 +119,7 @@ See [docs/SAFETY.md](docs/SAFETY.md) for the full model.
113119

114120
```bash
115121
cpuoptctl doctor
122+
cpuoptctl recommend --workload llama-inference
116123
cpuoptctl profile performance --dry-run --diff
117124
cpuoptctl monitor
118125
```
@@ -123,6 +130,7 @@ Demo assets:
123130
- [assets/cpuopt-doctor-demo.txt](C:\Users\ManishKL\Documents\Playground\cpuopt-kernel\assets\cpuopt-doctor-demo.txt)
124131
- [assets/cpuopt-dry-run-demo.txt](C:\Users\ManishKL\Documents\Playground\cpuopt-kernel\assets\cpuopt-dry-run-demo.txt)
125132
- [assets/cpuopt-monitor-demo.txt](C:\Users\ManishKL\Documents\Playground\cpuopt-kernel\assets\cpuopt-monitor-demo.txt)
133+
- [assets/cpuopt-recommend-demo.txt](C:\Users\ManishKL\Documents\Playground\cpuopt-kernel\assets\cpuopt-recommend-demo.txt)
126134

127135
## Example status output
128136

assets/cpuopt-recommend-demo.txt

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
CPUOpt Recommendation
2+
---------------------
3+
Recommended profile: ai-inference
4+
Confidence: high
5+
Workload: llama-inference
6+
7+
Reasons:
8+
- Intel platform with EPP exposure allows profile-aligned policy recommendations.
9+
- AI inference workload maps directly to the ai-inference profile.
10+
- Observed package temperature is about 51 C, suggesting current thermal headroom is acceptable.
11+
12+
Warnings:
13+
- Turbo or boost is currently disabled; recommended profile may underperform until it is re-enabled.
14+
- Laptop-like thermal and fan characteristics detected; quiet or balanced may be safer for sustained use.
15+
- Aggressive profiles on laptop-like systems may reduce thermal headroom.
16+
17+
Suggested dry-run command: python cpuoptctl/cpuoptctl.py profile ai-inference --dry-run --diff
18+
Suggested restore command: python cpuoptctl/cpuoptctl.py restore
19+
20+
Unsupported features:
21+
- none

cpuoptctl/cpuopt_apply.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,12 @@
44
from pathlib import Path
55
from typing import Any
66

7-
from .cpuopt_profiles import ProposedWrite
8-
from .cpuopt_utils import now_utc, read_text, save_json, write_text
7+
try:
8+
from .cpuopt_profiles import ProposedWrite
9+
from .cpuopt_utils import now_utc, read_text, save_json, write_text
10+
except ImportError:
11+
from cpuopt_profiles import ProposedWrite
12+
from cpuopt_utils import now_utc, read_text, save_json, write_text
913

1014

1115
def state_path(state_dir: str) -> Path:

cpuoptctl/cpuopt_recommend.py

Lines changed: 219 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,219 @@
1+
from __future__ import annotations
2+
3+
import json
4+
from pathlib import Path
5+
from typing import Any
6+
7+
8+
def recommend_profile(
9+
discovery: dict[str, Any],
10+
workload: str | None = None,
11+
workload_dir: str | None = None,
12+
) -> dict[str, Any]:
13+
normalized = _normalize_workload(workload)
14+
preset = _load_workload_preset(normalized, workload_dir) if normalized else None
15+
16+
vendor = str(discovery.get("vendor", ""))
17+
policies = discovery.get("policies", [])
18+
has_thermal = bool(discovery.get("thermal", {}).get("thermal_zones"))
19+
has_epp = any(policy.get("energy_performance_preference") for policy in policies)
20+
current_epp = [policy.get("energy_performance_preference") for policy in policies if policy.get("energy_performance_preference")]
21+
turbo_disabled = discovery.get("intel_pstate", {}).get("no_turbo") == "1" or discovery.get("cpufreq_boost") == "0"
22+
deepest_latency = max((state.get("latency") or 0) for state in discovery.get("cpuidle_states", [])) if discovery.get("cpuidle_states") else None
23+
package_temp = _package_temp(discovery)
24+
laptop_like = _is_laptop_like(discovery)
25+
26+
reasons: list[str] = []
27+
warnings: list[str] = []
28+
unsupported: list[str] = []
29+
confidence = "high"
30+
31+
if not has_thermal:
32+
confidence = _lower_confidence(confidence)
33+
warnings.append("Thermal zones are missing; recommendation confidence is reduced.")
34+
35+
profile = "balanced"
36+
if "AuthenticAMD" in vendor:
37+
profile = _recommend_for_amd(normalized)
38+
reasons.append("AMD platform detected; using generic cpufreq and amd-pstate-safe guidance.")
39+
unsupported.append("Intel-specific HWP/EPP recommendations are suppressed on AMD.")
40+
if not discovery.get("amd_pstate", {}).get("exists"):
41+
warnings.append("amd-pstate is not exposed; recommendations stay conservative.")
42+
elif "arm" in str(discovery.get("arch", "")).lower() or "ARM" in vendor or "aarch64" in str(discovery.get("arch", "")):
43+
profile = _recommend_for_arm(normalized)
44+
reasons.append("ARM-style platform detected; using cpufreq/SCMI-style guidance.")
45+
unsupported.append("Intel-specific EPP and HWP recommendations are suppressed on ARM.")
46+
else:
47+
profile = _recommend_for_intel(normalized, has_epp)
48+
if has_epp:
49+
reasons.append("Intel platform with EPP exposure allows profile-aligned policy recommendations.")
50+
else:
51+
warnings.append("EPP is not exposed; recommendations rely more on governors and boost state.")
52+
53+
if normalized in {"llama-inference", "ai-inference"}:
54+
profile = "ai-inference"
55+
reasons.append("AI inference workload maps directly to the ai-inference profile.")
56+
elif normalized == "latency" and "GenuineIntel" in vendor and has_epp:
57+
profile = "latency"
58+
reasons.append("Intel platform with EPP available and latency workload favors the latency profile.")
59+
elif normalized and preset is not None:
60+
preset_profile = preset.get("recommended_profile")
61+
if preset_profile in {"performance", "balanced", "latency", "quiet", "ai-inference"}:
62+
profile = preset_profile
63+
reasons.append(f"Workload preset '{preset.get('name')}' recommends the {preset_profile} profile.")
64+
65+
if laptop_like:
66+
warnings.append("Laptop-like thermal and fan characteristics detected; quiet or balanced may be safer for sustained use.")
67+
if profile in {"performance", "latency", "ai-inference"}:
68+
warnings.append("Aggressive profiles on laptop-like systems may reduce thermal headroom.")
69+
70+
if turbo_disabled and profile in {"performance", "latency", "ai-inference"}:
71+
warnings.append("Turbo or boost is currently disabled; recommended profile may underperform until it is re-enabled.")
72+
73+
if deepest_latency is not None and deepest_latency >= 100 and profile == "latency":
74+
reasons.append("Deepest C-state latency is high enough that optional idle tuning may help latency-sensitive workloads.")
75+
76+
if package_temp is not None and package_temp >= 80000:
77+
confidence = _lower_confidence(confidence)
78+
warnings.append("Thermal headroom appears limited; sustained high-performance profiles may not hold.")
79+
elif package_temp is not None:
80+
reasons.append(f"Observed package temperature is about {package_temp / 1000:.0f} C, suggesting current thermal headroom is acceptable.")
81+
82+
optimal_epp = _optimal_epp_for_profile(profile)
83+
if has_epp and current_epp and all(value == optimal_epp for value in current_epp if optimal_epp is not None):
84+
reasons.append("Current EPP already matches the recommended profile intent; no changes may be needed.")
85+
86+
if normalized is None:
87+
reasons.append("No workload preset was specified; recommendation is based on current platform capabilities and safety posture.")
88+
89+
return {
90+
"recommended_profile": profile,
91+
"confidence": confidence,
92+
"reasons": reasons,
93+
"warnings": warnings,
94+
"suggested_dry_run_command": f"python cpuoptctl/cpuoptctl.py profile {profile} --dry-run --diff",
95+
"suggested_restore_command": "python cpuoptctl/cpuoptctl.py restore",
96+
"unsupported_features": unsupported,
97+
"workload": normalized,
98+
}
99+
100+
101+
def format_recommendation(report: dict[str, Any]) -> str:
102+
lines = ["CPUOpt Recommendation", "---------------------"]
103+
lines.append(f"Recommended profile: {report.get('recommended_profile')}")
104+
lines.append(f"Confidence: {report.get('confidence')}")
105+
if report.get("workload"):
106+
lines.append(f"Workload: {report.get('workload')}")
107+
lines.append("")
108+
lines.append("Reasons:")
109+
for reason in report.get("reasons", []):
110+
lines.append(f"- {reason}")
111+
lines.append("")
112+
lines.append("Warnings:")
113+
for warning in report.get("warnings", []):
114+
lines.append(f"- {warning}")
115+
if not report.get("warnings"):
116+
lines.append("- none")
117+
lines.append("")
118+
lines.append(f"Suggested dry-run command: {report.get('suggested_dry_run_command')}")
119+
lines.append(f"Suggested restore command: {report.get('suggested_restore_command')}")
120+
lines.append("")
121+
lines.append("Unsupported features:")
122+
for item in report.get("unsupported_features", []):
123+
lines.append(f"- {item}")
124+
if not report.get("unsupported_features"):
125+
lines.append("- none")
126+
return "\n".join(lines)
127+
128+
129+
def _normalize_workload(workload: str | None) -> str | None:
130+
if not workload:
131+
return None
132+
value = workload.strip().lower()
133+
aliases = {
134+
"low-latency": "latency",
135+
"low-latency-trading": "low-latency-trading",
136+
"llama-inference": "llama-inference",
137+
"ai-inference": "ai-inference",
138+
"kernel-build": "kernel-build",
139+
"low-latency": "latency",
140+
}
141+
return aliases.get(value, value)
142+
143+
144+
def _load_workload_preset(workload: str, workload_dir: str | None) -> dict[str, Any] | None:
145+
if workload_dir is None:
146+
workload_dir = str(Path(__file__).resolve().parents[1] / "examples" / "workloads")
147+
candidate = Path(workload_dir) / f"{workload}.json"
148+
if not candidate.exists():
149+
return None
150+
try:
151+
return json.loads(candidate.read_text(encoding="utf-8"))
152+
except (OSError, json.JSONDecodeError):
153+
return None
154+
155+
156+
def _recommend_for_intel(workload: str | None, has_epp: bool) -> str:
157+
if workload == "kernel-build":
158+
return "performance"
159+
if workload in {"latency", "low-latency-trading"}:
160+
return "latency" if has_epp else "performance"
161+
if workload in {"llama-inference", "ai-inference"}:
162+
return "ai-inference"
163+
if workload == "laptop-quiet":
164+
return "quiet"
165+
return "balanced"
166+
167+
168+
def _recommend_for_amd(workload: str | None) -> str:
169+
if workload in {"llama-inference", "ai-inference", "kernel-build"}:
170+
return "balanced"
171+
if workload in {"latency", "low-latency-trading"}:
172+
return "latency"
173+
if workload == "laptop-quiet":
174+
return "quiet"
175+
return "balanced"
176+
177+
178+
def _recommend_for_arm(workload: str | None) -> str:
179+
if workload in {"llama-inference", "ai-inference"}:
180+
return "balanced"
181+
if workload in {"latency", "low-latency-trading"}:
182+
return "latency"
183+
if workload == "laptop-quiet":
184+
return "quiet"
185+
return "balanced"
186+
187+
188+
def _lower_confidence(confidence: str) -> str:
189+
order = ["low", "medium", "high"]
190+
index = order.index(confidence)
191+
return order[max(0, index - 1)]
192+
193+
194+
def _package_temp(discovery: dict[str, Any]) -> int | None:
195+
zones = discovery.get("thermal", {}).get("thermal_zones", [])
196+
if not zones:
197+
return None
198+
hottest = max(zones, key=lambda zone: zone.get("temp") or -1)
199+
return hottest.get("temp")
200+
201+
202+
def _is_laptop_like(discovery: dict[str, Any]) -> bool:
203+
model = str(discovery.get("model_name") or "").lower()
204+
fan_present = any(
205+
any(name.startswith("fan") for name in device.get("sensors", {}))
206+
for device in discovery.get("hwmon", [])
207+
)
208+
return fan_present and any(token in model for token in ("core(tm)", "ultra", "mobile", "laptop"))
209+
210+
211+
def _optimal_epp_for_profile(profile: str) -> str | None:
212+
mapping = {
213+
"performance": "performance",
214+
"balanced": "balance_performance",
215+
"latency": "performance",
216+
"quiet": "power",
217+
"ai-inference": "performance",
218+
}
219+
return mapping.get(profile)

cpuoptctl/cpuoptctl.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
from .cpuopt_explain import format_profile_explanation
1414
from .cpuopt_msr import decode_intel_msrs, format_msr_report
1515
from .cpuopt_profiles import ProposedWrite, propose_profile
16+
from .cpuopt_recommend import format_recommendation, recommend_profile
1617
from .cpuopt_telemetry import collect_sample, monitor
1718
from .cpuopt_utils import json_ready, now_utc, read_text, save_json
1819
except ImportError:
@@ -23,6 +24,7 @@
2324
from cpuopt_explain import format_profile_explanation
2425
from cpuopt_msr import decode_intel_msrs, format_msr_report
2526
from cpuopt_profiles import ProposedWrite, propose_profile
27+
from cpuopt_recommend import format_recommendation, recommend_profile
2628
from cpuopt_telemetry import collect_sample, monitor
2729
from cpuopt_utils import json_ready, now_utc, read_text, save_json
2830

@@ -205,6 +207,14 @@ def cmd_compare(args: argparse.Namespace) -> int:
205207
return 0
206208

207209

210+
def cmd_recommend(args: argparse.Namespace) -> int:
211+
_resolve_common_args(args)
212+
data = discover(sysfs_root=args.sysfs_root)
213+
report = recommend_profile(data, workload=args.workload, workload_dir=args.workload_dir)
214+
print(json.dumps(report, indent=2) if args.json else format_recommendation(report))
215+
return 0
216+
217+
208218
def cmd_restore(args: argparse.Namespace) -> int:
209219
_resolve_common_args(args)
210220
sp = state_path(args.state_dir)
@@ -326,6 +336,13 @@ def build_parser() -> argparse.ArgumentParser:
326336
compare.add_argument("--json", action="store_true")
327337
_add_common_local_args(compare)
328338
compare.set_defaults(func=cmd_compare)
339+
340+
recommend = subparsers.add_parser("recommend")
341+
recommend.add_argument("--workload")
342+
recommend.add_argument("--workload-dir")
343+
recommend.add_argument("--json", action="store_true")
344+
_add_common_local_args(recommend)
345+
recommend.set_defaults(func=cmd_recommend)
329346
return parser
330347

331348

docs/ROADMAP.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
- telemetry and benchmark comparison
1515
- doctor and explain surfaces
16+
- recommendation engine and workload-aware advisory output
1617
- dry-run diff visualization
1718
- Intel HWP reporting and read-only MSR telemetry
1819
- workload presets and demo assets

0 commit comments

Comments
 (0)