<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Joulie</title><link>https://joulie-k8s.github.io/Joulie/versions/v0.2.4/</link><description>Recent content on Joulie</description><generator>Hugo</generator><language>en-us</language><atom:link href="https://joulie-k8s.github.io/Joulie/versions/v0.2.4/index.xml" rel="self" type="application/rss+xml"/><item><title>Core Concepts</title><link>https://joulie-k8s.github.io/Joulie/versions/v0.2.4/docs/getting-started/core-concepts/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://joulie-k8s.github.io/Joulie/versions/v0.2.4/docs/getting-started/core-concepts/</guid><description>&lt;p&gt;Before installing Joulie, understand the control model.&lt;/p&gt;
&lt;h2 id="what-joulie-is"&gt;What Joulie is&lt;/h2&gt;
&lt;p&gt;Joulie is a Kubernetes-native energy management system that uses &lt;strong&gt;per-node digital twins&lt;/strong&gt; to optimize data center power consumption.&lt;/p&gt;
&lt;p&gt;It continuously ingests telemetry from every node (CPU/GPU power draw via RAPL and NVML/DCGM, read from Prometheus as the OpenTelemetry hardware metrics &lt;code&gt;hw.energy&lt;/code&gt; and &lt;code&gt;hw.power&lt;/code&gt; or as &lt;a href="https://github.com/sustainable-computing-io/kepler"&gt;Kepler&lt;/a&gt; energy counters, and per-pod resource utilization via cAdvisor) to maintain an up-to-date model of each node&amp;rsquo;s thermal and power state.&lt;/p&gt;</description></item><item><title>Quickstart</title><link>https://joulie-k8s.github.io/Joulie/versions/v0.2.4/docs/getting-started/quickstart/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://joulie-k8s.github.io/Joulie/versions/v0.2.4/docs/getting-started/quickstart/</guid><description>&lt;p&gt;This page is the fastest path to run Joulie.
For conceptual context first, read &lt;a href="https://joulie-k8s.github.io/Joulie/versions/v0.2.4/docs/getting-started/core-concepts/"&gt;Core Concepts&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="prerequisites"&gt;Prerequisites&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Kubernetes cluster with worker nodes&lt;/li&gt;
&lt;li&gt;Node Feature Discovery (NFD) deployed&lt;/li&gt;
&lt;li&gt;Optional for real enforcement: nodes exposing writable power interfaces
&lt;ul&gt;
&lt;li&gt;RAPL power limit files, or&lt;/li&gt;
&lt;li&gt;cpufreq sysfs interfaces&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Prometheus, for measured node power. The chart reads node power from Prometheus by default (&lt;code&gt;NODE_POWER_SOURCE=prometheus&lt;/code&gt;): it expects Prometheus at &lt;code&gt;http://prometheus-operated.monitoring:9090&lt;/code&gt; with its OTLP receiver on (&lt;code&gt;--web.enable-otlp-receiver&lt;/code&gt;), receiving &lt;code&gt;hw.energy&lt;/code&gt; or &lt;code&gt;hw.power&lt;/code&gt; for each node. Without that data every node reads 0 W, with source &lt;code&gt;prometheus-no-data&lt;/code&gt;.
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;values/prometheus-grafana.yaml&lt;/code&gt; configures kube-prometheus-stack this way (&lt;a href="https://joulie-k8s.github.io/Joulie/versions/v0.2.4/docs/architecture/telemetry/#prometheus-settings"&gt;Prometheus settings&lt;/a&gt;).&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/joulie-k8s/Joulie/tree/main/examples/11-otel-collector-bridge"&gt;&lt;code&gt;examples/11-otel-collector-bridge&lt;/code&gt;&lt;/a&gt; converts Kepler, dcgm-exporter and AMD device-metrics-exporter metrics into those names.&lt;/li&gt;
&lt;li&gt;To run without it, set &lt;code&gt;controllerManager.env.NODE_POWER_SOURCE=static&lt;/code&gt; (0 W for every node).&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="install-from-release-recommended"&gt;Install from release (recommended)&lt;/h2&gt;
&lt;p&gt;Install directly from OCI chart release:&lt;/p&gt;</description></item><item><title>Pod Compatibility for Joulie</title><link>https://joulie-k8s.github.io/Joulie/versions/v0.2.4/docs/getting-started/workload-compatibility/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://joulie-k8s.github.io/Joulie/versions/v0.2.4/docs/getting-started/workload-compatibility/</guid><description>&lt;p&gt;Joulie uses a single pod annotation to express workload placement intent:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;joulie.io/workload-class: performance | standard
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The scheduler extender reads this annotation and steers pods accordingly. No node affinity rules are needed.&lt;/p&gt;
&lt;h2 id="workload-classes"&gt;Workload classes&lt;/h2&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Class&lt;/th&gt;
 &lt;th&gt;Behavior&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;performance&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Must run on full-power nodes. The extender hard-rejects eco nodes.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;standard&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Default. Can run on any node. Adaptive scoring steers toward eco when performance nodes are congested.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;If no annotation is present, the extender treats it as &lt;code&gt;standard&lt;/code&gt;.&lt;/p&gt;</description></item><item><title>Agent Runtime Modes</title><link>https://joulie-k8s.github.io/Joulie/versions/v0.2.4/docs/getting-started/daemonset/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://joulie-k8s.github.io/Joulie/versions/v0.2.4/docs/getting-started/daemonset/</guid><description>&lt;p&gt;The chart runs the agent in one of three modes, or not at all (&lt;code&gt;agent.mode&lt;/code&gt;):&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;&lt;code&gt;agent.mode&lt;/code&gt;&lt;/th&gt;
 &lt;th&gt;Pods&lt;/th&gt;
 &lt;th&gt;Use&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;daemonset&lt;/code&gt; (default)&lt;/td&gt;
 &lt;td&gt;one pod per managed node (&lt;code&gt;charts/joulie/templates/agent-daemonset.yaml&lt;/code&gt;)&lt;/td&gt;
 &lt;td&gt;real hardware&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;pool&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;a StatefulSet whose pods each run many logical per-node agents (&lt;code&gt;agent-statefulset.yaml&lt;/code&gt;)&lt;/td&gt;
 &lt;td&gt;KWOK experiments over the simulator&amp;rsquo;s HTTP API&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;emulated&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;one Deployment per node of the simulated cluster (&lt;code&gt;agent-emulated.yaml&lt;/code&gt;), reading that node&amp;rsquo;s emulated &lt;code&gt;/sys&lt;/code&gt; and GPU tools&lt;/td&gt;
 &lt;td&gt;the &lt;a href="https://joulie-k8s.github.io/Joulie/versions/v0.2.4/docs/simulator/simulated-cluster/"&gt;simulated cluster&lt;/a&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;none&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;no agent&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id="daemonset-mode-real-hardware"&gt;DaemonSet mode (real hardware)&lt;/h2&gt;
