From 5778028e64bb35141e4f74ce5e89329c7b0af7e0 Mon Sep 17 00:00:00 2001 From: David Berenstein Date: Wed, 12 Aug 2026 23:32:07 +0200 Subject: [PATCH 1/4] docs: correct the estimation chain reference The documented CPU fallback ladder did not match resource_tracker.py, the equivalence constants were sourced to a file absent from the repo, Electricity Maps was undocumented, and the RAM heuristic was presented as empirically motivated. Rewrite methodology.md against the source and split the bibliography and equivalences into their own pages. Co-Authored-By: Claude Opus 5 (1M context) --- docs/explanation/accuracy.md | 2 +- docs/explanation/alternatives.md | 2 +- docs/explanation/equivalences.md | 71 +++ docs/explanation/faq.md | 2 +- docs/explanation/methodology.md | 811 ++++++++++++++----------------- docs/explanation/references.md | 96 ++++ docs/how-to/troubleshooting.md | 2 +- mkdocs.yml | 2 + 8 files changed, 542 insertions(+), 446 deletions(-) create mode 100644 docs/explanation/equivalences.md create mode 100644 docs/explanation/references.md diff --git a/docs/explanation/accuracy.md b/docs/explanation/accuracy.md index 8b3d3bf53..cc1cbd3c3 100644 --- a/docs/explanation/accuracy.md +++ b/docs/explanation/accuracy.md @@ -24,7 +24,7 @@ is not a whole-facility footprint, and it is not meant to be one. CodeCarbon prefers hardware energy counters and falls back to estimation when no counter is readable. The fallback order is documented in -[Methodology → CPU metrics priority](methodology.md#cpu-metrics-priority). +[Methodology → CPU metrics priority](methodology.md#which-backend-gets-chosen). | Backend | How it works | Agreement with a reference | When it is used | |---|---|---|---| diff --git a/docs/explanation/alternatives.md b/docs/explanation/alternatives.md index db712f2b4..e2b911801 100644 --- a/docs/explanation/alternatives.md +++ b/docs/explanation/alternatives.md @@ -126,7 +126,7 @@ Summarised, since the sections above are organised around other tools: - **Breadth of measurement backends** with a documented fallback order — RAPL, NVML, amdsmi, `powermetrics`, Windows EMI, then TDP-based estimation. See - [CPU metrics priority](methodology.md#cpu-metrics-priority). + [CPU metrics priority](methodology.md#which-backend-gets-chosen). - **Task-level attribution** within a single process. - **Offline mode** (`OfflineEmissionsTracker`) for air-gapped machines, with no no calls to the CodeCarbon API. diff --git a/docs/explanation/equivalences.md b/docs/explanation/equivalences.md new file mode 100644 index 000000000..0c5437601 --- /dev/null +++ b/docs/explanation/equivalences.md @@ -0,0 +1,71 @@ +# Equivalences + +The `carbonboard` dashboard translates a project's emissions into three +everyday comparisons. They are presentation aids, not part of the measurement: +nothing in the CSV output, the API payload or the emissions calculation depends +on them. + +All three factors live in +[`codecarbon/viz/data.py`](https://github.com/mlco2/codecarbon/blob/master/codecarbon/viz/data.py) +and are used by `viz/carbonboard.py` and `viz/carbonboard_on_api.py`. + +| Comparison | Factor | Applied as | Source | +|---|---|---|---| +| Car travel | **0.409 kg CO₂e per mile** | `emissions_kg / 0.409` → miles driven (`data.py:65`) | US EPA | +| Television | **0.097 kg CO₂ per hour** | `emissions_kg / 0.097` → hours of TV (`data.py:76`) | unsourced in code | +| US household | **160.58 kg CO₂ per week** | `emissions_kg / 160.58 × 100` → % of a household-week (`data.py:96`) | US EPA | + +## How each factor is derived + +The derivations below are reproduced from the docstrings in `viz/data.py`; they +are the only justification the code carries. + +**Car — 0.409 kg CO₂e/mile** (`data.py:54-65`) + +```text +8.89 × 10⁻³ metric tons CO₂ per gallon of gasoline + × 1 / 22.0 miles per gallon (car/truck average) + × 1 CO₂, CH₄ and N₂O / 0.988 CO₂ += 4.09 × 10⁻⁴ metric tons CO₂e per mile += 0.409 kg CO₂e per mile +``` + +This is the US EPA passenger-vehicle figure, so it reflects the US vehicle +fleet and US fuel. It is not a European or global average, and the unit is +**miles**, not kilometres. + +**Television — 0.097 kg CO₂/hour** (`data.py:67-76`) + +Described in the code only as the ratio for "a 32-inch LCD flat screen TV". +**No source, screen power, or grid intensity is given in the code**, so the +figure cannot be reproduced from what ships in the repository. Treat it as an +illustrative round number rather than a defensible factor. + +**US household — 160.58 kg CO₂/week** (`data.py:86-96`) + +```text +5.734 t CO₂ electricity ++ 2.06 t CO₂ natural gas ++ 0.26 t CO₂ liquid petroleum gas ++ 0.30 t CO₂ fuel oil += 8.35 t CO₂ per home per year +÷ 52 weeks += 160.58 kg CO₂ per week +``` + +Again a US EPA figure for total *home energy* use — heating fuels included, not +electricity alone — and specific to an average US home. + +## Caveats + +- All three factors are US-centric averages. A reader outside the US should + expect the comparison to be directionally useful and quantitatively off. +- The factors are hardcoded and not versioned against a dated source release, + so they drift out of date silently as the underlying EPA figures are revised. +- They are applied to the emissions total *after* the estimation chain + described in the [methodology](methodology.md), so every uncertainty in that + chain carries through unchanged. + +If you need a comparison you can defend, compute it yourself from the +`emissions` column of the [CSV output](../reference/output.md) using a factor +you can cite. diff --git a/docs/explanation/faq.md b/docs/explanation/faq.md index afd207a7d..03adb41ca 100644 --- a/docs/explanation/faq.md +++ b/docs/explanation/faq.md @@ -69,7 +69,7 @@ In a single Python process, the first tracker pays a one-time cost to detect har ## What hardware does CodeCarbon support? -CodeCarbon supports various CPU architectures, GPUs, and cloud providers. For details on measurement priority and supported hardware, see the [Methodology](methodology.md#cpu-metrics-priority) page. +CodeCarbon supports various CPU architectures, GPUs, and cloud providers. For details on measurement priority and supported hardware, see the [Methodology](methodology.md#which-backend-gets-chosen) page. ## How do I report a bug? diff --git a/docs/explanation/methodology.md b/docs/explanation/methodology.md index 58f37f8da..62f2638ad 100644 --- a/docs/explanation/methodology.md +++ b/docs/explanation/methodology.md @@ -1,498 +1,425 @@ # Methodology -Carbon dioxide (CO₂) emissions, expressed as kilograms of -CO₂-equivalents (CO₂eq), are the product of two main factors: +This page is the reference for how CodeCarbon turns a running process into a +number of kilograms of CO₂. It is deliberately detailed: every constant is +named with its value, and every fallback is stated with the condition under +which it applies, so that you can check a figure against the source. -``` text -C = Carbon Intensity of the electricity consumed for computation: quantified as g of CO₂ emitted per kilowatt-hour of electricity. +If you want the deviation figures rather than the mechanism, see +[Accuracy and validation](accuracy.md). The bibliography lives on +[References](references.md). -E = Energy Consumed by the computational infrastructure: quantified as kilowatt-hours. -``` - -Carbon dioxide emissions (CO₂eq) can then be calculated as `C * E` - -## Carbon Intensity - -Carbon Intensity of the consumed electricity is calculated as a weighted -average of the emissions from the different energy sources that are used -to generate electricity, including fossil fuels and renewables. In this -toolkit, the fossil fuels coal, petroleum, and natural gas are -associated with specific carbon intensities: a known amount of carbon -dioxide is emitted for each kilowatt-hour of electricity generated. -Renewable or low-carbon fuels include solar power, hydroelectricity, -biomass, geothermal, and more. The nearby energy grid contains a mixture -of fossil fuels and low-carbon energy sources, called the Energy Mix. -Based on the mix of energy sources in the local grid, the Carbon -Intensity of the electricity consumed can be computed. - -![Grid Energy Mix](../images/grid_energy_mix.png){.align-center width="350px" height="300px"} - -When available, CodeCarbon uses global carbon intensity of electricity -per cloud provider ( -[here](https://github.com/mlco2/codecarbon/blob/master/codecarbon/data/cloud/impact.csv)) -or per country ( -[here](https://github.com/mlco2/codecarbon/blob/master/codecarbon/data/private_infra/global_energy_mix.json) -). +## The shape of the calculation -If we don't have the global carbon intensity or electricity of a -country, but we have its electricity mix, we used to compute the carbon -intensity of electricity using this table: +Emissions, expressed in kilograms of CO₂-equivalent (CO₂eq), are the product of +two factors: -| Energy Source | Carbon Intensity (kg/MWh) | -|---------------|---------------------------| -| Coal | 995 | -| Petroleum | 816 | -| Natural Gas | 743 | -| Geothermal | 38 | -| Hydroelectricity | 26 | -| Nuclear | 29 | -| Solar | 48 | -| Wind | 26 | - -*Carbon Intensity Across Energy Sources* - -Sources: - -- [for fossil energies](https://github.com/responsibleproblemsolving/energy-usage#conversion-to-co2) -- [for renewables energies](http://www.world-nuclear.org/uploadedFiles/org/WNA/Publications/Working_Group_Reports/comparison_of_lifecycle.pdf) - -Then, for example, if the Energy Mix of the Grid Electricity is 25% -Coal, 35% Petroleum, 26% Natural Gas and 14% Nuclear: - -``` text -Net Carbon Intensity = 0.25 * 995 + 0.35 * 816 + 0.26 * 743 + 0.14 * 29 = 731.59 kgCO₂/kWh +```text +E = Energy consumed by the computational infrastructure, in kilowatt-hours +C = Carbon intensity of that electricity, in g CO₂ emitted per kilowatt-hour ``` -But it doesn't happen anymore because Our World in Data now provides -the global carbon intensity of electricity per country ( -[source](https://ourworldindata.org/grapher/carbon-intensity-electricity#explore-the-data) -). Some countries are missing data for last year, so we use the previous -year data available. - -If ever we have neither the global carbon intensity of a country nor -its electricity mix, we apply a world average of 475 gCO2.eq/KWh ( -[source](https://www.iea.org/reports/global-energy-co2-status-report-2019/emissions) -). - -As you can see, we try to be as accurate as possible in estimating -carbon intensity of electricity. Still there is room for improvement and -all contributions are welcome. - -## Power Usage - -Power supply to the underlying hardware is tracked at frequent time -intervals. This is a configurable parameter `measure_power_secs`, with -default value 15 seconds, that can be passed when instantiating the -emissions tracker. - -CodeCarbon focuses on the main compute components it can measure or -estimate directly: CPU, GPU, and RAM. It does not separately model disk -I/O, network transfers, displays, cooling, or other peripherals because -those sources are usually much smaller, and often negligible, for local -code-level experiments. They are also not exposed through the same -low-overhead measurement interfaces as CPU, GPU, and RAM. However, they -can matter for workloads dominated by data movement, storage, or -distributed systems. - -Currently, the package supports the following hardware infrastructure. - -### Tracking Modes +```text +Emissions (kgCO₂eq) = E × C +``` -CodeCarbon operates in two distinct modes to determine how power consumption is attributed to your work. Choosing the right mode is essential for data accuracy. +CodeCarbon computes `E` by sampling CPU, GPU and RAM power at a fixed interval +and integrating over time, and resolves `C` from the machine's location or +cloud region. Both halves have fallbacks, and which fallback ran is what +determines how much you should trust the result. -The `tracking_mode` parameter (values: `"machine"` or `"process"`, default `"machine"`) controls the **scope** of power attribution: +| Half of the formula | Best case | Worst case | +|---|---|---| +| `E` — energy | Hardware energy counters (RAPL, EMI, NVML) | A CPU model's catalogue TDP scaled by CPU load | +| `C` — carbon intensity | Live grid intensity from Electricity Maps | The 475 gCO₂eq/kWh world average | -**Machine Mode** (`tracking_mode="machine"`): Measures the total energy consumed by the whole hardware stack (all CPUs, GPUs, and RAM). This is the most straightforward measurement and is ideal for dedicated machines where the tracked workload dominates resource usage. +Nothing in the output states which case applied to `C`. For `E`, the log line +emitted at startup names the CPU backend. -**Process Mode** (`tracking_mode="process"`): Estimates the energy attributable to your Python process (and its child processes) by sampling their CPU time relative to total CPU capacity. This is a software-based approximation — it does **not** read hardware counters directly — and is preferable on shared environments where other workloads are running in parallel. +## Energy: what is measured, what is modelled -> ⚠️ **GPU limitation**: Process Mode only affects CPU and RAM attribution. GPU power is always measured at the device level, so if you share a GPU with other users or processes, CodeCarbon will still account for the **entire GPU's** power consumption, not just your share. +Power is sampled at `measure_power_secs`, default 15 seconds, configurable when +instantiating the tracker. Energy is the integral of power over time: +`Energy = Power × Time`. -Note: The underlying measurement method (Intel RAPL, Windows Energy Meter Interface, Intel Power Gadget, TDP-based CPU-load estimation…) is chosen automatically based on hardware availability and software permissions. It applies independently of the tracking mode. +| Component | Best available source | Modelled fallback | +|---|---|---| +| CPU | Intel RAPL (Linux), Energy Meter Interface (Windows 11), `powermetrics` (macOS), Intel Power Gadget (legacy) | TDP × CPU load, or a flat constant | +| GPU | NVML via `nvidia-ml-py` — a direct device reading | none; without NVML, GPU is not counted | +| RAM | none — there is no RAM power counter | always modelled from an estimated DIMM count | -### GPU +CodeCarbon does not separately model disk I/O, network transfer, displays, +cooling or other peripherals. Those are usually small for local, code-level +experiments, and they are not exposed through the same low-overhead interfaces. +They can matter for workloads dominated by data movement, storage or +distributed communication. -Tracks Nvidia GPUs energy consumption using `nvidia-ml-py` library -(installed with the package). +### Tracking modes -### RAM +The `tracking_mode` parameter (`"machine"` or `"process"`, default +`"machine"`) controls the **scope** of attribution, independently of which +measurement backend was selected. -CodeCarbon v2 uses a 3 Watts for 8 GB ratio -[source](https://www.crucial.com/support/articles-faq-memory/how-much-power-does-memory-use) -. +**Machine mode** measures the whole hardware stack. It is the straightforward +reading, and the right one on a dedicated machine. -But this is not a good measure because it doesn't take into account the -number of RAM slots used in the machine, that really drive the power -consumption, not the amount of RAM. For example, in servers you could -have thousands of GB of RAM but the power consumption would not be -proportional to the amount of memory used, but to the number of memory -modules used. +**Process mode** estimates the share attributable to your Python process and +its children, by sampling their CPU time against total CPU capacity. This is a +software approximation — it does **not** read per-process hardware counters — +and is preferable on shared machines. -Old machine could use 2 Mb memory stick, where modern servers will use -128 Mb memory stick. +!!! warning "GPU is always machine-wide" -So, in CodeCarbon v3 we switch to using 5 Watts for each RAM slot. The -energy consumption is calculated as follows: + Process mode affects CPU and RAM attribution only. GPU power is measured at + the device level, so on a shared GPU CodeCarbon still attributes the + **entire GPU's** consumption to your run. -``` text -RAM Power Consumption = 5 Watts * Number of RAM slots used -``` +### Power Usage Effectiveness (PUE) -But getting the number of RAM slots used is not possible as you need -root access to get the number of RAM slots used. So we use an heuristic -based on the RAM size. - -For example keep a minimum of 2 modules. Except for ARM CPU like -Raspberry Pi where we will consider a 3W constant. Then consider the max -RAM per module is 128GB and that RAM module only exist in power of 2 (2, -4, 8, 16, 32, 64, 128). So we can estimate the power consumption of the -RAM by the number of modules used. - -- For ARM CPUs (like Raspberry Pi), a constant 3W will be used as the - minimum power -- Base power per DIMM is 5W for x86 systems and 1.5W for ARM systems -- For standard systems (up to 4 DIMMs): linear scaling at full power - per DIMM -- For medium systems (5-8 DIMMs): decreasing efficiency (90% power per - additional DIMM) -- For large systems (9-16 DIMMs): further reduced efficiency (80% - power per additional DIMM) -- For very large systems (17+ DIMMs): highest efficiency (70% power - per additional DIMM) -- Ensures at least 10W for x86 systems (assuming 2 DIMMs at minimum) -- Ensures at least 3W for ARM systems - -Example Power Estimates: - -- **Small laptop (8GB RAM)**: ~10W (2 DIMMs at 5W each) -- **Desktop (32GB RAM)**: ~20W (4 DIMMs at 5W each) -- **Desktop (64GB RAM)**: ~20W (4 DIMMs at 5W each), the same as 32GB -- **Small server (128GB RAM)**: ~40W (8 DIMMs with efficiency - scaling) -- **Large server (1TB RAM)**: ~40W (using 8x128GB DIMMs with high - efficiency scaling) - -This approach significantly improves the accuracy for large servers by -recognizing that RAM power consumption doesn't scale linearly with -capacity, but rather with the number of physical modules. Since we -don't have direct access to the actual DIMM configuration, this -heuristic provides a more reasonable estimate than the previous linear -model. - -If you know the exact RAM power consumption of your system, then provide -it using the `force_ram_power` parameter, which will -override the automatic estimation. - -For example, in a Ubuntu machine, you can get the number of RAM slots -used with the following command: - -``` bash -sudo lshw -C memory -short | grep DIMM +If you set `pue`, it is applied inside the measurement loop to **each +component's energy** before accumulation +([`emissions_tracker.py:1194`](https://github.com/mlco2/codecarbon/blob/master/codecarbon/emissions_tracker.py#L1194)): -/0/37/0 memory 4GiB DIMM DDR4 Synchrone Unbuffered (Unregistered) 2400 MHz (0,4 ns) -/0/37/1 memory 4GiB DIMM DDR4 Synchrone Unbuffered (Unregistered) 2400 MHz (0,4 ns) -/0/37/2 memory 4GiB DIMM DDR4 Synchrone Unbuffered (Unregistered) 2400 MHz (0,4 ns) -/0/37/3 memory 4GiB DIMM DDR4 Synchrone Unbuffered (Unregistered) 2400 MHz (0,4 ns) +```python +energy *= self._pue ``` -Here we count 4 RAM slots used, so the power consumption will be 4 x 5 = -20 Watts, just add `force_ram_power=20` to the init of -CodeCarbon. - -### CPU - -- **On Windows** - -Tracks Intel and AMD processor energy consumption using the [Energy -Meter Interface -(EMI)](https://learn.microsoft.com/en-us/windows-hardware/drivers/powermeter/energy-meter-interface), -through which Windows 11 exposes the CPU RAPL energy counters (the same -hardware counters CodeCarbon reads on Linux). It is built into the OS: -no third-party driver, no administrator rights and no extra dependency -are needed. - -*Note*: EMI reports CPU power only on Windows 11 running on bare metal -(on Windows 10, only on devices with dedicated metering hardware, such -as the Surface Book). On virtual machines or older Windows versions, -CodeCarbon falls back to the CPU-load estimation mode described below. - -*Note*: as on Linux, only package channels are measured. Windows exposes -one EMI device per metered component, so a CPU shows up as one device per -core (`RAPL_Package0_Core3_CORE`) next to the device holding the package -channel (`RAPL_Package0_PKG`). Those per-core channels, like `PP0`/`PP1`, -are subdomains of the package: measuring both would count the same energy -twice, so CodeCarbon keeps the package channels only. On multi-die CPUs -where every die mirrors the same socket-wide counter, the duplicates are -detected and dropped as well. The `DRAM` channels are excluded too, unless -the +This means `cpu_energy`, `gpu_energy` and `ram_energy` in the CSV are **already +inflated by the PUE**, not just `energy_consumed` and `emissions`. Do not +multiply by PUE a second time when reusing the per-component columns. Water +consumption is derived from the post-PUE energy in the same loop. + +## CPU + +### Which backend gets chosen + +This is the part of CodeCarbon most often misread. The selection logic is in +[`resource_tracker.py:249-279`](https://github.com/mlco2/codecarbon/blob/master/codecarbon/core/resource_tracker.py#L249), +and it is evaluated strictly in this order — the first row that applies wins. + +| # | Condition | Result | Source | +|---|---|---|---| +| 1 | `force_cpu_power` is set | Fixed constant power. **Every platform backend is skipped** — RAPL is not consulted even if it is available. | `resource_tracker.py:255-260`, guard at `:272` | +| 2 | `force_mode_cpu_load` is set, `psutil` present, and a TDP is known | `cpu_load` mode | `resource_tracker.py:263-270` | +| 3 | Linux **and** RAPL files readable | `intel_rapl` — hardware energy counters | `resource_tracker.py:223-225` | +| 4 | macOS **and** Apple Silicon **and** `psutil` present | `cpu_load` mode | `resource_tracker.py:228-230` | +| 5 | macOS **and** Apple Silicon **and** no `psutil` **and** `powermetrics` usable | `powermetrics` | `resource_tracker.py:231-233` | +| 6 | macOS **and** Intel **and** Intel Power Gadget installed | `intel_power_gadget` | `resource_tracker.py:234-236` | +| 7 | macOS **and** Intel **and** `powermetrics` usable | `powermetrics` | `resource_tracker.py:237-239` | +| 8 | Windows **and** EMI available | `windows_emi` — hardware energy counters | `resource_tracker.py:241-243` | +| 9 | Windows **and** Intel Power Gadget installed | `intel_power_gadget` | `resource_tracker.py:244-246` | +| 10 | none of the above, `psutil` present | `cpu_load` mode | `resource_tracker.py:179-190`, `:201-212` | +| 11 | none of the above, no `psutil` | `constant` mode | `resource_tracker.py:191-198`, `:213-218` | + +Two consequences worth stating plainly, because they surprise people: + +- **`force_cpu_power` disables hardware measurement.** It is not a hint or a + ceiling. Setting it on a machine with working RAPL replaces a real counter + with your constant. +- **On Apple Silicon, `powermetrics` is effectively unreachable.** + `psutil` is a hard dependency of CodeCarbon, so row 4 fires before row 5 in + every normal installation. The `sudo`-granting instructions that used to + appear here describe a path the code no longer takes. + +### The fallback constants + +When no hardware counter is available, CodeCarbon needs a power figure. It gets +one from the following ladder, in +[`core/cpu.py:1002-1032`](https://github.com/mlco2/codecarbon/blob/master/codecarbon/core/cpu.py#L1002): + +| Situation | TDP used | Constant | Source | +|---|---|---|---| +| CPU model detected and present in the TDP registry (2000+ Intel and AMD parts) | the registry value | — | `cpu.py:1005-1012` | +| CPU model detected but **absent** from the registry, `psutil` present | `threads × 4 W` | `DEFAULT_POWER_PER_CORE = 4` | [`cpu.py:29`](https://github.com/mlco2/codecarbon/blob/master/codecarbon/core/cpu.py#L29), used at `:1024` | +| CPU model absent from the registry and no `psutil`, or the model could not be detected at all | `85 W` | `POWER_CONSTANT = 85` | [`hardware.py:23`](https://github.com/mlco2/codecarbon/blob/master/codecarbon/external/hardware.py#L23), applied at `:457` | + +Whatever TDP results is multiplied by the physical CPU package count +(`resource_tracker.py:267`, `:278`) to give the machine-level ceiling. + +That ceiling is then turned into an instantaneous power reading in one of two +ways, and **the two ways are not the same**: + +- **`cpu_load` mode** applies a cubic load curve with a 10% floor + ([`hardware.py:287-288`](https://github.com/mlco2/codecarbon/blob/master/codecarbon/external/hardware.py#L287)): + + ```text + load_factor = 0.1 + 0.9 × (cpu_load_percent / 100)³ + power = TDP × load_factor + ``` + + An idle machine therefore reports 10% of TDP, and a fully loaded one + reports 100%. There is no 50% anywhere in this path. + +- **`constant` mode** — reached only when `psutil` is unavailable — applies a + flat half of TDP + ([`hardware.py:363`](https://github.com/mlco2/codecarbon/blob/master/codecarbon/external/hardware.py#L363)): + + ```text + power = TDP × CONSUMPTION_PERCENTAGE_CONSTANT # CONSUMPTION_PERCENTAGE_CONSTANT = 0.5 + ``` + + `CONSUMPTION_PERCENTAGE_CONSTANT = 0.5` is defined at + [`hardware.py:26`](https://github.com/mlco2/codecarbon/blob/master/codecarbon/external/hardware.py#L26). + +The cubic curve is an asserted shape, not a fitted one; the code cites no +source for the exponent or the 10% floor. +[Accuracy and validation](accuracy.md#the-cpu-load-tdp-fallback-measured) +reports how far this fallback lands from measured RAPL energy. + +### Per-platform notes + +**Linux.** Energy is read from Intel RAPL files under +`/sys/class/powercap/intel-rapl/subsystem` +([Weaver](https://web.eece.maine.edu/~vweaver/projects/rapl/)). Every CPU listed +there is tracked. The files must exist *and* be readable by the running user; +on many distributions they are root-only by default. Despite the "Intel RAPL" +name, AMD processors are supported since Linux kernel 5.8. See +[RAPL Metrics](rapl.md) for the details. + +**Windows.** Intel and AMD CPU energy is read through the +[Energy Meter Interface (EMI)](https://learn.microsoft.com/en-us/windows-hardware/drivers/powermeter/energy-meter-interface), +through which Windows 11 exposes the same RAPL hardware counters CodeCarbon +reads on Linux. EMI is built into the OS: no third-party driver, no +administrator rights, no extra dependency. + +EMI reports CPU power only on Windows 11 running on bare metal (on Windows 10, +only on devices with dedicated metering hardware such as the Surface Book). On +virtual machines or older Windows, CodeCarbon falls back to the CPU-load +estimation above. + +As on Linux, only package channels are measured. Windows exposes one EMI device +per metered component, so a CPU shows up as one device per core +(`RAPL_Package0_Core3_CORE`) next to the device holding the package channel +(`RAPL_Package0_PKG`). Those per-core channels, like `PP0`/`PP1`, are +subdomains of the package: measuring both would count the same energy twice, so +CodeCarbon keeps the package channels only. On multi-die CPUs where every die +mirrors the same socket-wide counter, duplicates are detected and dropped. +`DRAM` channels are excluded too, unless [`rapl_include_dram`](../how-to/configuration.md#including-dram-in-the-cpu-measurement) -option is enabled. - -Legacy support for `Intel Power Gadget` is kept for machines where it is -still installed, but the tool has been -[discontinued by Intel](https://www.intel.com/content/www/us/en/developer/articles/tool/power-gadget.html) -(known limitation, tracked in -[issue #457](https://github.com/mlco2/codecarbon/issues/457)). - -- **On Mac (Intel)** - -Tracks Intel processors energy consumption using the -`Intel Power Gadget`. You need to install it yourself from this -[source](https://www.intel.com/content/www/us/en/developer/articles/tool/power-gadget.html) -. Intel has since discontinued the tool; this is a known limitation, -tracked in [issue #457](https://github.com/mlco2/codecarbon/issues/457). - -- **Apple Silicon Chips (M1, M2, M3, ...)** - -Apple Silicon Chips contain both the CPU and the GPU. - -Codecarbon tracks Apple Silicon Chip energy consumption using -`powermetrics`. It should be available natively on any mac. However, -this tool is only usable with `sudo` rights and to our current -knowledge, there are no other options to track the energy consumption of -the Apple Silicon Chip without administrative rights. This is a known -limitation; if you know of any solution, please -[open an issue](https://github.com/mlco2/codecarbon/issues) with your -proposed approach. - -To give sudo rights without having to enter a password each time, you -can modify the sudoers file with the following command: - -``` bash -sudo visudo -``` - -Then add the following line at the end of the file: - -``` bash -username ALL = (root) NOPASSWD: /usr/bin/powermetrics -``` - -If you do not want to give sudo rights to your user, then CodeCarbon -will fall back to constant mode to measure CPU energy consumption. - -- **On Linux** - -Tracks Intel and AMD processor energy consumption from Intel RAPL files -at `/sys/class/powercap/intel-rapl/subsystem` ( -[reference](https://web.eece.maine.edu/~vweaver/projects/rapl/) ). All -CPUs listed in this directory will be tracked. - -*Note*: The Power Consumption will be tracked only if the RAPL files -exist at the above-mentioned path and if the user has the necessary -permissions to read them. - -## CPU hardware - -The CPU die is the processing unit itself. It's a piece of -semiconductor that has been sculpted/etched/deposited by various -manufacturing processes into a net of logic blocks that do stuff that -makes computing possible. The processor package is what you get when you -buy a single processor. It contains one or more dies, plastic/ceramic -housing for dies and gold-plated contacts that match those on your -motherboard. - -In Linux kernel, energy_uj is a current energy counter in micro joules. -It is used to measure CPU core's energy consumption. - -Micro joules is then converted in kWh, with formula `kWh=energy * 10** (-6) * 2.77778e-7`. - -For example, on a laptop with Intel(R) Core(TM) i7-7600U, Code Carbon -will read two files : -/sys/class/powercap/intel-rapl/intel-rapl:1/energy_uj and -/sys/class/powercap/intel-rapl/intel-rapl:0/energy_uj - -## RAPL Metrics - -RAPL (Running Average Power Limit) is a feature of modern processors -that provides energy consumption measurements through hardware counters. - -For the primary sources, see Khan et al., *RAPL in Action: Experiences in -Using RAPL for Power Measurements*, and Weaver's -[Reading RAPL energy measurements from Linux](https://web.eece.maine.edu/~vweaver/projects/rapl/) -(both listed under [References](#references)). This +is enabled. + +**macOS, Intel.** Uses `Intel Power Gadget`, which you must install yourself. +Intel has +[discontinued the tool](https://www.intel.com/content/www/us/en/developer/articles/tool/power-gadget.html); +this is a known limitation, tracked in +[issue #457](https://github.com/mlco2/codecarbon/issues/457) — the issue is the +tracking record, not a source. + +**macOS, Apple Silicon.** The Apple Silicon chip contains both CPU and GPU, and +`powermetrics` can read both, but it requires `sudo` and — as row 4 of the table +above shows — `cpu_load` mode is selected first whenever `psutil` is installed, +which is the normal case. There is no known way to read Apple Silicon energy +without administrative rights; if you know of one, please +[open an issue](https://github.com/mlco2/codecarbon/issues). + +**Legacy Intel Power Gadget** is retained on all platforms for machines where +it is still installed, but it should not be relied on for new setups. + +### CPU hardware background + +The CPU die is the processing unit itself: a piece of semiconductor etched into +logic blocks. The processor *package* is what you buy — one or more dies, their +housing, and the contacts that match your motherboard. RAPL and EMI report at +the package level, which is why CodeCarbon deduplicates subdomain channels. + +On Linux, `energy_uj` is a running energy counter in microjoules. CodeCarbon +converts it to kWh with `kWh = energy × 10⁻⁶ × 2.77778e-7`. On a laptop with an +Intel Core i7-7600U, for example, CodeCarbon reads +`/sys/class/powercap/intel-rapl/intel-rapl:0/energy_uj` and +`.../intel-rapl:1/energy_uj`. + +For how a rolling energy counter becomes a power figure, see +[Power Estimation](power-estimation.md). For the primary literature on RAPL's +accuracy, see Khan et al. and Weaver on the [References](references.md) page; +this [blog post](https://blog.chih.me/read-cpu-power-with-RAPL.html) is a useful -informal walkthrough. - -Despite the name "Intel RAPL", it supports AMD processors since Linux -kernel 5.8. - -Read more about how we use it in [RAPL Metrics](rapl.md). - -## CPU metrics priority - -CodeCarbon will first try to read the energy consumption of the CPU from -a low level interface like RAPL (on Linux), the Energy Meter Interface -(on Windows 11) or `powermetrics` (on macOS). If none of the tracking -tools are available, CodeCarbon will be switched to a fallback mode: - -- It will first detect which CPU hardware is currently in use, and - then map it to a data source listing 2000+ Intel and AMD CPUs and - their corresponding thermal design powers (TDPs). -- If the CPU is not found in the data source, a global constant will - be applied. -- If `psutil` is available, CodeCarbon will try to estimate the energy - consumption from the TDP and the CPU load. -- CodeCarbon assumes that 50% of the TDP will be the average power - consumption to make this approximation. - -Here is a drawing of the fallback mode: - -![CPU Fallback](../images/cpu_fallback.png){.align-center} - -The code doing this is available in -[codecarbon/core/resource_tracker.py](https://github.com/mlco2/codecarbon/blob/master/codecarbon/core/resource_tracker.py#L24). - -The net Energy Used is the net power supply consumed during the compute -time, measured as `kWh`. - -We compute energy consumption as the product of the power consumed and -the time the power was consumed for. The formula is: -`Energy = Power * Time` - -## How CodeCarbon Works - -CodeCarbon uses a scheduler that, by default, calls for a measure every -15 seconds, so it has no significant overhead. - -The measure itself is fast and CodeCarbon is designed to be as light as -possible with a small memory footprint. - -The scheduler is started when the first `start` method is called and -stopped when `stop` method is called. - -Another scheduler (`scheduler_monitor_power`) -is used to monitor only the power consumption of the hardware every -second. It is needed for hardware that do not have energy counters but -only instant power, like in CPU load mode. - -## Estimation of Equivalent Usage Emissions - -The CodeCarbon dashboard provides equivalent emissions and energy usage -comparisons to help users better understand the carbon impact of their -activities. These comparisons are based on the following assumptions: - -### Car Usage - -- **Emission factor**: *0.12 kgCO₂ per kilometer driven*. -- This value is derived from the average emissions of a European - passenger car under normal driving conditions. - -Source : [European Environment -Agency](https://co2cars.apps.eea.europa.eu/?source=%7B%22track_total_hits%22%3Atrue%2C%22query%22%3A%7B%22bool%22%3A%7B%22must%22%3A%5B%7B%22constant_score%22%3A%7B%22filter%22%3A%7B%22bool%22%3A%7B%22must%22%3A%5B%7B%22bool%22%3A%7B%22should%22%3A%5B%7B%22term%22%3A%7B%22year%22%3A2023%7D%7D%5D%7D%7D%2C%7B%22bool%22%3A%7B%22should%22%3A%5B%7B%22term%22%3A%7B%22scStatus%22%3A%22Provisional%22%7D%7D%5D%7D%7D%5D%7D%7D%7D%7D%5D%7D%7D%2C%22display_type%22%3A%22tabular%22%7D) - -### TV Usage - -- **Energy consumption**: *138 Wh per day based on average use*. -- This assumes: - - An average daily usage of 6.5 hours. - - A modern television with a power consumption of approximately - *21.2 W per hour*. - -Source : [The French Agency for Ecological -Transition](https://agirpourlatransition.ademe.fr/particuliers/maison/economies-denergie-deau/electricite-combien-consomment-appareils-maison) - -### US Citizen Weekly Emissions - -- **Annual emissions**: *13.3 tons of CO₂ equivalent per year* for an - average US citizen. -- **Weekly emissions**: This value is divided by the 52 weeks in a - year to estimate weekly emissions: - -$$\text{Weekly Emissions} = \frac{\text{Annual Emissions (tons)}}{52}$$ - -$$\text{Weekly Emissions} = \frac{13.3}{52} \approx 0.256 \, \text{tons of CO₂ equivalent per week.}$$ - -Source : [IEA CO2 total emissions per capita by region, -2000-2023](https://www.iea.org/data-and-statistics/charts/co2-total-emissions-per-capita-by-region-2000-2023) +informal walkthrough but is secondary to both. + +## GPU + +Nvidia GPU energy is read through `nvidia-ml-py` (installed with the package), +which queries NVML — a direct device reading, not a model. There is no fallback: +if NVML is unavailable, GPU energy is not counted at all rather than estimated. +On Apple Silicon under `powermetrics`, the integrated GPU is reported as a +separate `AppleSiliconChip` device. + +## RAM + +**The RAM model is a heuristic, and the code cites no source for its central +constant.** This section says so explicitly because the figure it produces is +often a large share of a low-power machine's total. + +There is no hardware counter for RAM power on the platforms CodeCarbon +supports, and reading the actual DIMM configuration needs root. So CodeCarbon +runs a two-stage estimate in +[`external/ram.py:82-193`](https://github.com/mlco2/codecarbon/blob/master/codecarbon/external/ram.py#L82). + +**Stage 1 — guess the DIMM count** from total RAM, using a hardcoded step +function (`ram.py:82-139`): + +| Total RAM | Assumed DIMMs | +|---|---| +| ≤ 2 GB | 1 | +| ≤ 16 GB | 2 | +| 17–64 GB | 4 | +| 65–128 GB | 8 | +| > 128 GB | `ceil(total_GB / largest_fitting_DIMM_size)`, capped at 32 | + +**Stage 2 — assign power per DIMM** with a marginal-efficiency taper +(`ram.py:141-193`): + +| Parameter | Value | Source | +|---|---|---| +| Base power per DIMM, x86 | `RAM_SLOT_POWER_X86 = 5` W | [`ram.py:14`](https://github.com/mlco2/codecarbon/blob/master/codecarbon/external/ram.py#L14) — **unsourced** | +| Base power per DIMM, ARM | 1.5 W | `ram.py:158` — **unsourced** | +| DIMMs 1–4 | 100% of base each | `ram.py:168-170` | +| DIMMs 5–8 | 90% of base each | `ram.py:171-175` | +| DIMMs 9–16 | 80% of base each | `ram.py:176-182` | +| DIMMs 17+ | 70% of base each | `ram.py:183-190` | +| Minimum, x86 | 10 W (2 DIMMs × 5 W) | `ram.py:165`, applied `:193` | +| Minimum, ARM | 3 W | `ram.py:160`, applied `:193` | + +The only citation anywhere in the module is a pre-v3 +[Crucial FAQ](https://www.crucial.com/support/articles-faq-memory/how-much-power-does-memory-use) +recording the *old* 3 W-per-8 GB rule (`ram.py:20-22`), which the current model +replaced. **Neither the 5 W figure nor any of the 0.9/0.8/0.7 multipliers has a +source in the code.** They are asserted values that appear reasonable, not +measurements. + +The change from v2's 3 W-per-8 GB to per-DIMM power is nonetheless directionally +right: RAM power tracks the number of physical modules, not the number of +gigabytes. A server with 1 TB across 8 DIMMs does not draw 128× a laptop's RAM +power. Worked examples from the model above: + +- 8 GB laptop → 2 DIMMs → **10 W** +- 32 GB desktop → 4 DIMMs → **20 W** +- 64 GB desktop → 4 DIMMs → **20 W** (identical to 32 GB) +- 128 GB server → 8 DIMMs → **38 W** +- 1 TB server → 8 DIMMs → **38 W** (identical to 128 GB) + +**If you can measure or look up your real configuration, do so and override the +estimate.** On Linux: + +```bash +sudo lshw -C memory -short | grep DIMM -### Calculation Formula +/0/37/0 memory 4GiB DIMM DDR4 Synchrone Unbuffered (Unregistered) 2400 MHz +/0/37/1 memory 4GiB DIMM DDR4 Synchrone Unbuffered (Unregistered) 2400 MHz +/0/37/2 memory 4GiB DIMM DDR4 Synchrone Unbuffered (Unregistered) 2400 MHz +/0/37/3 memory 4GiB DIMM DDR4 Synchrone Unbuffered (Unregistered) 2400 MHz +``` -The equivalent emissions are calculated using this formula: +Four slots, so pass `force_ram_power=20` (4 × 5 W) — or better, a wattage you +have measured — to the tracker. `force_ram_power` bypasses the whole heuristic. -$$\text{Equivalent Emissions} = \frac{\text{Total Emissions (kgCO₂)}}{\text{Emission Factor (kgCO₂/unit)}}$$ +## Carbon intensity -For example: +Carbon intensity is the weighted average emissions of the energy sources +feeding the grid the machine is drawing from. Fossil fuels — coal, petroleum, +natural gas — carry high intensities; solar, hydro, wind, nuclear, biomass and +geothermal carry low ones. The local mix determines the figure. -- **Car Usage**: *1 kWh* of energy consumption is approximately - equivalent to: - - *8.33 kilometers driven by a car* (*1 ÷ 0.12*). - - *11.9 hours of TV usage* (*1 ÷ 0.084*), if emissions are - considered. -- **US Citizen Emissions**: - - *1 kWh* of energy consumption can be compared to a fraction of - the average weekly emissions of a US citizen: +![Grid Energy Mix](../images/grid_energy_mix.png){.align-center width="350px" height="300px"} -$$\text{US Citizen Equivalent} = \frac{\text{Total Emissions (tons)}}{0.256}$$ +### Resolution order -These estimates are approximate and subject to regional variations in: +CodeCarbon resolves `C` through the ladder below, implemented in +[`core/emissions.py`](https://github.com/mlco2/codecarbon/blob/master/codecarbon/core/emissions.py). +The first level that answers wins. -- Grid emissions intensity. -- Vehicle efficiencies. +| # | Level | Condition | Data | Source | +|---|---|---|---|---| +| 1 | Forced value | `force_carbon_intensity_g_co2e_kwh` is set | your number | `emissions.py:62-66`, `:156-160` | +| 2 | Cloud region | running on a recognised cloud provider **and** the provider/region pair is in `impact.csv` | [`data/cloud/impact.csv`](https://github.com/mlco2/codecarbon/blob/master/codecarbon/data/cloud/impact.csv) | `emissions.py:68-75` | +| 3 | Electricity Maps | an API token is configured | live grid intensity by lat/lon or country code | `emissions.py:162-177`, `core/electricitymaps_api.py` | +| 4 | Regional | country is US, Canada, Sweden, Norway or Finland **and** a region/bidding zone is known | US state and Canadian province files; Nordic bidding-zone factors | `emissions.py:182-195`, `:234-290` | +| 5 | Country | the country ISO code is in the energy-mix file | [`global_energy_mix.json`](https://github.com/mlco2/codecarbon/blob/master/codecarbon/data/private_infra/global_energy_mix.json), from [Our World in Data](https://ourworldindata.org/grapher/carbon-intensity-electricity) | `emissions.py:292-326` | +| 6 | World average | nothing above resolved | **475 gCO₂eq/kWh**, from the [IEA](https://www.iea.org/reports/global-energy-co2-status-report-2019/emissions) | `carbon_intensity_per_source.json`, applied at `emissions.py:88-96` and `:307-315` | -### Source Code +Some countries lack data for the most recent year, in which case the most +recent available year is used. -The emission factors used are defined in the [CodeCarbon source -code](https://github.com/mlco2/codecarbon/blob/master/webapp/src/helpers/constants.ts). -They are based on publicly available data and general assumptions. +!!! warning "The fallbacks are silent" -## References + Every step down this ladder degrades quietly. If your cloud provider and + region are not in `impact.csv` — and **AWS and Azure publish no + per-region carbon intensity at all; only GCP does** — CodeCarbon logs a + warning and drops to the country value, or to the 475 g world average if no + country is known (`emissions.py:76-97`). Likewise an unknown country ISO + code drops straight to the world average (`emissions.py:303-315`). -### Foundational work + **Nothing in the CSV, the API payload or the emissions figure records which + level answered.** A run resolved from a live Electricity Maps reading and a + run resolved from the world average produce output of identical shape. If + the provenance matters to you, check the startup logs at `WARNING` level or + set `force_carbon_intensity_g_co2e_kwh` explicitly so there is no ambiguity. -Strubell, E., Ganesh, A., & McCallum, A. (2019). *Energy and Policy -Considerations for Deep Learning in NLP*. Proceedings of ACL 2019. - +### The legacy per-fuel table -Lacoste, A., Luccioni, A., Schmidt, V., & Dandres, T. (2019). *Quantifying -the Carbon Emissions of Machine Learning*. NeurIPS Workshop on Tackling -Climate Change with Machine Learning. - +Before per-country intensities were available from Our World in Data, +CodeCarbon derived intensity from a country's electricity *mix* using this +table. It is retained for the regional Canadian data, which is published as a +mix rather than as an intensity. -Lottick, K., Susai, S., Friedler, S. A., & Wilson, J. P. (2019). *Energy -Usage Reports: Environmental awareness as part of algorithmic -accountability*. NeurIPS Workshop on Tackling Climate Change with Machine -Learning. +| Energy Source | Carbon Intensity (kg/MWh) | +|---------------|---------------------------| +| Coal | 995 | +| Petroleum | 816 | +| Natural Gas | 743 | +| Geothermal | 38 | +| Hydroelectricity | 26 | +| Nuclear | 29 | +| Solar | 48 | +| Wind | 26 | -Henderson, P., Hu, J., Romoff, J., Brunskill, E., Jurafsky, D., & Pineau, J. -(2020). *Towards the Systematic Reporting of the Energy and Carbon Footprints -of Machine Learning*. Journal of Machine Learning Research, 21(248). - +Sources: [fossil fuels](https://github.com/responsibleproblemsolving/energy-usage#conversion-to-co2), +[renewables](http://www.world-nuclear.org/uploadedFiles/org/WNA/Publications/Working_Group_Reports/comparison_of_lifecycle.pdf). -Patterson, D., Gonzalez, J., Le, Q., Liang, C., Munguia, L.-M., -Rothchild, D., So, D., Texier, M., & Dean, J. (2021). *Carbon Emissions and -Large Neural Network Training*. +For a mix of 25% coal, 35% petroleum, 26% natural gas and 14% nuclear: -Luccioni, A. S., Viguier, S., & Ligozat, A.-L. (2022). *Estimating the Carbon -Footprint of BLOOM, a 176B Parameter Language Model*. - +```text +Net Carbon Intensity = 0.25 × 995 + 0.35 × 816 + 0.26 × 743 + 0.14 × 29 + = 731.59 kgCO₂/MWh +``` -### Hardware measurement +## Measurement cadence -Khan, K. N., Hirki, M., Niemi, T., Nurminen, J. K., & Ou, Z. (2018). -*RAPL in Action: Experiences in Using RAPL for Power Measurements*. -ACM Transactions on Modeling and Performance Evaluation of Computing Systems. +CodeCarbon runs a scheduler that takes a measurement every `measure_power_secs` +(default 15 s), started by `start()` and stopped by `stop()`. The measurement +itself is fast and the memory footprint is small, so overhead is not +significant at the default interval. -Weaver, V. M. *Reading RAPL energy measurements from Linux*. - +A second scheduler, `scheduler_monitor_power`, samples power once per second. +It exists for hardware that exposes instantaneous power but no cumulative +energy counter — `cpu_load` mode in particular — so that the per-interval +energy is an average of many samples rather than a single instant. -Microsoft. *Energy Meter Interface (EMI) driver documentation*. - +## Equivalences -### Carbon intensity data sources +The dashboard's "equivalent to *n* miles driven" comparisons are documented +separately, with their factors and derivations, on +[Equivalences](equivalences.md). They are presentation aids and play no part in +the calculation above. -Our World in Data. *Carbon intensity of electricity generation*. - +## Checking this page -International Energy Agency (2019). *Global Energy & CO2 Status Report*. - +Every constant above is stated with the file and line it comes from. If a +number here disagrees with the source, the source wins and the page is a bug — +please [open an issue](https://github.com/mlco2/codecarbon/issues). -Google Cloud. *Carbon free energy for Google Cloud regions*. - +The selection logic lives in +[`codecarbon/core/resource_tracker.py`](https://github.com/mlco2/codecarbon/blob/master/codecarbon/core/resource_tracker.py), +the power models in +[`codecarbon/external/hardware.py`](https://github.com/mlco2/codecarbon/blob/master/codecarbon/external/hardware.py) +and +[`codecarbon/external/ram.py`](https://github.com/mlco2/codecarbon/blob/master/codecarbon/external/ram.py), +and the carbon intensity resolution in +[`codecarbon/core/emissions.py`](https://github.com/mlco2/codecarbon/blob/master/codecarbon/core/emissions.py). -### Citing CodeCarbon +## Further reading -See [CITATION.cff](https://github.com/mlco2/codecarbon/blob/master/CITATION.cff) -or the "Cite this repository" button on GitHub. The -[README](https://github.com/mlco2/codecarbon#citation) also carries a ready-made -BibTeX entry. +- [Accuracy and validation](accuracy.md) — measured deviations and known gaps +- [RAPL Metrics](rapl.md) — the Linux energy counters in detail +- [Power Estimation](power-estimation.md) — counters to power +- [References](references.md) — the bibliography +- [Output reference](../reference/output.md) — what each field means diff --git a/docs/explanation/references.md b/docs/explanation/references.md new file mode 100644 index 000000000..295f2c921 --- /dev/null +++ b/docs/explanation/references.md @@ -0,0 +1,96 @@ +# References + +The literature and data sources behind CodeCarbon's +[methodology](methodology.md). The [accuracy](accuracy.md) and +[alternatives](alternatives.md) pages cite this list rather than repeating it. + +## Foundational work + +Strubell, E., Ganesh, A., & McCallum, A. (2019). *Energy and Policy +Considerations for Deep Learning in NLP*. Proceedings of the 57th Annual +Meeting of the Association for Computational Linguistics (ACL 2019). + + +Lacoste, A., Luccioni, A., Schmidt, V., & Dandres, T. (2019). *Quantifying +the Carbon Emissions of Machine Learning*. arXiv preprint arXiv:1910.09700. + + +Lottick, K., Susai, S., Friedler, S. A., & Wilson, J. P. (2019). *Energy +Usage Reports: Environmental awareness as part of algorithmic +accountability*. Workshop on Tackling Climate Change with Machine Learning, +NeurIPS 2019. + +Henderson, P., Hu, J., Romoff, J., Brunskill, E., Jurafsky, D., & Pineau, J. +(2020). *Towards the Systematic Reporting of the Energy and Carbon Footprints +of Machine Learning*. Journal of Machine Learning Research, 21(248), 1–43. + + +Patterson, D., Gonzalez, J., Le, Q., Liang, C., Munguia, L.-M., Rothchild, D., +So, D., Texier, M., & Dean, J. (2021). *Carbon Emissions and Large Neural +Network Training*. arXiv preprint arXiv:2104.10350. + + +Luccioni, A. S., Viguier, S., & Ligozat, A.-L. (2022). *Estimating the Carbon +Footprint of BLOOM, a 176B Parameter Language Model*. arXiv preprint +arXiv:2211.02001. + +Lacoste et al. is the closest methodological ancestor of CodeCarbon: it comes +from the same `mlco2` GitHub organization, and two of its authors are listed +as CodeCarbon authors. + +## Hardware measurement + +Khan, K. N., Hirki, M., Niemi, T., Nurminen, J. K., & Ou, Z. (2018). *RAPL in +Action: Experiences in Using RAPL for Power Measurements*. ACM Transactions on +Modeling and Performance Evaluation of Computing Systems, 3(2), Article 9, +1–26. + +Weaver, V. M. *Reading RAPL energy measurements from Linux*. + + +Microsoft. *Energy Meter Interface (EMI) driver documentation*. + + +Chih, M. *Read CPU power with RAPL*. + — an informal walkthrough, +useful as an introduction but secondary to Khan et al. and Weaver above. + +## Carbon intensity data sources + +Our World in Data. *Carbon intensity of electricity generation*. + — the source +of the per-country intensities in `global_energy_mix.json`. + +International Energy Agency (2019). *Global Energy & CO2 Status Report*. + — +the source of the 475 gCO₂eq/kWh world average. + +Electricity Maps. *Carbon intensity API*. + — optional live +carbon intensity, used when an API token is configured. + +Google Cloud. *Carbon free energy for Google Cloud regions*. + — the source of the GCP +rows in `impact.csv`. + +Responsible Problem Solving. *Energy Usage — conversion to CO2*. + — +the per-fuel intensities for fossil sources. + +World Nuclear Association. *Comparison of lifecycle greenhouse gas emissions of +various electricity generation sources*. + +— the per-fuel intensities for low-carbon sources. + +## Equivalence factors + +The car, television and household equivalences shown in the dashboard come from +the US EPA; see [equivalences](equivalences.md) for the exact figures and their +derivations. + +## Citing CodeCarbon + +Use +[CITATION.cff](https://github.com/mlco2/codecarbon/blob/master/CITATION.cff), +or the "Cite this repository" button on the +[GitHub repository](https://github.com/mlco2/codecarbon). diff --git a/docs/how-to/troubleshooting.md b/docs/how-to/troubleshooting.md index a4624f47c..dff9672fa 100644 --- a/docs/how-to/troubleshooting.md +++ b/docs/how-to/troubleshooting.md @@ -252,7 +252,7 @@ there is no way to answer an interactive prompt from inside a library. To get measured values, grant passwordless `sudo` for `powermetrics` alone by editing your sudoers file, as described under -[Power usage](../explanation/methodology.md#power-usage). Run with +[Power usage](../explanation/methodology.md#energy-what-is-measured-what-is-modelled). Run with `log_level="debug"` to confirm the check now passes. ### "Returncode while logging power values using Powermetrics" diff --git a/mkdocs.yml b/mkdocs.yml index e8aa19047..5a1ace21b 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -194,6 +194,8 @@ nav: - RAPL Metrics: explanation/rapl.md - Power Estimation: explanation/power-estimation.md - Model Comparisons: explanation/model-comparisons.md + - Equivalences: explanation/equivalences.md + - References: explanation/references.md - FAQ: explanation/faq.md - Reference: - API Reference: reference/api.md From e0408589f1697258a832e7cfd38a946814c6814b Mon Sep 17 00:00:00 2001 From: David Berenstein Date: Wed, 12 Aug 2026 23:35:58 +0200 Subject: [PATCH 2/4] docs: document both cpu_load power models tracking_mode silently swaps the power model on the estimation path: cubic with a 10% floor in machine mode (hardware.py:287-288), linear with no floor in process mode (hardware.py:346). Documenting only the cubic invited readers to blame the divergence on attribution scope. Also scope accuracy.md's deviation figures to machine mode, which is what the profiling script actually measured. Refs #1378 Co-Authored-By: Claude Opus 5 (1M context) --- docs/explanation/accuracy.md | 23 +++++++++-- docs/explanation/methodology.md | 70 +++++++++++++++++++++++++++------ 2 files changed, 77 insertions(+), 16 deletions(-) diff --git a/docs/explanation/accuracy.md b/docs/explanation/accuracy.md index cc1cbd3c3..9033b925c 100644 --- a/docs/explanation/accuracy.md +++ b/docs/explanation/accuracy.md @@ -34,7 +34,7 @@ counter is readable. The fallback order is documented in | macOS `powermetrics` | System power reporting | Not yet measured against an external reference | macOS, Intel and Apple Silicon | | Windows EMI | Energy Meter Interface | Not yet measured against an external reference | Windows 11, where the platform exposes it | | Intel Power Gadget | Vendor tool, deprecated upstream | Not yet measured | Legacy path | -| CPU load × TDP | Estimates power from CPU utilisation against the TDP listed in `cpu_power.csv` | See the profiling results below: on the machines profiled, the estimate deviated from RAPL by roughly −60% to +90% depending on CPU and load point | Fallback when no CPU counter is available | +| CPU load × TDP | Estimates power from CPU utilisation against the TDP listed in `cpu_power.csv`. Two different curves, [selected by `tracking_mode`](methodology.md#the-two-cpu_load-models) | See the profiling results below: in **machine mode**, on the machines profiled, the estimate deviated from RAPL by roughly −60% to +90% depending on CPU and load point. Process mode is uncharacterised | Fallback when no CPU counter is available | | Default watts per thread | Estimates from thread count alone | Not characterised; this is the least accurate path | Last resort, when the CPU model is absent from `cpu_power.csv` | ### The CPU load × TDP fallback, measured @@ -45,6 +45,20 @@ CodeCarbon would have produced. The raw sweeps live in `codecarbon/data/hardware/cpu_load_profiling/` and are plotted in `examples/compare_cpu_load_and_RAPL.ipynb`. Runs are dated January 2025. +!!! warning "These figures are machine mode only" + + The profiling script constructs its tracker with `force_mode_cpu_load=True` + and no `tracking_mode` argument (`compare_cpu_load_and_RAPL.py:289-292`), so + every number below was gathered under the default + `tracking_mode="machine"`. + + That matters because `cpu_load` mode uses a **different power model** in + process mode — linear with no idle floor, rather than cubic with a 10%-of-TDP + floor. See + [Methodology → The two cpu_load models](methodology.md#the-two-cpu_load-models). + **The deviations below do not transfer to `tracking_mode="process"`.** No + equivalent profiling has been done for process mode. + Deviation of the estimate from the RAPL reading, over load points above 5% (negative means the estimate is lower than RAPL): @@ -63,8 +77,11 @@ for the pair, while RAPL reported about 117 W at full load — the chips are hel near their base frequency and never reach the rated figure. On the EPYC 8024P the gap is larger still. -**The relationship between load and power is not linear.** CodeCarbon -interpolates linearly between idle and TDP. Real curves are convex on some parts +**The assumed load-to-power curve does not match the real one.** In machine +mode CodeCarbon applies a cubic curve with a 10%-of-TDP floor +(`hardware.py:287-288`); in process mode it interpolates linearly from zero +(`hardware.py:346`). Neither shape is fitted to hardware. Real curves are convex +on some parts (the E3-1240 v2 stays under 10 W up to 40% load, so the linear estimate overshoots it by well over 100%) and saturate early on others (the Threadripper reaches its power ceiling around 65% load, so the estimate *undershoots* at mid diff --git a/docs/explanation/methodology.md b/docs/explanation/methodology.md index 62f2638ad..8dc5ac5a6 100644 --- a/docs/explanation/methodology.md +++ b/docs/explanation/methodology.md @@ -74,6 +74,14 @@ and is preferable on shared machines. the device level, so on a shared GPU CodeCarbon still attributes the **entire GPU's** consumption to your run. +!!! warning "On the estimation path, `tracking_mode` also changes the power model" + + When CodeCarbon falls back to `cpu_load` mode, `tracking_mode` selects not + just the attribution scope but a **different power curve** — cubic with a + 10% floor for machine mode, linear with no floor for process mode. See + [The two cpu_load models](#the-two-cpu_load-models). On the hardware-counter + paths (RAPL, EMI, NVML) this does not apply. + ### Power Usage Effectiveness (PUE) If you set `pue`, it is applied inside the measurement loop to **each @@ -139,16 +147,8 @@ Whatever TDP results is multiplied by the physical CPU package count That ceiling is then turned into an instantaneous power reading in one of two ways, and **the two ways are not the same**: -- **`cpu_load` mode** applies a cubic load curve with a 10% floor - ([`hardware.py:287-288`](https://github.com/mlco2/codecarbon/blob/master/codecarbon/external/hardware.py#L287)): - - ```text - load_factor = 0.1 + 0.9 × (cpu_load_percent / 100)³ - power = TDP × load_factor - ``` - - An idle machine therefore reports 10% of TDP, and a fully loaded one - reports 100%. There is no 50% anywhere in this path. +- **`cpu_load` mode** — see the next section. **It uses two different power + models depending on `tracking_mode`.** - **`constant` mode** — reached only when `psutil` is unavailable — applies a flat half of TDP @@ -161,10 +161,54 @@ ways, and **the two ways are not the same**: `CONSUMPTION_PERCENTAGE_CONSTANT = 0.5` is defined at [`hardware.py:26`](https://github.com/mlco2/codecarbon/blob/master/codecarbon/external/hardware.py#L26). -The cubic curve is an asserted shape, not a fitted one; the code cites no -source for the exponent or the 10% floor. +### The two cpu_load models + +`cpu_load` mode does not have one power model. It has two, selected by +`tracking_mode` inside +[`_get_power_from_cpu_load`](https://github.com/mlco2/codecarbon/blob/master/codecarbon/external/hardware.py#L274) +(`hardware.py:274-352`). They differ in shape, not only in scope, and the +difference is large at low load. + +| | `tracking_mode="machine"` | `tracking_mode="process"` | +|---|---|---| +| Load source | `psutil.cpu_percent()`, system-wide (`hardware.py:280-282`) | per-process CPU-time deltas over wall clock, summed across the process and its children (`hardware.py:294-330`) | +| Normalisation | none — already a 0–100% figure | divided by core count (`hardware.py:345`) | +| Curve | **cubic** (`hardware.py:287-288`) | **linear** (`hardware.py:346`) | +| Idle floor | **10% of TDP** | **none** — 0% load gives 0 W | + +```python +# machine mode — hardware.py:287-288 +load_factor = 0.1 + 0.9 * ((cpu_load / 100.0) ** 3) +power = tdp * load_factor +``` + +```python +# process mode — hardware.py:345-346 +cpu_load_normalized = cpu_load / self._cpu_count +power = self._tdp * cpu_load_normalized / 100 +``` + +!!! warning "The same workload measured both ways will not differ only by scope" + + Switching `tracking_mode` silently swaps the power model. At low + utilisation the two diverge sharply: machine mode never reports below 10% + of TDP, while process mode reports proportionally to load and reaches zero. + At 50% load, machine mode gives `0.1 + 0.9 × 0.125 = 21%` of TDP, while a + process saturating half the cores gives 50% of TDP — more than double, + from the same underlying utilisation. + + If you compare a machine-mode run against a process-mode run and the + numbers disagree by more than the attribution scope explains, this is why. + It is not evidence that either number is wrong; it is two different models. + +**None of these shape choices is sourced in the code.** The cubic exponent, the +0.1 floor in machine mode, and the absence of any floor in process mode are all +asserted, with no comment, citation or fitting procedure anywhere in the +module. They are plausible defaults, not measurements. + [Accuracy and validation](accuracy.md#the-cpu-load-tdp-fallback-measured) -reports how far this fallback lands from measured RAPL energy. +reports how far this fallback lands from measured RAPL energy — **in machine +mode only**; those figures do not transfer to process mode. ### Per-platform notes From 4ef79081aa5da40026b148304d0bd5a45524a891 Mon Sep 17 00:00:00 2001 From: David Berenstein Date: Wed, 12 Aug 2026 23:41:12 +0200 Subject: [PATCH 3/4] docs: reconcile the opening formula with PUE The overview page states the formula PUE-inflated; this page stated E x C and introduced PUE later, so the two read as disagreeing. Co-Authored-By: Claude Opus 5 (1M context) --- docs/explanation/methodology.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/docs/explanation/methodology.md b/docs/explanation/methodology.md index 8dc5ac5a6..7b9bf356d 100644 --- a/docs/explanation/methodology.md +++ b/docs/explanation/methodology.md @@ -28,6 +28,12 @@ and integrating over time, and resolves `C` from the machine's location or cloud region. Both halves have fallbacks, and which fallback ran is what determines how much you should trust the result. +`E` here is the **PUE-inflated** energy: if you set a `pue`, it is already +folded into every energy figure CodeCarbon reports, per component, so expanding +the formula gives `Emissions = Σ_intervals (power × Δt × PUE) × C`. This +matters when you do arithmetic with the CSV columns — see +[Power Usage Effectiveness](#power-usage-effectiveness-pue). + | Half of the formula | Best case | Worst case | |---|---|---| | `E` — energy | Hardware energy counters (RAPL, EMI, NVML) | A CPU model's catalogue TDP scaled by CPU load | @@ -105,6 +111,11 @@ This is the part of CodeCarbon most often misread. The selection logic is in [`resource_tracker.py:249-279`](https://github.com/mlco2/codecarbon/blob/master/codecarbon/core/resource_tracker.py#L249), and it is evaluated strictly in this order — the first row that applies wins. +Rows 1–9 are decided in that selector. If none of them matches, it delegates to +the `_setup_fallback_tracking` helper (`resource_tracker.py:159-219`), which is +where rows 10 and 11 are decided — hence the different line citations on the +last two rows. + | # | Condition | Result | Source | |---|---|---|---| | 1 | `force_cpu_power` is set | Fixed constant power. **Every platform backend is skipped** — RAPL is not consulted even if it is available. | `resource_tracker.py:255-260`, guard at `:272` | From 1f690bf170ebc92048123676dca4e69ab07f8faf Mon Sep 17 00:00:00 2001 From: David Berenstein Date: Thu, 13 Aug 2026 08:34:22 +0200 Subject: [PATCH 4/4] docs: credit AMDSMI alongside NVML in the source table The GPU row named only NVML, but AMD devices are read through AMDSMI in codecarbon/core/gpu_amd.py. Co-Authored-By: Claude Opus 5 (1M context) --- docs/explanation/methodology.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/explanation/methodology.md b/docs/explanation/methodology.md index 7b9bf356d..7e9980409 100644 --- a/docs/explanation/methodology.md +++ b/docs/explanation/methodology.md @@ -51,7 +51,7 @@ instantiating the tracker. Energy is the integral of power over time: | Component | Best available source | Modelled fallback | |---|---|---| | CPU | Intel RAPL (Linux), Energy Meter Interface (Windows 11), `powermetrics` (macOS), Intel Power Gadget (legacy) | TDP × CPU load, or a flat constant | -| GPU | NVML via `nvidia-ml-py` — a direct device reading | none; without NVML, GPU is not counted | +| GPU | NVML via `nvidia-ml-py` (Nvidia) or AMDSMI (AMD) — a direct device reading | none; without a supported GPU library, GPU is not counted | | RAM | none — there is no RAM power counter | always modelled from an estimated DIMM count | CodeCarbon does not separately model disk I/O, network transfer, displays,