Flattening CPU Heat with Software Scheduling: The Smart Cooling Daemon
Can you reduce thermal throttling without touching hardware?
Every laptop hits thermal throttling eventually. The CPU gets hot, the frequency drops, performance tanks. The standard fix is better cooling — bigger fans, liquid cooling, phase change pads. But what if you could flatten thermal peaks in software, at the thread-scheduling level?
I built the Smart Cooling Daemon — a C++ program that rotates thread affinity across CPU cores, inserts microsecond-scale cooling windows between compute bursts, and uses a 5-model statistical ensemble to decide which workloads to prune. It's a falsifiable hypothesis: if we give every physical core microsecond cooling windows via software scheduling, can we delay or avoid thermal throttling?
This article covers the architecture, the two implementation branches (CPU-only and SYCL/GPU), the real benchmark results — including null results — and what it takes to build your own.
The Core Idea: Spiral TLL
The key mechanism is Spiral TLL (Thermal Load Leveling) — deterministic thread-affinity rotation around a 4-core ring:
Forward (clockwise): core1 → core2 → core3 → core4 → (repeat)
OS 0 1 2 3
Reverse (anti-clockwise): core1 → core4 → core3 → core2 → (repeat)
OS 0 3 2 1
Between compute bursts, the daemon moves the active thread to the next core in the ring and sleeps for a cooling window. This gives each core a chance to cool down before the next burst hits it.
3-Mode Hysteresis
The daemon dynamically adjusts its behavior based on temperature:
| Mode | Temp Range | Burst Ratio | Behavior |
|---|---|---|---|
| Balanced | < 60°C | 15% | Standard spiral schedule |
| Quiet | 60–75°C | 10% | Reduced compute, more cooling |
| Performance | > 75°C | 25% | Maximum burst, trust cooling windows |
With a ±2.0°C hysteresis deadband to prevent oscillation near thresholds.
The Workload: 1024×1024 Matrix Multiply
The original benchmark was a sin/cos counter that completed in nanoseconds — too light to generate real thermal stress. The current workload is a 1024×1024 blocked double-precision matrix multiplication (~2.1 billion FLOPs per iteration, ~ms per iteration). This is realistic thermal stress — the same kernel that runs in every ML training loop.
Two Branches, Side by Side
The project keeps two implementation branches running in parallel:
| Branch | Dependencies | GPU/DMA | Status |
|---|---|---|---|
| CPU-only | C++17, any compiler | ✗ | ✅ Compiles & runs |
| SYCL | Intel oneAPI (icpx -fsycl) |
✓ Iris Xe GPU + DMA | ✅ Compiles & runs |
CPU-Only Branch
The simplest branch. Pure C++17, no external dependencies. On this machine (Intel i7-2720QM, Sandy Bridge):
[00:56:06.486] Smart Cooling Daemon v2 (CPU-only) started
[00:56:06.487] [Workload] Matrix multiply 1024x1024 double
[00:56:06.487] [Temp] Multi-source: PDH → WMI → IPG → Freq proxy → Fallback
[Temp] 80.05°C [PDH: High Precision Temp]
[Mode] Switched → Cooling-First
[Heartbeat] iter=0 temp=80.05C mode=0 src=PDH
Temperature sensing uses a 4-tier fallback chain:
- PDH Perf Counter — Windows native API, centikelvin precision
-
WMI —
Win32_TemperatureSensor(locked on Tiger Lake+) -
Intel Power Gadget CLI —
ipg.exe --csv - CPU Frequency Proxy — self-calibrating throughput ratio
- Fallback — hardcoded 55°C
SYCL Branch
The GPU branch adds:
- Iris Xe GPU detection via SYCL
- DMA 16 MB H2D/D2H memcpy loop — host-device data transfer pressure
- sin/cos SYCL kernel on GPU
- Temperature-throttled DMA — slows DMA when temperature exceeds threshold
# Build SYCL branch
icpx -fsycl -O3 -std=c++17 -DNDEBUG daemon/smart_cooling_daemon.cpp -o smart_cooling_daemon
Keeping both branches lets you directly compare pure CPU scheduling vs CPU + GPU/DMA closed-loop and determine which mechanism actually contributes to thermal reduction.
VOID 5-Model Ensemble
The daemon includes a VOID (Virtual Operating Intensity Detector) prediction engine — a 5-model ensemble that predicts which nodes are likely to be pruned:
| Model | Description |
|---|---|
| Markov1 | First-order thermal state transition |
| Markov2 | Second-order thermal state transition |
| ExpSmooth | Exponential smoothing of temperature series |
| AR(1) | Autoregressive model of order 1 |
| Base | Simple moving average baseline |
Extended predictors: LSTM and Transformer models available in Python for research.
VOID threshold: 0.85 — nodes above this score are pruned.
The Python bridge works without SYCL:
from daemon.python_sycl_bridge import SYCLBridge, VoidNodeData
bridge = SYCLBridge(use_sycl=False, void_threshold=0.85)
bridge.start()
nodes = [VoidNodeData(fingerprint=i, activation_count=0, epoch=10, max_epochs=100)
for i in range(10)]
scores = bridge.predict(nodes)
# Output: 10 scores, sample: 1.0000
Real Benchmark Results
CPU Overhead: −99.96%
| Metric | Before | After | Improvement |
|---|---|---|---|
| Cooling CPU overhead | 414.3 ms/cycle | 0.17 ms/cycle | −99.96% (≈2400×) |
This is the real win — the cooling mechanism itself became negligible.
Reverse-Direction A/B: NULL RESULT
Testing whether spiral direction matters:
| Config | Δ time | p-value | Result |
|---|---|---|---|
| N=128 interleaved | +0.033 ms | p=0.87 | Null |
| N=192 interleaved | −0.163 ms | p=0.66 | Null |
Direction does not matter. Only reproducible signal is core 2/3 asymmetry (core 2 ≈ 0.2 ms faster).
Dual-Spiral + Home-Swap Control
To decompose direction vs. core effects:
- Direction effect D ≈ 0 (null)
- Core effect C ≈ 0.2 ms (core 2 reproducibly faster than core 3)
The only real signal is a hardware asymmetry, not a scheduling direction.
Falsifiability Window: EMPTY
The theoretical falsification window is empty:
- T_b ∝ N³·¹⁴⁶ (T_b ≤ 28 ms ⇒ N ≤ 281)
- W_set = 32N² (L3 edge ⇒ N ≥ 433)
- 281 < 433 → no N satisfies both constraints
This means the cooling window cannot be small enough to fit in the falsifiability gap. An important negative result.
Thermal Policy: --triggered (trigidle)
Adaptive idle — runs cooling window only when hot, with hysteresis:
| Claim | Status |
|---|---|
| trigidle ≈ fixed at fixed cost | Package trigger saturates (100% fire) → trigidle ≡ fixed |
| Per-core trigger beats fixed | ✅ −14% burst time (session 1), −1.8% (session 2) |
| trigidle ≈ bare ("free lunch") | ❌ Falsified — mean +14–17% from window overhead |
Headroom as frequency (--uclamp) |
✅ Window overhead +14.5% → +1.3%, freq min 1397→2494 MHz |
PI-sized window (--pi-duty) |
✅ Fires 14.4% → 6.6%, overhead +16.7% → +8.1% |
| Host burst on coolest core | ⚪ Net-neutral — Tmax −1°C but Tmean +1.5°C |
| Window shape (2/3/4-core) | ⚪ Near-null — 4-phase ~1°C warmer than 2/3-core |
| Early-stop window | ⚠️ Saves ~26% window but costs ~2.4°C Tmean |
| Faster because it avoids throttling | ❌ Bare does NOT throttle here |
| Thermal headroom at equal median speed | ✅ Robust — −8 to −9°C Tmean vs bare |
Architecture Diagram
┌─────────────────────────────────────────────────────────────┐
│ React Dashboard │
│ (client/ — Vite + React, polls /api/* every 2s) │
├─────────────────────────────────────────────────────────────┤
│ HTTP API Server │
│ (daemon/api_server.py — pure stdlib, localhost:8080) │
│ Routes: /status /nodes /models /benchmark /messages │
├─────────────────────────────────────────────────────────────┤
│ Shared Data Layer │
│ • void_memory_ledger.json (VOID node state) │
│ • ai_shared_data/ (cross-agent messages) │
│ • cooling_benchmark_*.json (benchmark results) │
├─────────────────────────────────────────────────────────────┤
│ C++ Daemons │
│ ┌────────────────────┐ ┌──────────────────────────────┐ │
│ │ smart_cooling_ │ │ smart_cooling_daemon.cpp │ │
│ │ daemon_cpu.cpp │ │ (SYCL + GPU DMA loop) │ │
│ │ (CPU-only, no GPU) │ │ │ │
│ └────────┬────────────┘ └──────────────┬───────────────┘ │
│ │ Shared core │ │
│ │ ┌─────────────────────┐ │ │
│ │ │ Spiral TLL Scheduler│ │ │
│ │ │ 3-Mode Hysteresis │ │ │
│ │ │ Temp Probe Chain │ │ │
│ │ │ VOID Node Pruning │ │ │
│ │ │ Matrix Matmul │ │ │
│ │ └─────────────────────┘ │ │
├───────────┴────────────────────────────────┴─────────────────┤
│ Python Bridge │
│ python_sycl_bridge.py — 5-model ensemble │
│ void_entanglement_extended.py — LSTM + Transformer │
├─────────────────────────────────────────────────────────────┤
│ Hermes Agent │
│ main.py — LangChain agent with VPU tool │
│ vpu.py — VPU parallel reasoning wrapper │
│ soul.md — Agent persona │
└─────────────────────────────────────────────────────────────┘
Design Decisions
Why Matrix Multiplication?
- Compute-bound: O(n³) FLOPs, ~2.1 billion FLOPs per iteration
- Cache-sensitive: Blocked algorithm (block size 64) creates sustained memory bandwidth pressure
- Realistic: GEMM is the fundamental kernel in ML/AI workloads
Why Not Just Use the OS?
The Linux kernel has thermal_throttle and Intel has RAPL. This daemon explores a software-only approach — preemptive scheduling rather than reactive throttling — that could work on any OS without kernel modifications or driver access.
Why Hysteresis?
Temperature sensors have noise and drift. Without hysteresis, the daemon oscillates between modes near thresholds. The ±2.0°C deadband requires a 4°C total swing for a mode change.
Why 5 Models?
No single predictor captures all thermal dynamics. The ensemble approach:
- Markov models — capture discrete state transitions
- Exponential smoothing — captures trends
- AR(1) — captures autocorrelation
- Base (moving average) — provides a stable baseline
Build Instructions
CPU-Only (Simplest)
# Linux (g++)
g++ -O3 -std=c++17 -DNDEBUG daemon/smart_cooling_daemon_cpu.cpp \
-o smart_cooling_daemon_cpu
# Windows (MSVC)
cl /O2 /std:c++17 /DNDEBUG daemon\smart_cooling_daemon_cpu.cpp \
/Fe:smart_cooling_daemon_cpu.exe
# Windows (icpx)
icpx -O3 -std=c++17 -DNDEBUG daemon/smart_cooling_daemon_cpu.cpp \
-o smart_cooling_daemon_cpu.exe
SYCL (GPU)
# Linux
icpx -fsycl -O3 -std=c++17 -DNDEBUG daemon/smart_cooling_daemon.cpp \
-o smart_cooling_daemon
# Windows (needs MSVC + oneAPI)
call "C:\Program Files (x86)\Microsoft Visual Studio\2022\BuildTools\VC\Auxiliary\Build\vcvars64.bat"
call "C:\Program Files (x86)\Intel\oneAPI\setvars.bat" intel64 --force
icpx -fsycl -O3 -std=c++17 -DNDEBUG daemon\smart_cooling_daemon.cpp \
-o smart_cooling_daemon.exe
Run
# CPU daemon (10 heartbeats by default)
./smart_cooling_daemon_cpu
# Full test suite
python daemon/test_sycl_void_system.py
# Benchmark (baseline vs cooling)
python daemon/bench_smart_cooling.py
React Dashboard & API Server
The project includes a real-time monitoring dashboard built with React + Vite that polls the daemon API every 2 seconds.
Architecture
┌─────────────────────────────────────────────────────────────┐
│ React Dashboard │
│ (client/ — Vite + React, polls /api/* every 2s) │
├─────────────────────────────────────────────────────────────┤
│ HTTP API Server │
│ (daemon/api_server.py — pure stdlib, localhost:8080) │
│ Routes: /api/status /api/nodes /api/models │
│ /api/benchmark /api/messages │
├─────────────────────────────────────────────────────────────┤
│ Shared Data Layer │
│ • void_memory_ledger.json (VOID node state) │
│ • ai_shared_data/ (cross-agent messages) │
│ • cooling_benchmark_*.json (benchmark results) │
├─────────────────────────────────────────────────────────────┤
│ C++ Daemons │
│ • smart_cooling_daemon_cpu.exe (CPU-only) │
│ • smart_cooling_daemon.exe (SYCL + GPU) │
└─────────────────────────────────────────────────────────────┘
Dashboard Components
| Component | Description |
|---|---|
| TemperaturePanel | Current temp, mode, status badge |
| TemperatureChart | Live 2-minute temperature history (60 samples) |
| NodeGrid | VOID node ledger — total, active, pruned |
| ModelCards | VOID 5-Model Ensemble info + extended predictors |
| LiveLog | Cross-agent messages from shared data hub |
API Server
The API server is a pure-Python HTTP server (no Flask, no FastAPI) that reads data from the daemon's shared files:
# Start with simulated data (for demo/testing)
python daemon/api_server.py --simulate --port 8080
# Start with real daemon data
python daemon/api_server.py --port 8080
Simulate mode generates realistic temperature oscillation (55°C ± 8°C with 12°C heat bursts every 10 seconds) and mode hysteresis transitions — useful for testing the dashboard without running the actual daemon.
Dashboard Setup
cd client
npm install
npm run dev
# Opens at http://localhost:5173
The Vite dev server proxies /api/* requests to localhost:8080 automatically.
Build for Production
cd client
npm run build
# Output in dist/ — deploy to any static host
Build output:
dist/index.html 0.54 kB
dist/assets/index.css 5.93 kB
dist/assets/index.js 153.99 kB (49.88 kB gzipped)
Two Bugs Fixed
During verification, two bugs were found and fixed:
Unclosed JSX element —
<Dashboard>in App.jsx was missing its closing/>, causing a build failure. Fixed by adding/>before the ternary closes.Missing prop —
tempHistorywas passed to<Dashboard>but not included in the destructured props, causing a runtimeundefinederror. Fixed by addingtempHistoryto the destructured parameters.
Conclusion
The Smart Cooling Daemon is a falsifiable experiment — not a product. Here's what we learned:
- Cooling CPU overhead is negligible (−99.96%) — the mechanism itself doesn't cost performance
- Spiral direction is a null result — forward and reverse are statistically identical
- The falsifiability window is empty — no window size satisfies both constraints
- Per-core trigger beats fixed window (−14% burst time) — real win
-
Headroom harvesting works —
--uclamprecovers speed cost from +14.5% to +1.3% - Thermal headroom is robust — −8 to −9°C Tmean at equal median speed
The daemon is designed to be falsified. If you can run it on your hardware and show it doesn't work, that's valuable data. Open an issue with your results — including null results.
MIT licensed. Code available at github.com/logout2login.
Top comments (0)