We ran the full OSWorld test set (all 10 desktop-app categories) through Holo3-35B-A3B on a fanless Intel Core Ultra 7 356H mini-PC and metered energy with Intel RAPL at three granularities — per task, per agent step, and per computation vs. data movement — then re-ran the workload on the iGPU and NPU. The starting hypothesis: moving data between DRAM and the processors dominates the energy.
RAPL psys/package/core/dramllama.cpp Q4_K_M OpenVINO 2026.0 iGPU+NPUno root · powercap sysfs run 2026-07-05
The only reliable energy instrument on this box (no root ⇒ turbostat,
perf, intel_gpu_top are all blocked) is the powercap RAPL
sysfs — which is the gold standard anyway and, crucially, exposes a separate
dram domain. We read five domains: psys (whole platform),
package-0 (CPU chip), core, uncore (iGPU/fabric),
and dram (the DIMMs). Energy is Δenergy_uj across each work
window; power traces are sampled at 10–20 Hz with a background thread that also
logs iGPU frequency and NPU busy-time. Idle baselines (psys 19.5 W) are subtracted
so the fixed platform draw is separated from the work.
Data sources: (A) a complete 141-task OSWorld run with per-step, per-domain energy
already on disk; (M2) STREAM + variable-arithmetic-intensity microkernels in C
(-O3 -march=native, OpenMP); (M3/M4) identical matmul + memory workloads
on CPU/iGPU/NPU via OpenVINO, plus a real transformer (Qwen2.5-1.5B) on CPU vs. iGPU
via llama.cpp Vulkan; (M5) the full Holo3-35B loaded onto the iGPU (all 21 GB,
-ngl 99, after freeing ~54 GB) and metered on CPU vs. iGPU across seven
OSWorld task sizes. Every offload number below is a direct measurement, not a projection.
Across 141 completed tasks the agent spent 132 hours in the model for 135 hours of wall time — 98% of the run is inference, not the desktop environment. That is the macro-inefficiency: an average desktop task costs 70 Wh and ~56 minutes of compute. The counter-intuitive first result is that the dram domain — the literal DRAM chips your data moves to and from — draws under 1 W even at full tilt and is only 1.4% of the energy. If "data movement" meant "DRAM-device power," the hypothesis would be dead on arrival. It isn't — see §3.
| category | tasks | steps | Wh | kJ/step | DRAM% |
|---|---|---|---|---|---|
| multi_apps | 12 | 258 | 1370 | 19.1 | 1.3 |
| libreoffice_calc | 13 | 386 | 2011 | 18.8 | 1.3 |
| vs_code | 12 | 183 | 942 | 18.5 | 1.3 |
| chrome | 32 | 535 | 2593 | 17.4 | 1.4 |
| vlc | 12 | 175 | 702 | 14.4 | 1.4 |
| libreoffice_writer | 11 | 147 | 588 | 14.4 | 1.4 |
| thunderbird | 12 | 132 | 517 | 14.1 | 1.4 |
| libreoffice_impress | 13 | 150 | 579 | 13.9 | 1.4 |
| os | 11 | 81 | 263 | 11.7 | 1.4 |
| gimp | 13 | 89 | 278 | 11.3 | 1.4 |
Step energy is integrated from the consistent per-step profiles (core ≤ package ≤ psys). The as-shipped per-task summary field under-counts psys on ~10% of tasks and was not used.
Two inference phases sit on opposite sides of the roofline: prefill reads the 8k-token prompt and is compute-bound (weights reused across many tokens); decode generates one token at a time and is memory-bound — every token streams the model's active weights from DRAM once.
core, not dram.
dram domain but 768 pJ at
the platform (~20×) — because slow movement keeps the whole ~20 W platform awake longer.
Caveat: on this client SoC the RAPL dram domain is a
partial/modeled estimate of DRAM access energy (it excludes refresh, I/O, termination and
the on-die memory-controller/PHY, which fold into package), so the 1.4% and
39 pJ/byte are lower bounds on true memory-system energy — which strengthens, not weakens,
the point that movement cost is hidden outside the dram meter.
So why isn't decode the top energy line? Because the OSWorld agent re-sends a ~8,000-token prompt (three screenshots + accessibility tree) every step but generates only ~284 tokens — a 28.5× prefill:decode token ratio. Prefill is cheap per token yet enormous in aggregate (~63% of energy); decode is expensive per token but small in volume (~10%).
The decisive test: run the full Holo3-35B on the iGPU. We freed ~54 GB, loaded all
21 GB of the model onto the Xe3 via llama.cpp Vulkan (-ngl 99), and metered
prefill + decode energy across seven OSWorld task sizes on CPU vs iGPU.
Why 4.1×, and not more? Two supporting probes bracket the ceiling:
The iGPU's raw compute is ~35× more efficient per FLOP; a 1.5B dense model captures 8.6× of that. But Holo3-35B is a Mixture-of-Experts, and the Vulkan MoE prefill tops out near CPU speed — so the realized energy win on the actual deployed model is 4.1×, earned entirely by freeing the cores, not by going faster. The win grows as the backend matures.
| engine | GFLOP/s | pJ/FLOP (compute) | vs CPU energy | core W during work | real-model prefill mJ/tok |
|---|---|---|---|---|---|
| CPU · 16C AVX2 | 474 | 93.2 | 1× | 40.5 | 311 |
| iGPU · Xe3 | 11,951 | 2.84 | 33× | 5.9 | 36 |
| NPU · AI Boost | 11,408 | 2.61 | 36× | 2.9 | — |
The synthetic 33×/36× compares iGPU fp16 / NPU int8 against CPU fp32 (each engine's native precision) — a device+precision figure, the upper bound on the device-alone gain. The real deployed model realizes less: full Holo3-35B (MoE) prefill on the iGPU is 4.1× (≈700 → ≈170 mJ/tok); a 1.5B dense model gets 8.6×. NPU was not run on the GGUF models (llama.cpp targets CPU/Vulkan). The "real-model prefill" column below is the 1.5B point.
Ranked by measured leverage against the 9.8 kWh total:
| # | lever | attacks | share | mechanism / measured basis |
|---|---|---|---|---|
| 1 | Shrink the re-sent prompt (downscale/crop screenshots, prune the a11y tree) + cache the stable head | repeated prefill | ≤63% | 8,078 prompt tokens re-encoded every step. The screenshot + a11y tree is fresh each step (the screen changes after each action), so prefix caching only removes the stable head (system + task) — a fraction of the 63%; the bigger lever is making the per-step prompt smaller. |
| 2 | Offload prefill to iGPU / NPU | prefill compute | 63% | Prefill is compute-bound ⇒ 4.1× less energy on the full Holo3-35B (8.6× on a 1.5B dense model; 33× synthetic ceiling), and frees the cores (58→1.6 W). Keep decode on the CPU. |
| 3 | Race-to-idle | platform tax | 27% | A fixed ~20 W (VRM/rails/I/O) is billed for every second the box is awake. Levers 1–2 shorten wall time ⇒ shrink the fixed-power × time integral directly. |
| 4 | Cut bytes/token (lower-bit quant, fewer active experts, spec-decode) | decode | 10% | Decode is memory-bound; offload barely helps (shared DRAM). The lever that works is moving fewer bytes per token — decode pays a 4.6×/token premium (11.8× in the DRAM domain) precisely because it re-streams the active weights each token. |
Honesty notes. (1) uncore reads ~0 during CPU inference — on Panther
Lake the memory-controller/PHY energy folds into package, so the on-chip
data-movement cost sits inside the core/package figures, not
separately metered; and the dram domain is a partial/modeled client-SoC
estimate, so the 1.4% share is a lower bound. (2) The phase split is an OLS regression on
2,136 real steps (R²=0.96), with prefill work defined as
max(0, prompt_tokens − cached_tokens) (tokens actually re-encoded). It is
predictor-dependent: prefill lands at 56–74% and fixed at 16–34% depending
on whether cached tokens are counted; decode ~10% is robust across definitions,
and the intercept independently matches idle-power × inference-time. (3) Per-domain
watts are raw (busy); pJ/byte and pJ/FLOP are net of the idle
baseline. (4) The accelerator 33×/36× is fp16/int8 vs fp32 (device+precision); the
real-model figures are 4.1× (full Holo3-35B MoE) and 8.6× (1.5B dense). (5) The
full 35B iGPU run is text-only (no vision projector — mmproj is not yet
Vulkan-compatible, so the vision-encode energy is characterized on CPU only, via full361);
prefill prompts are token-matched to real OSWorld sizes (this is the robust, headline
comparison). Decode is a weaker probe: greedy generation hit EOS after only ~17 tokens on
the short prompt, so the decode energy ratio (~1.9×) carries real uncertainty — the
firm signal that decode belongs on the CPU is the clean 2.5× throughput deficit
(server-side timing), and the absolute decode energy is a lower bound vs a full 8k KV cache. (6) Absolute totals use the consistent per-step
integration; the shipped per-task energy_j field under-counts psys and was
discarded.
2026-07-05_osworld-energy-profile ·
Xuming Huang