&lt;h3 id="required-runtime-settings"&gt;Required runtime settings&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;securityContext.privileged: true&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Host mount:
&lt;ul&gt;
&lt;li&gt;host path &lt;code&gt;/sys&lt;/code&gt; -&amp;gt; container path &lt;code&gt;/host-sys&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Env:
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;NODE_NAME&lt;/code&gt; from &lt;code&gt;spec.nodeName&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;AGENT_MODE=daemonset&lt;/code&gt; (default)&lt;/li&gt;
&lt;li&gt;optional &lt;code&gt;RECONCILE_INTERVAL&lt;/code&gt; (default &lt;code&gt;20s&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;optional &lt;code&gt;SIMULATE_ONLY=true&lt;/code&gt; (skip host writes, log requested actions)&lt;/li&gt;
&lt;li&gt;optional &lt;code&gt;METRICS_ADDR&lt;/code&gt; (default &lt;code&gt;:8080&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="pool-mode-kwok--simulation"&gt;Pool mode (KWOK / simulation)&lt;/h2&gt;
&lt;p&gt;Pool mode preserves per-node semantics but shards logical node controllers across replicas.&lt;/p&gt;</description></item><item><title>CPU Support and Power Capping</title><link>https://joulie-k8s.github.io/Joulie/versions/v0.2.4/docs/hardware/cpus/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://joulie-k8s.github.io/Joulie/versions/v0.2.4/docs/hardware/cpus/</guid><description>&lt;p&gt;Joulie supports node-level CPU power capping through &lt;code&gt;NodeTwin&lt;/code&gt; intents enforced by the agent.&lt;/p&gt;
&lt;h2 id="contract-model"&gt;Contract model&lt;/h2&gt;
&lt;p&gt;CPU intent is defined in &lt;code&gt;NodeTwin.spec.cpu&lt;/code&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;packagePowerCapWatts&lt;/code&gt; (optional absolute cap)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;packagePowerCapPctOfMax&lt;/code&gt; (optional normalized profile intent)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Precedence:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;packagePowerCapWatts&lt;/code&gt; if present&lt;/li&gt;
&lt;li&gt;otherwise &lt;code&gt;packagePowerCapPctOfMax&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="policy-behavior"&gt;Policy behavior&lt;/h2&gt;
&lt;p&gt;Controller manager profile assignment remains &lt;code&gt;performance&lt;/code&gt; vs &lt;code&gt;eco&lt;/code&gt;.
CPU cap values are generated per profile and written into &lt;code&gt;NodeTwin.spec&lt;/code&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;performance profile typically maps to a higher cap (often 100%)&lt;/li&gt;
&lt;li&gt;eco profile maps to a lower cap&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For heterogeneous nodes, percentage-based intent remains useful because each node resolves normalized intent using node-local capabilities.
If percentage intent cannot be converted to watts (for example missing RAPL range), the agent applies a DVFS percent fallback path when possible.&lt;/p&gt;</description></item><item><title>CRD and Policy Model</title><link>https://joulie-k8s.github.io/Joulie/versions/v0.2.4/docs/architecture/policy/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://joulie-k8s.github.io/Joulie/versions/v0.2.4/docs/architecture/policy/</guid><description>&lt;p&gt;This page defines Joulie&amp;rsquo;s core contract:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;demand&lt;/strong&gt; comes from pod scheduling constraints,&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;supply&lt;/strong&gt; is exposed by node power-profile labels,&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;discovered hardware&lt;/strong&gt; is published through &lt;code&gt;NodeHardware&lt;/code&gt;,&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;desired state&lt;/strong&gt; is published through &lt;code&gt;NodeTwin&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="apis"&gt;APIs&lt;/h2&gt;
&lt;p&gt;Group/version:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;joulie.io/v1alpha1&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;CRDs:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;NodeHardware&lt;/code&gt; (&lt;code&gt;nodehardwares&lt;/code&gt;, cluster-scoped)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;NodeTwin&lt;/code&gt; (&lt;code&gt;nodetwins&lt;/code&gt;, cluster-scoped)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;CRD definitions live in:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;config/crd/bases/joulie.io_nodehardwares.yaml&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;config/crd/bases/joulie.io_nodetwins.yaml&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="demand-model-workloads"&gt;Demand model (workloads)&lt;/h2&gt;
&lt;p&gt;Workload class is determined from the &lt;code&gt;joulie.io/workload-class&lt;/code&gt; pod annotation:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;performance&lt;/code&gt; demand: pod carries &lt;code&gt;joulie.io/workload-class: performance&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;standard&lt;/code&gt; demand (default): no annotation, or &lt;code&gt;joulie.io/workload-class: standard&lt;/code&gt;. Can run on any node; adaptive scoring steers toward eco when performance nodes are congested.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="supply-model-nodes"&gt;Supply model (nodes)&lt;/h2&gt;
&lt;p&gt;Node supply is represented by:&lt;/p&gt;</description></item><item><title>GPU Support (NVIDIA, AMD, Intel)</title><link>https://joulie-k8s.github.io/Joulie/versions/v0.2.4/docs/hardware/gpus/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://joulie-k8s.github.io/Joulie/versions/v0.2.4/docs/hardware/gpus/</guid><description>&lt;p&gt;Joulie supports node-level GPU power-cap intents for NVIDIA, AMD and Intel.&lt;/p&gt;
&lt;h2 id="validation-status"&gt;Validation status&lt;/h2&gt;
&lt;p&gt;GPU support has been validated in simulator mode only (no bare-metal GPU access yet).
The host code paths are designed to work on bare metal (NVIDIA, AMD, Intel) when GPU nodes are available.&lt;/p&gt;
&lt;h2 id="contract-model"&gt;Contract model&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;NodeTwin.spec.gpu.powerCap&lt;/code&gt; defines a per-GPU cap intent:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;scope: perGpu&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;capWattsPerGpu&lt;/code&gt; (absolute, optional)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;capPctOfMax&lt;/code&gt; (percentage, optional)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Precedence:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;capWattsPerGpu&lt;/code&gt; if present&lt;/li&gt;
&lt;li&gt;otherwise &lt;code&gt;capPctOfMax&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The same cap is applied uniformly to all GPUs on the node.&lt;/p&gt;</description></item><item><title>Simulated Cluster</title><link>https://joulie-k8s.github.io/Joulie/versions/v0.2.4/docs/simulator/simulated-cluster/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://joulie-k8s.github.io/Joulie/versions/v0.2.4/docs/simulator/simulated-cluster/</guid><description>&lt;p&gt;The simulated cluster runs the real Joulie components on emulated bare-metal nodes, on one machine with Docker. The agent discovers and caps emulated hardware through the same files and tools it uses on a real node, the controller manager reads node power from the simulator&amp;rsquo;s OpenTelemetry metrics in Prometheus, and the scheduler extender scores real pods. The scripts in &lt;code&gt;scripts/simcluster&lt;/code&gt; drive it, and &lt;code&gt;tests/simcluster&lt;/code&gt; checks it end to end.&lt;/p&gt;
&lt;p&gt;Use it to see a change to the agent, the controller manager, the extender, the chart or the simulator work across components. A change to discovery or capping alone is checked faster by the hwemu tests in &lt;code&gt;cmd/agent&lt;/code&gt; (&lt;code&gt;go test ./cmd/agent/ -run Hwemu&lt;/code&gt;).&lt;/p&gt;</description></item><item><title>Workload Scenarios</title><link>https://joulie-k8s.github.io/Joulie/versions/v0.2.4/docs/simulator/scenarios/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://joulie-k8s.github.io/Joulie/versions/v0.2.4/docs/simulator/scenarios/</guid><description>&lt;p&gt;A workload scenario runs a workload through the &lt;a href="https://joulie-k8s.github.io/Joulie/versions/v0.2.4/docs/simulator/simulated-cluster/"&gt;simulated cluster&lt;/a&gt; and checks what happened: whether the jobs completed, where they were placed, how long they took, what energy they used, and how Joulie&amp;rsquo;s settings compare with each other or with no Joulie at all. The jobs are real Kubernetes Jobs, scheduled by the real scheduler and the Joulie extender onto KWOK Nodes, and the simulator&amp;rsquo;s v1 workload engine drives them on the emulated hardware, so a cap the agent writes slows the jobs on that Node.&lt;/p&gt;</description></item><item><title>Installation</title><link>https://joulie-k8s.github.io/Joulie/versions/v0.2.4/docs/simulator/installation/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://joulie-k8s.github.io/Joulie/versions/v0.2.4/docs/simulator/installation/</guid><description>&lt;p&gt;This page installs the simulator into a Kubernetes cluster you already have, or runs it from a workstation. To try Joulie on emulated nodes with nothing but Docker, use the &lt;a href="https://joulie-k8s.github.io/Joulie/versions/v0.2.4/docs/simulator/simulated-cluster/"&gt;simulated cluster&lt;/a&gt; instead: it creates the cluster and installs everything.&lt;/p&gt;
&lt;p&gt;Every command below uses the namespace &lt;code&gt;joulie-sim-demo&lt;/code&gt; and the release &lt;code&gt;joulie-simulator&lt;/code&gt;, the defaults of &lt;code&gt;make simulator-install&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id="prerequisites"&gt;Prerequisites&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;A Kubernetes cluster (real, or &lt;a href="https://kind.sigs.k8s.io/"&gt;kind&lt;/a&gt; for local work), with &lt;code&gt;kubectl&lt;/code&gt; pointed at it.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;helm&lt;/code&gt; v3 or later.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="install-with-helm"&gt;Install with Helm&lt;/h2&gt;
&lt;p&gt;The simulator chart is published next to the Joulie chart:&lt;/p&gt;</description></item><item><title>Emulate Mode</title><link>https://joulie-k8s.github.io/Joulie/versions/v0.2.4/docs/simulator/emulate-mode/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://joulie-k8s.github.io/Joulie/versions/v0.2.4/docs/simulator/emulate-mode/</guid><description>&lt;p&gt;Emulate mode emulates the bare-metal nodes of a &lt;a href="#the-cluster-spec"&gt;cluster spec&lt;/a&gt; at the boundary the agent sees: the files it reads and writes, and the GPU tools it runs. An unmodified agent then runs its real discovery and capping code against each node, while Kubernetes scheduling stays real. The &lt;a href="https://joulie-k8s.github.io/Joulie/versions/v0.2.4/docs/simulator/simulated-cluster/"&gt;simulated cluster&lt;/a&gt; runs it; to run it yourself, see &lt;a href="https://joulie-k8s.github.io/Joulie/versions/v0.2.4/docs/simulator/installation/#emulate-mode-in-a-cluster"&gt;Installation&lt;/a&gt;. Nodes the spec does not list keep the label-based model of &lt;a href="https://joulie-k8s.github.io/Joulie/versions/v0.2.4/docs/simulator/simulator/"&gt;KWOK and standalone modes&lt;/a&gt;, in the same simulator.&lt;/p&gt;</description></item><item><title>KWOK and Standalone Modes</title><link>https://joulie-k8s.github.io/Joulie/versions/v0.2.4/docs/simulator/simulator/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://joulie-k8s.github.io/Joulie/versions/v0.2.4/docs/simulator/simulator/</guid><description>&lt;p&gt;Outside &lt;a href="https://joulie-k8s.github.io/Joulie/versions/v0.2.4/docs/simulator/emulate-mode/"&gt;emulate mode&lt;/a&gt; the simulator models each node from its labels: it keeps per-node power, cap and thermal state in memory and serves it over HTTP, with no files and no GPU tools. It runs in one of two ways:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;KWOK mode&lt;/strong&gt;: in a Kubernetes cluster whose API server and scheduler are real and whose worker nodes are &lt;a href="https://kwok.sigs.k8s.io/"&gt;KWOK&lt;/a&gt; fake nodes. The real controller manager plans, the real scheduler extender scores, and the agent in &lt;a href="https://joulie-k8s.github.io/Joulie/versions/v0.2.4/docs/getting-started/daemonset/"&gt;pool mode&lt;/a&gt; reads telemetry from and sends caps to the simulator&amp;rsquo;s &lt;a href="#http-api"&gt;HTTP API&lt;/a&gt;. The benchmark experiments run this way.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Standalone mode&lt;/strong&gt; (&lt;code&gt;SIM_STANDALONE=true&lt;/code&gt;): no Kubernetes at all. The simulator places the jobs of a trace and runs the policy baselines itself, in simulated time, as fast as the CPU allows.&lt;/li&gt;
&lt;/ul&gt;
&lt;img src='https://joulie-k8s.github.io/Joulie/versions/v0.2.4/images/joulie-arch-simulator.png
' alt="Joulie simulator architecture overview"&gt;
&lt;h2 id="how-kwok-mode-works"&gt;How KWOK mode works&lt;/h2&gt;
&lt;p&gt;Pod placement and pod lifetime stay real in Kubernetes, the one source of truth for where work runs. The simulator only derives each node&amp;rsquo;s hardware state from what runs there:&lt;/p&gt;</description></item><item><title>Joulie Controller Manager</title><link>https://joulie-k8s.github.io/Joulie/versions/v0.2.4/docs/architecture/controller-manager/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://joulie-k8s.github.io/Joulie/versions/v0.2.4/docs/architecture/controller-manager/</guid><description>&lt;p&gt;The controller manager is Joulie&amp;rsquo;s cluster-level decision engine.&lt;/p&gt;
&lt;p&gt;It does not write host power interfaces directly.
Instead, it decides desired node states and publishes them through Kubernetes objects and labels.&lt;/p&gt;
&lt;p&gt;In practice, the controller manager answers one question over and over:
which nodes should currently supply &lt;code&gt;performance&lt;/code&gt; capacity, and which can safely supply &lt;code&gt;eco&lt;/code&gt; capacity?&lt;/p&gt;
&lt;h2 id="responsibilities"&gt;Responsibilities&lt;/h2&gt;
&lt;p&gt;At each reconcile tick, the controller manager:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;selects eligible managed nodes,&lt;/li&gt;
&lt;li&gt;reads &lt;code&gt;NodeHardware&lt;/code&gt; when available and falls back to node labels when it is not,&lt;/li&gt;
&lt;li&gt;resolves hardware identity against the shared inventory,&lt;/li&gt;
&lt;li&gt;classifies workload demand from pod scheduling constraints,&lt;/li&gt;
&lt;li&gt;runs a policy algorithm (&lt;code&gt;pkg/controller/policy/&lt;/code&gt;) to compute a plan,&lt;/li&gt;
&lt;li&gt;applies transition guards for safe downgrades,&lt;/li&gt;
&lt;li&gt;writes desired node targets (&lt;code&gt;NodeTwin.spec&lt;/code&gt;: caps and, with governor control on, governor presets) and the &lt;code&gt;joulie.io/power-profile&lt;/code&gt; node label.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The agent then enforces those targets node-by-node.&lt;/p&gt;</description></item><item><title>Workload Generation</title><link>https://joulie-k8s.github.io/Joulie/versions/v0.2.4/docs/simulator/workload-generation/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://joulie-k8s.github.io/Joulie/versions/v0.2.4/docs/simulator/workload-generation/</guid><description>&lt;p&gt;&lt;code&gt;workloadgen&lt;/code&gt; (&lt;code&gt;simulator/cmd/workloadgen&lt;/code&gt;) writes synthetic traces of AI cluster workloads for the simulator. This page says what a trace contains and how the generator samples it; &lt;a href="https://joulie-k8s.github.io/Joulie/versions/v0.2.4/docs/simulator/workload-simulator/"&gt;Workload Simulator&lt;/a&gt; says how the simulator runs it.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;go run ./simulator/cmd/workloadgen --out trace.jsonl --jobs &lt;span style="color:#ae81ff"&gt;200&lt;/span&gt; --seed &lt;span style="color:#ae81ff"&gt;7&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The generator models AI-oriented Kubernetes clusters: CPU and GPU jobs, memory-bound jobs, and workloads made of several pods, such as distributed training and hyperparameter searches. It does not model network bandwidth, disk throughput or framework communication protocols: their cost is folded into the utilisation and bottleneck signals of each workload (&lt;code&gt;gpuUtilization&lt;/code&gt;, &lt;code&gt;memoryIntensity&lt;/code&gt;, &lt;code&gt;cpuFeedIntensityGpu&lt;/code&gt;).&lt;/p&gt;</description></item><item><title>Workload Simulator</title><link>https://joulie-k8s.github.io/Joulie/versions/v0.2.4/docs/simulator/workload-simulator/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://joulie-k8s.github.io/Joulie/versions/v0.2.4/docs/simulator/workload-simulator/</guid><description>&lt;p&gt;This page says how the simulator runs a workload trace: which Kubernetes objects it creates, how each job progresses under caps, and how jobs load the hardware model. &lt;a href="https://joulie-k8s.github.io/Joulie/versions/v0.2.4/docs/simulator/workload-generation/"&gt;Workload Generation&lt;/a&gt; describes the traces; &lt;a href="https://joulie-k8s.github.io/Joulie/versions/v0.2.4/docs/simulator/power-simulator/"&gt;Power Simulator&lt;/a&gt; the power side of a node.&lt;/p&gt;
&lt;h2 id="two-engines-legacy-and-v1"&gt;Two engines: legacy and v1&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;SIM_WORKLOAD_MODEL&lt;/code&gt; selects the engine that runs a trace.&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;&lt;/th&gt;
 &lt;th&gt;legacy (outside emulate mode)&lt;/th&gt;
 &lt;th&gt;v1 (emulate mode)&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;Started by&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;SIM_WORKLOAD_TRACE_PATH&lt;/code&gt;, replayed from startup&lt;/td&gt;
 &lt;td&gt;the run API, or &lt;code&gt;SIM_WORKLOAD_TRACE_PATH&lt;/code&gt; as an implicit run&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Kubernetes objects&lt;/td&gt;
 &lt;td&gt;one bare pod per job&lt;/td&gt;
 &lt;td&gt;one &lt;code&gt;batch/v1&lt;/code&gt; Job per group (&lt;code&gt;workloadId&lt;/code&gt;, &lt;code&gt;podRole&lt;/code&gt;, submit offset), Indexed when larger than one, &lt;code&gt;backoffLimit: 0&lt;/code&gt;, deleted &lt;code&gt;SIM_WORKLOAD_JOB_TTL&lt;/code&gt; after it finishes&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Class&lt;/td&gt;
 &lt;td&gt;inferred from the pod&amp;rsquo;s affinity&lt;/td&gt;
 &lt;td&gt;declared by the annotation &lt;code&gt;joulie.io/workload-class&lt;/code&gt; (&lt;code&gt;SIM_WORKLOAD_INTENT&lt;/code&gt;)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Placement&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;type=kwok&lt;/code&gt; and the KWOK toleration&lt;/td&gt;
 &lt;td&gt;the trace&amp;rsquo;s placement, plus the selector &lt;code&gt;sim.joulie.io/cluster=&amp;lt;spec&amp;gt;&lt;/code&gt; and the KWOK toleration&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Completion&lt;/td&gt;
 &lt;td&gt;the pod is deleted&lt;/td&gt;
 &lt;td&gt;the pod is annotated &lt;code&gt;sim.joulie.io/complete: &amp;quot;true&amp;quot;&lt;/code&gt; and KWOK&amp;rsquo;s &lt;code&gt;pod-complete&lt;/code&gt; Stage moves it to &lt;code&gt;Succeeded&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;A job that cannot progress&lt;/td&gt;
 &lt;td&gt;completed by force after &lt;code&gt;SIM_WORKLOAD_STALL_FORCE_COMPLETE&lt;/code&gt; (120 s)&lt;/td&gt;
 &lt;td&gt;reported as stalled (&lt;code&gt;joulie.sim.job.stalled&lt;/code&gt;, &lt;code&gt;joulie.sim.run.stalled&lt;/code&gt;), never completed&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Creates per tick&lt;/td&gt;
 &lt;td&gt;20&lt;/td&gt;
 &lt;td&gt;no cap&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Speed&lt;/td&gt;
 &lt;td&gt;the node model&amp;rsquo;s frequency scale and GPU cap ratio&lt;/td&gt;
 &lt;td&gt;the throughput of the emulated hardware the job runs on, under the limits the agent wrote&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Energy&lt;/td&gt;
 &lt;td&gt;node and cluster totals at &lt;code&gt;/debug/energy&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;each node&amp;rsquo;s energy ledger delta, attributed per job and class by the jobs&amp;rsquo; requests; the unrequested share of idle package power is class &lt;code&gt;idle&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Results&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;joulie_sim_job_*&lt;/code&gt; counters at &lt;code&gt;/metrics&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;OTLP JSON Lines in the run directory, plus the run API&amp;rsquo;s status&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;In a v1 run a job is submitted at the first tick after its create call returns, scheduled at the first tick that sees its pod bound, and done at the tick its work runs out, so its times are exact to one &lt;code&gt;SIM_EMULATE_TICK&lt;/code&gt;. A finish with cleanup deletes the run&amp;rsquo;s Job objects or bare pods by the run&amp;rsquo;s label, one call per namespace (&lt;a href="https://joulie-k8s.github.io/Joulie/versions/v0.2.4/docs/getting-started/05-configuration-reference/#simulator-workloads-and-scenarios"&gt;Run API&lt;/a&gt;).&lt;/p&gt;</description></item><item><title>Power Simulator</title><link>https://joulie-k8s.github.io/Joulie/versions/v0.2.4/docs/simulator/power-simulator/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://joulie-k8s.github.io/Joulie/versions/v0.2.4/docs/simulator/power-simulator/</guid><description>&lt;p&gt;This page says how the simulator advances a modelled node (any node outside the emulate-mode cluster spec): its control state, its physics step, and the telemetry it serves. The physical model itself, its parameters and their sources, is in &lt;a href="https://joulie-k8s.github.io/Joulie/versions/v0.2.4/docs/hardware/hardware-modeling/"&gt;Hardware Modeling&lt;/a&gt;; how jobs progress on a node is in &lt;a href="https://joulie-k8s.github.io/Joulie/versions/v0.2.4/docs/simulator/workload-simulator/"&gt;Workload Simulator&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The power simulator:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;keeps per-node control state (CPU cap, DVFS throttle, GPU cap),&lt;/li&gt;
&lt;li&gt;applies control actions from &lt;code&gt;/control/{node}&lt;/code&gt;,&lt;/li&gt;
&lt;li&gt;advances caps, frequency, power and temperature with their settling lags,&lt;/li&gt;
&lt;li&gt;serves power telemetry on &lt;code&gt;/otlp/telemetry/{node}&lt;/code&gt; and &lt;code&gt;/telemetry/{node}&lt;/code&gt;,&lt;/li&gt;
&lt;li&gt;integrates energy over time (&lt;code&gt;/debug/energy&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="the-physics-step"&gt;The physics step&lt;/h2&gt;
&lt;p&gt;A modelled node advances only in a physics step, in simulated time, never when it is read:&lt;/p&gt;</description></item><item><title>Hardware Calibration</title><link>https://joulie-k8s.github.io/Joulie/versions/v0.2.4/docs/simulator/calibration/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://joulie-k8s.github.io/Joulie/versions/v0.2.4/docs/simulator/calibration/</guid><description>&lt;p&gt;&lt;code&gt;hwcalib fit&lt;/code&gt; (&lt;code&gt;simulator/cmd/hwcalib&lt;/code&gt;, package &lt;code&gt;simulator/pkg/calib&lt;/code&gt;) fits the physics of a hardware profile to a sweep measured on one of your nodes, and writes the result as a local profile the simulator loads from &lt;code&gt;SIM_PROFILES_DIR&lt;/code&gt;. The builtin profiles keep vendor figures and placeholders; a calibrated profile replaces them with what your machine draws. &lt;code&gt;hwcalib sweep&lt;/code&gt; records such a sweep on the node itself (see &lt;a href="#recording-on-the-node-with-hwcalib-sweep"&gt;Recording on the node&lt;/a&gt;).&lt;/p&gt;
&lt;h2 id="recording-a-sweep"&gt;Recording a sweep&lt;/h2&gt;
&lt;p&gt;A sweep holds the node at a series of setpoints and records, at every sample, the node&amp;rsquo;s readings and the setpoints it held. Each setpoint should last long enough to settle: the fit drops the start of every setpoint (see &lt;a href="#segments-and-steady-windows"&gt;Segments and steady windows&lt;/a&gt;) and needs at least 5 samples after that, so with a 2 s sample period a setpoint of 22 s or more is enough.&lt;/p&gt;</description></item><item><title>Simulator Metrics</title><link>https://joulie-k8s.github.io/Joulie/versions/v0.2.4/docs/simulator/metrics/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://joulie-k8s.github.io/Joulie/versions/v0.2.4/docs/simulator/metrics/</guid><description>&lt;p&gt;The simulator publishes three kinds of telemetry:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Prometheus metrics about itself&lt;/strong&gt; at &lt;code&gt;/metrics&lt;/code&gt; (&lt;code&gt;joulie_sim_*&lt;/code&gt;), for Prometheus to scrape.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;OpenTelemetry metrics and events about the emulated nodes and the facility&lt;/strong&gt;, in &lt;a href="https://joulie-k8s.github.io/Joulie/versions/v0.2.4/docs/simulator/emulate-mode/"&gt;emulate mode&lt;/a&gt; only. Metrics are pushed as OTLP JSON over HTTP; events are written to &lt;code&gt;events.jsonl&lt;/code&gt; and pushed when a logs endpoint is set.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Compatibility exporters&lt;/strong&gt;, in emulate mode only: the Prometheus formats of real exporters (Kepler, dcgm-exporter, the ROCm device-metrics-exporter) at &lt;code&gt;/exporters/&amp;lt;name&amp;gt;/metrics&lt;/code&gt;, opt-in.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The OpenTelemetry names are spelled once, in &lt;code&gt;pkg/otel/semconv&lt;/code&gt;, and the controller manager reads the same names back from Prometheus. Their Prometheus spelling is computed by &lt;code&gt;pkg/otel/promname&lt;/code&gt;.&lt;/p&gt;</description></item><item><title>Hardware Modeling and Physical Power Model</title><link>https://joulie-k8s.github.io/Joulie/versions/v0.2.4/docs/hardware/hardware-modeling/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://joulie-k8s.github.io/Joulie/versions/v0.2.4/docs/hardware/hardware-modeling/</guid><description>&lt;p&gt;This page documents how Joulie models CPUs and GPUs across the project using a mix of:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;official vendor specifications and management APIs&lt;/strong&gt;,&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;public measured power curves&lt;/strong&gt;, and&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;explicit proxy models&lt;/strong&gt; where public exact curves are not yet available.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It serves two closely related purposes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;for the &lt;strong&gt;agent&lt;/strong&gt;, it describes the hardware assumptions used to resolve caps, interpret device limits, and reason about how throttling affects attainable performance&lt;/li&gt;
&lt;li&gt;for the &lt;strong&gt;simulator&lt;/strong&gt;, it describes the physical model used to turn utilization and control actions into simulated power and slowdown&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="quick-summary"&gt;Quick summary&lt;/h2&gt;
&lt;p&gt;If you want the short version before the details:&lt;/p&gt;</description></item><item><title>Joulie Agent</title><link>https://joulie-k8s.github.io/Joulie/versions/v0.2.4/docs/architecture/agent/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://joulie-k8s.github.io/Joulie/versions/v0.2.4/docs/architecture/agent/</guid><description>&lt;p&gt;The agent is Joulie&amp;rsquo;s node-side enforcement component.&lt;/p&gt;
&lt;p&gt;It consumes desired state and applies node-local controls through configured backends.&lt;/p&gt;
&lt;p&gt;If the controller manager decides &amp;ldquo;this node should now behave like eco&amp;rdquo; or &amp;ldquo;this node should stay performance&amp;rdquo;,
the agent is the component that turns that intent into concrete control actions.&lt;/p&gt;
&lt;h2 id="responsibilities"&gt;Responsibilities&lt;/h2&gt;
&lt;p&gt;At each reconcile tick, the agent:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;identifies its node scope (single node in daemonset mode, sharded set in pool mode),&lt;/li&gt;
&lt;li&gt;discovers local CPU/GPU hardware and runtime control capability,&lt;/li&gt;
&lt;li&gt;publishes &lt;code&gt;NodeHardware&lt;/code&gt; for each owned node,&lt;/li&gt;
&lt;li&gt;reads desired target (&lt;code&gt;NodeTwin.spec&lt;/code&gt;) for each owned node,&lt;/li&gt;
&lt;li&gt;resolves telemetry/control backend from environment variables (default: host),&lt;/li&gt;
&lt;li&gt;applies controls (host or HTTP),&lt;/li&gt;
&lt;li&gt;exports metrics and status.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="inputs-and-outputs"&gt;Inputs and outputs&lt;/h2&gt;
&lt;p&gt;Inputs:&lt;/p&gt;</description></item><item><title>Digital Twin</title><link>https://joulie-k8s.github.io/Joulie/versions/v0.2.4/docs/architecture/digital-twin/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://joulie-k8s.github.io/Joulie/versions/v0.2.4/docs/architecture/digital-twin/</guid><description>&lt;p&gt;The digital twin is Joulie&amp;rsquo;s core predictive engine. It is a lightweight O(1) parametric model that predicts the impact of scheduling and power-cap decisions on node thermal and power state, without running a full simulation for each scheduling decision.&lt;/p&gt;
&lt;h2 id="what-the-digital-twin-computes"&gt;What the digital twin computes&lt;/h2&gt;
&lt;p&gt;For each managed node, the twin produces three scores stored in &lt;code&gt;NodeTwin.status&lt;/code&gt;:&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Signal&lt;/th&gt;
 &lt;th&gt;Range&lt;/th&gt;
 &lt;th&gt;Meaning&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;Power headroom&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;up to 100, can go negative&lt;/td&gt;
 &lt;td&gt;Fraction of the node&amp;rsquo;s capped power budget still unused. Higher is better for new workload placement. Negative means the node already draws more than its budget.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;CoolingStress&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;0-100&lt;/td&gt;
 &lt;td&gt;Measured node power as a fraction of node TDP, scaled by an ambient temperature multiplier. High values indicate the node is near its thermal limit.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;PSUStress&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;0-100&lt;/td&gt;
 &lt;td&gt;Rack power draw (or cluster power draw when no rack is known) as a fraction of a reference rack capacity. Computed and published, but not read by the scheduler today.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The twin also computes:&lt;/p&gt;</description></item><item><title>Policy Algorithms</title><link>https://joulie-k8s.github.io/Joulie/versions/v0.2.4/docs/architecture/policies/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://joulie-k8s.github.io/Joulie/versions/v0.2.4/docs/architecture/policies/</guid><description>&lt;p&gt;This page documents the controller policy algorithms implemented in &lt;code&gt;pkg/controller/policy/&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Use this page after:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href="https://joulie-k8s.github.io/Joulie/versions/v0.2.4/docs/architecture/policy/"&gt;CRD and Policy Model&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://joulie-k8s.github.io/Joulie/versions/v0.2.4/docs/architecture/controller-manager/"&gt;Joulie Controller Manager&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="classification-input"&gt;Classification Input&lt;/h2&gt;
&lt;p&gt;Policy demand classification is derived from the &lt;code&gt;joulie.io/workload-class&lt;/code&gt; pod annotation:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;performance&lt;/code&gt;: pod carries &lt;code&gt;joulie.io/workload-class: performance&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;standard&lt;/code&gt; (default): no annotation or &lt;code&gt;joulie.io/workload-class: standard&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="shared-reconcile-flow"&gt;Shared Reconcile Flow&lt;/h2&gt;
&lt;p&gt;Each reconcile tick:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Select eligible nodes from &lt;code&gt;NODE_SELECTOR&lt;/code&gt;, excluding reserved and unschedulable nodes.&lt;/li&gt;
&lt;li&gt;Build a hardware view from &lt;code&gt;NodeHardware&lt;/code&gt; when available, otherwise from node labels/inventory fallback.&lt;/li&gt;
&lt;li&gt;Build a desired plan with the selected policy.&lt;/li&gt;
&lt;li&gt;Apply downgrade guard (sets &lt;code&gt;NodeTwin.status.schedulableClass&lt;/code&gt; to &lt;code&gt;draining&lt;/code&gt; while blocking pods still run).&lt;/li&gt;
&lt;li&gt;Write &lt;code&gt;NodeTwin.spec&lt;/code&gt; and update the &lt;code&gt;joulie.io/power-profile&lt;/code&gt; node label.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;In other words, &lt;code&gt;static_partition&lt;/code&gt; and &lt;code&gt;queue_aware_v1&lt;/code&gt; decide &lt;em&gt;how many&lt;/em&gt; nodes stay in &lt;code&gt;performance&lt;/code&gt;, and the per-family split below decides &lt;em&gt;which&lt;/em&gt; nodes those are.&lt;/p&gt;</description></item><item><title>Scheduler Extender</title><link>https://joulie-k8s.github.io/Joulie/versions/v0.2.4/docs/architecture/scheduler/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://joulie-k8s.github.io/Joulie/versions/v0.2.4/docs/architecture/scheduler/</guid><description>&lt;p&gt;Joulie ships a scheduler extender that steers workloads toward appropriate nodes based on power profile, thermal stress, and hardware capabilities.&lt;/p&gt;
&lt;h2 id="how-a-pod-gets-scheduled-end-to-end"&gt;How a pod gets scheduled (end-to-end)&lt;/h2&gt;
&lt;p&gt;When a new pod is created in the cluster, the following sequence occurs:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;1. Pod created (e.g., kubectl apply, Job controller, Deployment rollout)
 |
2. kube-scheduler picks up the unscheduled pod
 |
3. kube-scheduler runs its default filters (resource fits, taints, affinity)
 |
4. kube-scheduler calls Joulie&amp;#39;s /filter endpoint
 | - Sends: pod spec + candidate node list
 | - Joulie reads pod annotation joulie.io/workload-class
 | - Performance pods: reject nodes with schedulableClass = eco or draining
 | - Standard pods: pass all nodes
 | - Returns: filtered node list + rejection reasons
 |
5. kube-scheduler calls Joulie&amp;#39;s /prioritize endpoint
 | - Sends: pod spec + surviving node list
 | - Joulie reads NodeTwin CRs (cached, 30s TTL) for power state
 | - Joulie reads NodeHardware CRs (cached, 30s TTL) for hardware specs
 | - Joulie extracts pod CPU/GPU requests for marginal power estimation
 | - Joulie scores each node 0-100 using the scoring formula
 | - Returns: list of (node, score) pairs
 |
6. kube-scheduler combines Joulie scores with its own plugin scores
 |
7. Pod is bound to the highest-scoring node
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The extender participates in steps 4 and 5 only. It does not replace the Kubernetes scheduler; it extends it with energy-aware filter and scoring logic.&lt;/p&gt;</description></item><item><title>Energy-Aware Scheduling</title><link>https://joulie-k8s.github.io/Joulie/versions/v0.2.4/docs/architecture/energy-aware-scheduling/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://joulie-k8s.github.io/Joulie/versions/v0.2.4/docs/architecture/energy-aware-scheduling/</guid><description>&lt;p&gt;Joulie&amp;rsquo;s scheduler extender makes placement decisions informed by real-time energy telemetry, workload characteristics, and facility-level power conditions. This page describes the full pipeline from metrics collection through scoring and optional rescheduling.&lt;/p&gt;
&lt;h2 id="end-to-end-pipeline"&gt;End-to-end pipeline&lt;/h2&gt;
&lt;p&gt;The energy-aware scheduling pipeline has five stages:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;RAPL/NVML readings as OpenTelemetry metrics (or Kepler)
 -&amp;gt; Prometheus (OTLP receiver, or scrape)
 -&amp;gt; Digital twin (NodeTwin.status)
 -&amp;gt; Scheduler extender (filter + score)
 -&amp;gt; Placement decision
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Each stage runs independently and communicates through Kubernetes CRDs or Prometheus queries. There is no monolithic scheduling engine; each component does one thing and feeds the next.&lt;/p&gt;</description></item><item><title>Configuration Reference</title><link>https://joulie-k8s.github.io/Joulie/versions/v0.2.4/docs/getting-started/05-configuration-reference/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://joulie-k8s.github.io/Joulie/versions/v0.2.4/docs/getting-started/05-configuration-reference/</guid><description>&lt;p&gt;Complete reference for all Joulie environment variables. These are set via Helm values or directly in the Deployment/DaemonSet manifests.&lt;/p&gt;
&lt;p&gt;Defaults listed below are the &lt;strong&gt;code defaults&lt;/strong&gt;. The Helm chart (&lt;code&gt;charts/joulie/values.yaml&lt;/code&gt;) overrides some of them: notably, the controller manager &lt;code&gt;NODE_SELECTOR&lt;/code&gt; defaults to &lt;code&gt;joulie.io/managed=true&lt;/code&gt; in the chart even though the code default is &lt;code&gt;node-role.kubernetes.io/worker&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Booleans.&lt;/strong&gt; The agent, the controller manager and the simulator read every on/off variable the same way: &lt;code&gt;true&lt;/code&gt;, &lt;code&gt;1&lt;/code&gt;, &lt;code&gt;yes&lt;/code&gt; or &lt;code&gt;on&lt;/code&gt;, and &lt;code&gt;false&lt;/code&gt;, &lt;code&gt;0&lt;/code&gt;, &lt;code&gt;no&lt;/code&gt; or &lt;code&gt;off&lt;/code&gt;, in any case, with surrounding spaces ignored. Unset or empty means the default. Any other value keeps the default and logs one warning naming the variable, so a typo never flips a switch. The exceptions are &lt;code&gt;SIM_OTEL_FILE_METRICS&lt;/code&gt;, &lt;code&gt;OTEL_SDK_DISABLED&lt;/code&gt; and &lt;code&gt;fakesmi&lt;/code&gt;&amp;rsquo;s &lt;code&gt;HWEMU_UNPRIVILEGED&lt;/code&gt;, whose rows say how they are read.&lt;/p&gt;</description></item><item><title>Input Telemetry and Actuation Interfaces</title><link>https://joulie-k8s.github.io/Joulie/versions/v0.2.4/docs/architecture/telemetry/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://joulie-k8s.github.io/Joulie/versions/v0.2.4/docs/architecture/telemetry/</guid><description>&lt;p&gt;This page describes runtime IO contracts:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;how Joulie reads telemetry inputs,&lt;/li&gt;
&lt;li&gt;how Joulie sends control intents.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you want the CRD-level summary first, read &lt;a href="https://joulie-k8s.github.io/Joulie/versions/v0.2.4/docs/architecture/policy/"&gt;CRD and Policy Model&lt;/a&gt;.
This page is the detailed runtime reference for the telemetry and control contract.&lt;/p&gt;
&lt;p&gt;It is not the &lt;code&gt;/metrics&lt;/code&gt; exposition contract.
For exported metrics, see &lt;a href="https://joulie-k8s.github.io/Joulie/versions/v0.2.4/docs/architecture/metrics/"&gt;Metrics Reference&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="why-this-abstraction-exists"&gt;Why this abstraction exists&lt;/h2&gt;
&lt;p&gt;Joulie must run in two worlds with the same control logic:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;real hardware clusters,&lt;/li&gt;
&lt;li&gt;simulator/KWOK clusters.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So agent/controller manager logic depends on provider interfaces, not directly on sysfs or simulator HTTP shape.&lt;/p&gt;</description></item><item><title>Metrics Reference</title><link>https://joulie-k8s.github.io/Joulie/versions/v0.2.4/docs/architecture/metrics/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://joulie-k8s.github.io/Joulie/versions/v0.2.4/docs/architecture/metrics/</guid><description>&lt;p&gt;Joulie exposes Prometheus metrics from multiple components.&lt;/p&gt;
&lt;p&gt;This page covers &lt;strong&gt;controller manager + agent + scheduler extender&lt;/strong&gt; metrics.
Simulator metrics are documented separately in:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://joulie-k8s.github.io/Joulie/versions/v0.2.4/docs/simulator/metrics/"&gt;Simulator Metrics&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For telemetry/control input interfaces (host/http routing), see:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://joulie-k8s.github.io/Joulie/versions/v0.2.4/docs/architecture/telemetry/"&gt;Input Telemetry and Actuation Interfaces&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="endpoints-by-component"&gt;Endpoints by component&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Agent:
&lt;ul&gt;
&lt;li&gt;path: &lt;code&gt;/metrics&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;default address: &lt;code&gt;:8080&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;env override: &lt;code&gt;METRICS_ADDR&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Controller manager:
&lt;ul&gt;
&lt;li&gt;path: &lt;code&gt;/metrics&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;default address: &lt;code&gt;:8081&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;env override: &lt;code&gt;METRICS_ADDR&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Scheduler extender:
&lt;ul&gt;
&lt;li&gt;path: &lt;code&gt;/metrics&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;default address: &lt;code&gt;:9877&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;env override: &lt;code&gt;METRICS_ADDR&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="agent-metrics"&gt;Agent metrics&lt;/h2&gt;
&lt;h3 id="backend-and-selected-cap"&gt;Backend and selected cap&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;joulie_backend_mode{node,mode}&lt;/code&gt; (gauge)
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;mode&lt;/code&gt;: &lt;code&gt;none|rapl|dvfs&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;active mode is &lt;code&gt;1&lt;/code&gt;, others &lt;code&gt;0&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;joulie_policy_cap_watts{node,policy}&lt;/code&gt; (gauge)
&lt;ul&gt;
&lt;li&gt;current selected policy cap in watts&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="rapl-powerenergy"&gt;RAPL power/energy&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;joulie_rapl_energy_uj{node,zone}&lt;/code&gt; (gauge)
&lt;ul&gt;
&lt;li&gt;latest raw RAPL energy counter in microjoules&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;joulie_rapl_estimated_power_watts{node,zone}&lt;/code&gt; (gauge)
&lt;ul&gt;
&lt;li&gt;per-zone estimated power from energy deltas&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;joulie_rapl_package_total_power_watts{node}&lt;/code&gt; (gauge)
&lt;ul&gt;
&lt;li&gt;sum of package-level estimated power&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="dvfs-controller"&gt;DVFS controller&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;joulie_dvfs_observed_power_watts{node}&lt;/code&gt; (gauge)
&lt;ul&gt;
&lt;li&gt;observed package power used by DVFS controller&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;joulie_dvfs_ema_power_watts{node}&lt;/code&gt; (gauge)
&lt;ul&gt;
&lt;li&gt;EMA-smoothed power used for decisions&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;joulie_dvfs_throttle_pct{node}&lt;/code&gt; (gauge)
&lt;ul&gt;
&lt;li&gt;current throttle percentage&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;joulie_dvfs_above_trip_count{node}&lt;/code&gt; (gauge)
&lt;ul&gt;
&lt;li&gt;consecutive above-threshold samples&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;joulie_dvfs_below_trip_count{node}&lt;/code&gt; (gauge)
&lt;ul&gt;
&lt;li&gt;consecutive below-threshold samples&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;joulie_dvfs_actions_total{node,action}&lt;/code&gt; (counter)
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;action&lt;/code&gt;: &lt;code&gt;throttle_up|throttle_down&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="cpu-frequency-observability"&gt;CPU frequency observability&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;joulie_dvfs_cpu_cur_freq_khz{node,cpu}&lt;/code&gt; (gauge)
&lt;ul&gt;
&lt;li&gt;current CPU/policy frequency in kHz&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;joulie_dvfs_cpu_max_freq_khz{node,cpu}&lt;/code&gt; (gauge)
&lt;ul&gt;
&lt;li&gt;enforced max frequency cap in kHz&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="governors"&gt;Governors&lt;/h3&gt;
&lt;p&gt;Written by the agent&amp;rsquo;s &lt;a href="https://joulie-k8s.github.io/Joulie/versions/v0.2.4/docs/architecture/agent/#governor-control"&gt;governor control&lt;/a&gt;.&lt;/p&gt;</description></item><item><title>Upgrading</title><link>https://joulie-k8s.github.io/Joulie/versions/v0.2.4/docs/getting-started/06-upgrading/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://joulie-k8s.github.io/Joulie/versions/v0.2.4/docs/getting-started/06-upgrading/</guid><description>&lt;p&gt;What to check when you upgrade a Joulie installation from v0.2 to v0.3. Go through the list before running &lt;code&gt;helm upgrade&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id="before-the-upgrade"&gt;Before the upgrade&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;input disabled="" type="checkbox"&gt; &lt;strong&gt;Apply the new CRDs.&lt;/strong&gt; &lt;code&gt;helm upgrade&lt;/code&gt; never updates CRDs, and the API server prunes every field the installed CRD does not know, such as the governor fields of &lt;code&gt;NodeTwin&lt;/code&gt; and &lt;code&gt;NodeHardware&lt;/code&gt;. Apply them first:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;kubectl apply -f charts/joulie/crds/
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Without a checkout, take them from the release chart: &lt;code&gt;helm pull oci://registry.cern.ch/joulie/joulie --version &amp;lt;version&amp;gt; --untar --untardir /tmp/joulie-chart&lt;/code&gt;, then &lt;code&gt;kubectl apply -f /tmp/joulie-chart/joulie/crds/&lt;/code&gt;.&lt;/p&gt;</description></item><item><title>CPU-Only Benchmark</title><link>https://joulie-k8s.github.io/Joulie/versions/v0.2.4/docs/experiments/cpu-only-benchmark/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://joulie-k8s.github.io/Joulie/versions/v0.2.4/docs/experiments/cpu-only-benchmark/</guid><description>&lt;p&gt;This page reports results from the CPU-only cluster benchmark experiment (KWOK, 40 nodes):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/joulie-k8s/Joulie/tree/main/experiments/01-cpu-only-benchmark"&gt;&lt;code&gt;experiments/01-cpu-only-benchmark/&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note (2026-10-09):&lt;/strong&gt; The results on this page, like the experiment&amp;rsquo;s &lt;code&gt;REPORT.md&lt;/code&gt; and &lt;code&gt;REPORT-standalone.md&lt;/code&gt;, were produced with an older simulator model and have not been rerun. The simulator reads the hardware catalog embedded in it, with a SPECpower curve for each CPU that has one, and thermal throttling is off unless configured. &lt;a href="https://github.com/joulie-k8s/Joulie/blob/main/simulator/CHANGES.md"&gt;Simulator changes&lt;/a&gt; lists each change and how far it moves the experiment&amp;rsquo;s &lt;code&gt;configs/benchmark-debug.yaml&lt;/code&gt;.&lt;/p&gt;</description></item><item><title>Governor Exploration</title><link>https://joulie-k8s.github.io/Joulie/versions/v0.2.4/docs/experiments/governor-exploration/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://joulie-k8s.github.io/Joulie/versions/v0.2.4/docs/experiments/governor-exploration/</guid><description>&lt;p&gt;This page reports experiment 05, CPU and GPU governor presets as an energy
dimension of their own, next to power caps:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/joulie-k8s/Joulie/tree/main/experiments/05-governor-exploration"&gt;&lt;code&gt;experiments/05-governor-exploration/&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Joulie writes a governor as a vendor-neutral preset in the NodeTwin
(&lt;code&gt;spec.cpu.governor.preset&lt;/code&gt;: &lt;code&gt;performance&lt;/code&gt;, &lt;code&gt;balanced&lt;/code&gt;, &lt;code&gt;efficiency&lt;/code&gt;;
&lt;code&gt;spec.gpu.governor.preset&lt;/code&gt;: &lt;code&gt;auto&lt;/code&gt;, &lt;code&gt;sustained&lt;/code&gt;, &lt;code&gt;efficient&lt;/code&gt;, &lt;code&gt;low&lt;/code&gt;, &lt;code&gt;peak&lt;/code&gt;),
and the agent maps it to the native words of the node&amp;rsquo;s CPU frequency driver
and GPU family. Governor control is off by default (&lt;code&gt;GOVERNOR_CONTROL&lt;/code&gt;,
&lt;code&gt;GOVERNOR_WRITES&lt;/code&gt;, &lt;a href="https://joulie-k8s.github.io/Joulie/versions/v0.2.4/docs/getting-started/05-configuration-reference/"&gt;Configuration reference&lt;/a&gt;).&lt;/p&gt;</description></item><item><title>Heterogeneous GPU Cluster Benchmark</title><link>https://joulie-k8s.github.io/Joulie/versions/v0.2.4/docs/experiments/heterogeneous-benchmark/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://joulie-k8s.github.io/Joulie/versions/v0.2.4/docs/experiments/heterogeneous-benchmark/</guid><description>&lt;p&gt;This page reports results from the heterogeneous GPU cluster benchmark experiment (KWOK, 41 nodes):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/joulie-k8s/Joulie/tree/main/experiments/02-heterogeneous-benchmark"&gt;&lt;code&gt;experiments/02-heterogeneous-benchmark/&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note (2026-10-09):&lt;/strong&gt; The results on this page, like the experiment&amp;rsquo;s &lt;code&gt;REPORT.md&lt;/code&gt; and &lt;code&gt;REPORT-standalone.md&lt;/code&gt;, were produced with an older simulator model and have not been rerun. The simulator reads the hardware catalog embedded in it, with a SPECpower curve for each CPU that has one, and thermal throttling is off unless configured. &lt;a href="https://github.com/joulie-k8s/Joulie/blob/main/simulator/CHANGES.md"&gt;Simulator changes&lt;/a&gt; lists each change and how far it moves the experiment&amp;rsquo;s &lt;code&gt;configs/benchmark-debug.yaml&lt;/code&gt;.&lt;/p&gt;</description></item><item><title>Homogeneous H100 NVL Benchmark</title><link>https://joulie-k8s.github.io/Joulie/versions/v0.2.4/docs/experiments/homogeneous-h100-benchmark/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://joulie-k8s.github.io/Joulie/versions/v0.2.4/docs/experiments/homogeneous-h100-benchmark/</guid><description>&lt;p&gt;This page reports results from the homogeneous H100 NVL cluster benchmark experiment (KWOK, 41 nodes):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/joulie-k8s/Joulie/tree/main/experiments/03-homogeneous-h100-benchmark"&gt;&lt;code&gt;experiments/03-homogeneous-h100-benchmark/&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note (2026-10-09):&lt;/strong&gt; The results on this page, like the experiment&amp;rsquo;s &lt;code&gt;REPORT.md&lt;/code&gt; and &lt;code&gt;REPORT-standalone.md&lt;/code&gt;, were produced with an older simulator model and have not been rerun. The simulator reads the hardware catalog embedded in it, with a SPECpower curve for each CPU that has one, and thermal throttling is off unless configured. &lt;a href="https://github.com/joulie-k8s/Joulie/blob/main/simulator/CHANGES.md"&gt;Simulator changes&lt;/a&gt; lists each change and how far it moves the experiment&amp;rsquo;s &lt;code&gt;configs/benchmark-debug.yaml&lt;/code&gt;.&lt;/p&gt;</description></item><item><title>Scoring Formula Validation</title><link>https://joulie-k8s.github.io/Joulie/versions/v0.2.4/docs/experiments/scoring-formula-validation/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://joulie-k8s.github.io/Joulie/versions/v0.2.4/docs/experiments/scoring-formula-validation/</guid><description>&lt;p&gt;This page reports results from the energy-aware scheduling formula validation experiment:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/joulie-k8s/Joulie/tree/main/experiments/04-scoring-formula-validation"&gt;&lt;code&gt;experiments/04-scoring-formula-validation/&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="objective"&gt;Objective&lt;/h2&gt;
&lt;p&gt;Validate Joulie&amp;rsquo;s energy-aware scheduling formula by demonstrating that power-aware scheduling improves energy efficiency compared to standard Kubernetes bin-packing (MostAllocated), using a Modelica FMU (DXCooled Airside Economizer) for physically-accurate cooling/PUE computation.&lt;/p&gt;
&lt;p&gt;Two scales tested:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Small cluster&lt;/strong&gt; (28 nodes): formula tuning and component selection&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Large cluster&lt;/strong&gt; (2,500 nodes): production-scale validation with H100 GPUs&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The experiment also validated the evolution from a legacy multi-component formula to the current streamlined Joulie scoring formula.&lt;/p&gt;</description></item></channel></rss>