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:

  1. PDH Perf Counter — Windows native API, centikelvin precision
  2. WMI — Win32_TemperatureSensor (locked on Tiger Lake+)
  3. Intel Power Gadget CLI — ipg.exe --csv
  4. CPU Frequency Proxy — self-calibrating throughput ratio
  5. 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:

  1. Unclosed JSX element — <Dashboard> in App.jsx was missing its closing />, causing a build failure. Fixed by adding /> before the ternary closes.

  2. Missing prop — tempHistory was passed to <Dashboard> but not included in the destructured props, causing a runtime undefined error. Fixed by adding tempHistory to the destructured parameters.

Conclusion

The Smart Cooling Daemon is a falsifiable experiment — not a product. Here's what we learned:

  1. Cooling CPU overhead is negligible (−99.96%) — the mechanism itself doesn't cost performance
  2. Spiral direction is a null result — forward and reverse are statistically identical
  3. The falsifiability window is empty — no window size satisfies both constraints
  4. Per-core trigger beats fixed window (−14% burst time) — real win
  5. Headroom harvesting works — --uclamp recovers speed cost from +14.5% to +1.3%
  6. 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.