DEV Community

Cover image for Flattening CPU Heat with Software Scheduling: The Smart Cooling Daemon
logout2login
logout2login

Posted on

Flattening CPU Heat with Software Scheduling: The Smart Cooling Daemon

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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                                      │
└─────────────────────────────────────────────────────────────┘
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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)                    │
└─────────────────────────────────────────────────────────────┘
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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.

Top comments (0)