From 831795b42967eb346c8b71a42992d5f65dbeeed1 Mon Sep 17 00:00:00 2001 From: David Berenstein Date: Wed, 12 Aug 2026 16:13:37 +0200 Subject: [PATCH 1/9] docs: complete metadata, citation, troubleshooting and accuracy docs Implements the twelve documentation and traction items tracked in updates/docs: - PyPI metadata: description, keywords, expanded classifiers - CITATION.cff plus an unpinned, DOI-based citation in the README - Rewritten examples/README.md indexing the real examples and APIs - New troubleshooting page keyed to actual log lines, linked from the README, FAQ, docs index and from RAPL warning strings in cpu.py - RAPL setup guide promoted out of "Deployment" to a top-level how-to - New alternatives comparison and accuracy/validation pages - Methodology bibliography built out; blog-post and open-issue citations demoted or replaced - GitHub issue forms that collect `codecarbon detect` output - Contributing guide split into contributor / development / maintainer pages, stray `` in headings removed - CHANGELOG.md, changelog and deprecations reference pages, a blog surface, and release-drafter version resolution - Open Graph tags, footer links, and start()/stop() in the API reference Co-Authored-By: Claude Opus 5 (1M context) --- .github/ISSUE_TEMPLATE/bug_report.yml | 122 ++++++ .github/ISSUE_TEMPLATE/config.yml | 14 + .../documentation_improvement.yml | 26 ++ .github/ISSUE_TEMPLATE/feature_request.yml | 35 ++ CHANGELOG.md | 11 + CITATION.cff | 62 ++++ docs/blog/2026-08-04-codecarbon-3-3-0.md | 39 ++ docs/blog/index.md | 10 + docs/explanation/accuracy.md | 168 +++++++++ docs/explanation/alternatives.md | 161 ++++++++ docs/how-to/development.md | 325 ++++++++++++++++ docs/how-to/troubleshooting.md | 351 ++++++++++++++++++ docs/images/og-card.png | Bin 0 -> 39931 bytes docs/maintaining.md | 135 +++++++ docs/reference/changelog.md | 103 +++++ docs/reference/deprecations.md | 71 ++++ overrides/main.html | 34 ++ tests/test_readme_links.py | 23 ++ 18 files changed, 1690 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/bug_report.yml create mode 100644 .github/ISSUE_TEMPLATE/config.yml create mode 100644 .github/ISSUE_TEMPLATE/documentation_improvement.yml create mode 100644 .github/ISSUE_TEMPLATE/feature_request.yml create mode 100644 CHANGELOG.md create mode 100644 CITATION.cff create mode 100644 docs/blog/2026-08-04-codecarbon-3-3-0.md create mode 100644 docs/blog/index.md create mode 100644 docs/explanation/accuracy.md create mode 100644 docs/explanation/alternatives.md create mode 100644 docs/how-to/development.md create mode 100644 docs/how-to/troubleshooting.md create mode 100644 docs/images/og-card.png create mode 100644 docs/maintaining.md create mode 100644 docs/reference/changelog.md create mode 100644 docs/reference/deprecations.md create mode 100644 overrides/main.html create mode 100644 tests/test_readme_links.py diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 000000000..26dc0d15a --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,122 @@ +name: Bug report +description: Something in CodeCarbon is not working as expected +labels: ["bug"] +body: + - type: markdown + attributes: + value: | + Thanks for taking the time to file a report. + + Before you start: if your emissions look wrong rather than the tool + crashing, please check the + [troubleshooting guide](https://docs.codecarbon.io/latest/how-to/troubleshooting/) + first — many measurement issues are configuration, especially RAPL + permissions on Linux. + + - type: textarea + id: what-happened + attributes: + label: What happened? + description: What did you expect, and what did you get instead? + validations: + required: true + + - type: textarea + id: reproduce + attributes: + label: Minimal code to reproduce + description: The smallest script that shows the problem. + render: python + validations: + required: true + + - type: textarea + id: detect-output + attributes: + label: Output of `codecarbon detect` + description: | + Run `codecarbon detect` in the same environment and paste the full + output. This tells us what hardware and measurement backends + CodeCarbon found, and it answers most questions immediately. + render: shell + validations: + required: true + + - type: textarea + id: logs + attributes: + label: Debug log + description: | + Re-run with `EmissionsTracker(log_level="debug")` and paste the + CodeCarbon log output. Warnings about RAPL, CPU detection, or + another running instance are especially relevant. + render: shell + validations: + required: false + + - type: input + id: version + attributes: + label: CodeCarbon version + description: "Output of `codecarbon --version`" + placeholder: "3.3.0" + validations: + required: true + + - type: input + id: python-version + attributes: + label: Python version + placeholder: "3.12.4" + validations: + required: true + + - type: dropdown + id: os + attributes: + label: Operating system + options: + - Linux + - macOS (Apple Silicon) + - macOS (Intel) + - Windows + - Windows (WSL) + - Other + validations: + required: true + + - type: dropdown + id: environment + attributes: + label: Where is this running? + options: + - Directly on my machine + - Docker / container + - Virtual machine + - Cloud VM (AWS / GCP / Azure / other) + - HPC cluster / SLURM + - CI pipeline + - Jupyter notebook / Colab + - Other + validations: + required: true + + - type: dropdown + id: install-method + attributes: + label: How did you install CodeCarbon? + options: + - pip + - uv + - conda + pip + - from source + - other + validations: + required: true + + - type: textarea + id: context + attributes: + label: Anything else? + validations: + required: false diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 000000000..d6526b020 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,14 @@ +blank_issues_enabled: false +contact_links: + - name: Question or help getting started + url: https://discord.gg/GS9js2XkJR + about: Ask on Discord — usually the fastest way to get an answer. + - name: Troubleshooting guide + url: https://docs.codecarbon.io/latest/how-to/troubleshooting/ + about: Common problems, error messages, and how to fix them. + - name: FAQ + url: https://docs.codecarbon.io/latest/explanation/faq/ + about: Accuracy, data sources, platform support, and more. + - name: Documentation + url: https://docs.codecarbon.io/ + about: Full documentation. diff --git a/.github/ISSUE_TEMPLATE/documentation_improvement.yml b/.github/ISSUE_TEMPLATE/documentation_improvement.yml new file mode 100644 index 000000000..95b7d2bba --- /dev/null +++ b/.github/ISSUE_TEMPLATE/documentation_improvement.yml @@ -0,0 +1,26 @@ +name: Documentation improvement +description: Report something missing, wrong, or confusing in the documentation +labels: ["documentation"] +body: + - type: input + id: location + attributes: + label: Which page? + description: URL or file path of the page that needs improvement. + placeholder: "https://docs.codecarbon.io/latest/how-to/configuration/" + validations: + required: true + + - type: textarea + id: problem + attributes: + label: What is wrong or missing? + validations: + required: true + + - type: textarea + id: suggestion + attributes: + label: What would you suggest instead? + validations: + required: false diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 000000000..d3cda3383 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,35 @@ +name: Feature request +description: Suggest a capability or improvement for CodeCarbon +labels: ["enhancement"] +body: + - type: textarea + id: goal + attributes: + label: What are you trying to accomplish? + description: | + Describe the goal rather than the implementation — what are you + measuring, and what makes it hard or impossible today? + validations: + required: true + + - type: textarea + id: proposal + attributes: + label: What would you like CodeCarbon to do? + validations: + required: true + + - type: textarea + id: alternatives + attributes: + label: What have you tried or considered? + description: Workarounds, other tools, or configuration you already tested. + validations: + required: false + + - type: textarea + id: context + attributes: + label: Anything else? + validations: + required: false diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 000000000..4b90aad1e --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,11 @@ +# Changelog + +The curated, user-facing changelog lives in the documentation: + +- **[docs/reference/changelog.md](docs/reference/changelog.md)** — rendered at + +- **[docs/reference/deprecations.md](docs/reference/deprecations.md)** — what is + deprecated, when it is removed, and what to use instead + +The full list of merged pull requests per release is on the +[GitHub releases page](https://github.com/mlco2/codecarbon/releases). diff --git a/CITATION.cff b/CITATION.cff new file mode 100644 index 000000000..346304a85 --- /dev/null +++ b/CITATION.cff @@ -0,0 +1,62 @@ +cff-version: 1.2.0 +message: "If you use this software, please cite it using the metadata from this file." +title: "CodeCarbon: Estimate and track carbon emissions from computing" +abstract: >- + CodeCarbon estimates the electricity consumed by the hardware running your + code and converts it to CO2 emissions using the carbon intensity of the + local electricity grid. +type: software +authors: + - given-names: Benoit + family-names: Courty + - given-names: Victor + family-names: Schmidt + - given-names: Sasha + family-names: Luccioni + - given-names: Boris + family-names: Feld + - given-names: Jérémy + family-names: Lecourt + - given-names: Mathilde + family-names: Léval + - given-names: Luis + family-names: Blanche + - given-names: Alexis + family-names: Cruveiller + - given-names: Aditya + family-names: Joshi + - given-names: Alexis + family-names: Bogroff + - given-names: Hugues + family-names: de Lavoreille + - given-names: Niko + family-names: Laskaris + - given-names: Edoardo + family-names: Abati + - given-names: Douglas + family-names: Blank + - given-names: Ziyao + family-names: Wang + - given-names: Armin + family-names: Catovic + - given-names: Marc + family-names: Alencon + - given-names: Michał + family-names: Stęchły + - given-names: Christian + family-names: Bauer + - given-names: Lucas Otávio N. + family-names: de Araújo + - name: "The CodeCarbon contributors" +repository-code: "https://github.com/mlco2/codecarbon" +url: "https://codecarbon.io" +license: MIT +version: 3.3.0 +doi: 10.5281/zenodo.4658424 +date-released: 2026-08-04 +keywords: + - carbon emissions + - green AI + - sustainable computing + - energy consumption + - machine learning diff --git a/docs/blog/2026-08-04-codecarbon-3-3-0.md b/docs/blog/2026-08-04-codecarbon-3-3-0.md new file mode 100644 index 000000000..8bb3f4178 --- /dev/null +++ b/docs/blog/2026-08-04-codecarbon-3-3-0.md @@ -0,0 +1,39 @@ +# CodeCarbon 3.3.0: measuring energy on Windows + +*4 August 2026* + +CodeCarbon 3.3.0 is out. The headline change is that Windows machines can now be +measured rather than estimated. + +## Windows Energy Meter Interface + +On Linux we read CPU energy from RAPL, and on Apple Silicon from `powermetrics`. +Windows had neither, so CPU power there fell back to a constant-fraction estimate +based on the chip's TDP — usable, but never as good as reading a counter. + +3.3.0 adds support for the +[Windows Energy Meter Interface](https://learn.microsoft.com/en-us/windows-hardware/drivers/powermeter/energy-meter-interface) +(EMI), the OS-level API that exposes hardware energy meters. Where the platform +provides it, CodeCarbon now reads CPU power from EMI instead of estimating it, and it +handles machines that expose one meter per die. + +Two caveats worth knowing before you expect a change in your numbers: EMI reports CPU +power on Windows 11 running on bare metal, so virtual machines are still on the +estimated path, and not every platform exposes an EMI device at all. The +[troubleshooting guide](../how-to/troubleshooting.md) has a script that prints the +channels your machine exposes, and the +[methodology page](../explanation/methodology.md) describes how the measurement fits +into the rest of the pipeline. + +## If you use the API client directly + +`ApiClient` used to swallow HTTP errors and return `None`. It now raises. If you wrote +code against the old behaviour and checked for `None`, that check will no longer fire — +handle the exception instead. Nothing changes if you only use `EmissionsTracker`. + +## Everything else + +The full list of merged pull requests is on the +[3.3.0 release page](https://github.com/mlco2/codecarbon/releases/tag/3.3.0), and the +user-visible changes per release are collected in the +[changelog](../reference/changelog.md). diff --git a/docs/blog/index.md b/docs/blog/index.md new file mode 100644 index 000000000..c4769b0ff --- /dev/null +++ b/docs/blog/index.md @@ -0,0 +1,10 @@ +# Blog + +News, release announcements and deep-dives on measuring the carbon footprint of +computing. Want to write one? See the +[contribution guidelines](../how-to/contributing.md). + +## Posts + +- [CodeCarbon 3.3.0: measuring energy on Windows](2026-08-04-codecarbon-3-3-0.md) — + 4 August 2026 diff --git a/docs/explanation/accuracy.md b/docs/explanation/accuracy.md new file mode 100644 index 000000000..8b3d3bf53 --- /dev/null +++ b/docs/explanation/accuracy.md @@ -0,0 +1,168 @@ +# Accuracy and validation + +This page describes what CodeCarbon measures, how close those measurements are +to a hardware reference, where the remaining error comes from, and what you can +do to reduce it. + +## What is inside the measurement boundary + +CodeCarbon reports the **direct electricity consumption of the compute +components it can read or estimate**: CPU, GPU and RAM. It then multiplies the +resulting energy by the carbon intensity of the local electricity grid. + +Outside the boundary: + +- disk I/O, network transfers, displays, cooling and other peripherals; +- power supply and datacenter overhead, unless you set a + [PUE](../how-to/configuration.md) value yourself; +- life-cycle (embodied) emissions of the hardware. + +Every accuracy statement below is scoped to that boundary. A CodeCarbon figure +is not a whole-facility footprint, and it is not meant to be one. + +## How accurate each measurement backend is + +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). + +| Backend | How it works | Agreement with a reference | When it is used | +|---|---|---|---| +| Intel RAPL | Reads hardware energy counters under `/sys/class/powercap/` | On the four CPUs profiled in this repository, CodeCarbon's RAPL readings matched `stress-ng --rapl` on the same machine. RAPL is itself an on-die estimate and its own absolute error is not characterised here. | Linux, Intel and AMD (kernel ≥ 5.8), when the counters are readable | +| NVML (NVIDIA) | Reads accumulated board energy from the driver (`nvmlDeviceGetTotalEnergyConsumption`) | Not yet measured against an external reference | Any NVIDIA GPU with a working driver | +| amdsmi (AMD) | Reads the driver energy counter (`amdsmi_get_energy_count`) | Not yet measured against an external reference | AMD GPUs | +| 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 | +| 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 + +`examples/compare_cpu_load_and_RAPL.py` sweeps CPU load with `stress-ng` and +records, at each load point, both the RAPL reading and the TDP-based estimate +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. + +Deviation of the estimate from the RAPL reading, over load points above 5% +(negative means the estimate is lower than RAPL): + +| CPU | At full load | Range across load points | +|---|---|---| +| Dual Intel Xeon E5-2620 v3 (24 threads) | +40% | +2% to +59% | +| Intel Xeon E3-1240 v2 (8 threads) | +37% | +20% to +187% | +| AMD Ryzen Threadripper 1950X (32 threads) | +3% | −57% to +18% | +| AMD EPYC 8024P (16 threads) | +88% | −50% to +88% | + +Two things drive the error. + +**The TDP is not the real power ceiling.** CodeCarbon assumes a CPU at 100% load +draws its full rated TDP. On the dual E5-2620 v3, the database TDP implies 170 W +for the pair, while RAPL reported about 117 W at full load — the chips are held +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 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 +load). + +The practical reading: the fallback gets the order of magnitude right and can be +off by a factor of two in either direction on a specific machine and workload. +It is not a substitute for RAPL. If your numbers need to be defensible, +[enable RAPL](../how-to/enable-rapl.md). + +### Wall-socket comparison + +The Threadripper sweeps also record whole-machine power from a smart plug +(`tapo_power` in the CSV files). Those figures are not directly comparable to +the CPU numbers — they include the GPU, disks, fans and power supply losses, and +the machine drew about 115 W at idle. They are published for completeness, not as +a validation of the CPU figures. + +A proper wall-socket validation — a controlled comparison of a wattmeter against +CodeCarbon's reported energy for the same interval, on the same machine — has +not been done. It is the reference method reviewers ask about, and no amount of +RAPL-versus-estimate analysis substitutes for it, because RAPL is itself an +instrument with its own error. Contributions are welcome. + +## Where the error comes from + +Three independent error sources compound. Which one dominates depends on your +setup, and the remedy differs for each. + +**Power measurement.** Covered by the table above. Small when hardware counters +are available, potentially a factor of two when the TDP fallback is in use. + +**Carbon intensity.** Often the largest term. When CodeCarbon has no data for +your country it falls back to a world average of 475 gCO₂eq/kWh. Real national +intensities span from under 50 gCO₂eq/kWh to over 700, so this default can be +wrong by close to an order of magnitude. Marginal versus average intensity, and +hourly versus annual averages, add further uncertainty that CodeCarbon does not +model. See [Methodology](methodology.md) for the data sources. + +**Temporal resolution.** CodeCarbon samples every `measure_power_secs` seconds +(default 15). Energy counters accumulate between samples, so this does not lose +energy on the counter paths; but on the estimation path, and for workloads +shorter or spikier than the interval, the sampled load is a poor summary of what +actually happened. + +## What to do about it + +In rough order of impact: + +1. **Enable RAPL on Linux** — see [Improve measurement accuracy with + RAPL](../how-to/enable-rapl.md). This is the single largest improvement + available on most machines, and it moves you from estimation to measurement. +2. **Set your country, region and cloud provider correctly** in the + [configuration](../how-to/configuration.md). A wrong region is usually a + larger error than a wrong power reading. +3. **Set `pue`** if you run in a datacenter whose overhead you know. +4. **Contribute your CPU model** to + `codecarbon/data/hardware/cpu_power.csv` if CodeCarbon logs that it does not + know your CPU. That moves you off the default-watts-per-thread path. +5. **Measure longer runs.** Short runs are dominated by sampling noise and + tracker startup. +6. **Compare within one machine, not across machines.** Relative comparisons + (this model versus that model, on the same hardware and backend) are far more + trustworthy than absolute totals. + +## Reproduce this yourself + +Nothing here needs to be taken on trust. + +- `examples/compare_cpu_load_and_RAPL.py` — runs the load sweep and writes a CSV + with RAPL and estimated power side by side. Requires `stress-ng` and readable + RAPL counters. +- `examples/compare_cpu_load_and_RAPL.ipynb` — plots the CSVs, including the + sweeps committed under `codecarbon/data/hardware/cpu_load_profiling/`. +- `examples/rapl/` — diagnostic scripts for inspecting RAPL domains + (`intel_rapl_show.py`, `test_rapl_domains.py`, `test_dram_option.py` and + others). +- `examples/test_rapl_calculus.sh` — a shell check of the RAPL energy + calculation. +- `examples/print_hardware.py` — shows which backend CodeCarbon selected on your + machine, which tells you which row of the table above applies to you. + +If you run the sweep on a CPU that is not yet profiled, a pull request adding +the CSV to `codecarbon/data/hardware/cpu_load_profiling/` is a directly useful +contribution. + +## Known gaps + +Stated plainly, because a validation page that only lists strengths is not a +validation page: + +- No external wattmeter validation of any backend. +- GPU and RAM backends are not validated against an independent reference. +- The RAPL-versus-estimate comparison covers four CPUs, all Linux, none of them + recent. +- No uncertainty interval is attached to reported emissions figures. +- On the dual E5-2620 v3, RAPL reported markedly lower package power when the + same total load was spread over fewer cores than over all cores. The notebook + flags this as unexpected and unexplained; it was reproduced on a second run. + + diff --git a/docs/explanation/alternatives.md b/docs/explanation/alternatives.md new file mode 100644 index 000000000..db712f2b4 --- /dev/null +++ b/docs/explanation/alternatives.md @@ -0,0 +1,161 @@ +# CodeCarbon and the alternatives + +Several tools measure or estimate the energy and carbon footprint of computing. +They overlap, but they were built for different jobs, and for some of the +questions below another tool is the better answer. This page tries to say so +plainly. + +For the CodeCarbon versus EcoLogits question specifically — local hardware +versus remote GenAI APIs — see [When to use +CodeCarbon](when-to-use.md). The two are complementary rather than competing. + +## At a glance + +| Tool | Type | Language | Licence | Power source | Distinguishing capability | +|---|---|---|---|---|---| +| **CodeCarbon** | Library + CLI + hosted dashboard | Python | MIT | RAPL, NVML, amdsmi, macOS `powermetrics`, Windows EMI; TDP × load estimate as fallback | Per-task attribution, offline mode, several output backends | +| [ML CO2 Impact](https://mlco2.github.io/impact/) | Web calculator | — (web) | MIT | None — you enter hardware, hours and region | Estimate *before* you run; same `mlco2` GitHub organisation as CodeCarbon | +| [carbontracker](https://github.com/lfwa/carbontracker) | Library | Python | MIT | RAPL, NVML | Predicts a run's total footprint from its first epochs | +| [eco2AI](https://github.com/sb-ai-lab/Eco2AI) | Library | Python | Apache-2.0 | RAPL, NVML | Its own regional emission-intensity dataset | +| [experiment-impact-tracker](https://github.com/Breakend/experiment-impact-tracker) | Library | Python | MIT | RAPL, NVML | The original tool in this space; widely cited | +| [Zeus](https://github.com/ml-energy/zeus) | Library | Python | Apache-2.0 | NVML, RAPL | Energy/time trade-off optimisation, not only reporting | +| [Scaphandre](https://github.com/hubblo-org/scaphandre) | Agent / exporter | Rust | Apache-2.0 | RAPL | Host- and container-level metering, Prometheus-native | +| Cloud provider tools | Vendor console | — | proprietary | Provider-internal | Whole-account totals at billing granularity | + +A note on `experiment-impact-tracker`: its GitHub repository is archived, with no +commits since January 2024. It remains worth knowing about, because many people +find the paper first and then look for the tool. If that is you, CodeCarbon, +carbontracker and eco2AI all cover the same ground and are receiving updates. + +The table deliberately has no per-hardware yes/no columns. Almost every tool +answers "yes, if the counters are readable", so a grid of qualified yeses carries +no information. It also has no maintenance column, for the reason above: the +facts belong in prose with dates attached. + +## Which one should you use? + +### "I want to know the cost before I run it" + +Use **ML CO2 Impact** or **carbontracker**. This is not what CodeCarbon does. + +ML CO2 Impact is a web form: you pick a GPU, a region and a number of hours, and +it gives you an estimate plus a LaTeX snippet for your paper. Nothing to install, +nothing to instrument. + +carbontracker measures the first epochs of a training run and extrapolates to the +full schedule, which lets you decide whether to continue before you have spent +the compute. If "should I run this at all" is your question, that is the right +shape of tool. + +CodeCarbon reports what a run actually consumed, during and after the fact. + +### "I want to attribute emissions to a specific function or training phase" + +Use **CodeCarbon**. `tracker.start_task("name")` and `tracker.stop_task()` split +a single process into separately reported segments, so you can compare +preprocessing against training against inference inside one script. See +`examples/task_inference.py` and `examples/task_loop_same_task.py`. + +### "I want to reduce energy use, not just report it" + +Look at **Zeus** first. It comes from a systems-research angle and is built +around finding a good point on the energy/time trade-off curve — batch size, +power limit, GPU frequency — rather than around producing a footprint report. +CodeCarbon will tell you what a configuration costs; it will not search for a +better one. + +### "I want to monitor a fleet, not a script" + +**Scaphandre** is likely the better fit. It is an agent, not a library: it meters +whole hosts and containers without any change to the applications running on +them, and it exports to Prometheus natively. If your goal is a +datacenter-or-cluster dashboard and you are not instrumenting individual jobs, +start there. + +CodeCarbon can do fleet work, but it approaches it from the other direction — it +starts inside the process and scales outward. If you want per-job attribution +*and* fleet aggregation, it has: + +- a [Prometheus output](../how-to/logging.md) (`examples/prometheus_call.py`); +- a [Linux service deployment mode](../how-to/linux-service.md) that monitors a + whole machine without instrumenting code; +- an [Ansible playbook](../how-to/ansible.md) for rolling that out; +- a hosted API and dashboard that aggregates across machines and projects. + +### "I'm on SLURM or an HPC cluster" + +CodeCarbon has a dedicated [SLURM guide](../how-to/slurm.md) covering multi-node +runs, plus ROCm and PyTorch examples under `examples/slurm_rocm/`. Most of the +alternatives leave the multi-node aggregation problem to you. + +### "I need the total for my AWS or GCP account" + +Use the provider's own tool: the AWS Customer Carbon Footprint Tool, Google Cloud +Carbon Footprint, or the Azure Emissions Impact Dashboard. They have access to +data no external library can see — actual facility PUE, the provider's own +energy contracts — and they cover your whole account, including services no +Python library can instrument. + +They will not answer "what did *this training run* cost", because they report at +billing-account granularity with monthly latency. The two are complementary: the +provider console for reporting obligations, CodeCarbon for attributing a number +to a specific experiment. + +### "I need something I can cite" + +carbontracker, eco2AI and experiment-impact-tracker each have an accompanying +academic paper, and so does the methodology behind CodeCarbon. CodeCarbon also +has a Zenodo DOI: use the **Cite this repository** button in the GitHub sidebar, +or the BibTeX entry in the repository README. See also the +[Methodology](methodology.md) page. If a reviewer asks how accurate the numbers +are, [Accuracy and validation](accuracy.md) is the page to point at — including +its list of known gaps. + +### "I'm not on Linux" + +CodeCarbon reads `powermetrics` on macOS (Intel and Apple Silicon) and the Energy +Meter Interface on Windows 11, falling back to the TDP estimate elsewhere. Most +of the alternatives are Linux-first and rely on RAPL. If you are on a Mac or a +Windows machine, check platform support carefully whichever tool you pick — and +read the [accuracy page](accuracy.md), because a fallback estimate on any of +these tools is an estimate. + +## What CodeCarbon is actually good at + +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). +- **Task-level attribution** within a single process. +- **Offline mode** (`OfflineEmissionsTracker`) for air-gapped machines, with no + no calls to the CodeCarbon API. +- **Output flexibility** — CSV, HTTP endpoint, logger, Prometheus, Logfire, + BOAMPS, and the hosted API. +- **A hosted dashboard**, if you do not want to build the aggregation layer + yourself. +- **A CLI**, so you can measure a process without writing any Python. + +## Where CodeCarbon is the wrong choice + +- You want a footprint estimate for a run you have not done yet → ML CO2 Impact, + or carbontracker's prediction mode. +- You want to *optimise* energy use rather than report it → Zeus. +- You want host-level metering with no application changes and a Prometheus + dashboard → Scaphandre. +- You are measuring calls to a hosted LLM API rather than local compute → + [EcoLogits](when-to-use.md). +- You need an auditable total for a cloud account → the provider's own tool. + +## Corrections welcome + +Comparison pages go stale, and one that is wrong about another project is worse +than no page at all. If something here misrepresents a tool you work on, please +[open an issue](https://github.com/mlco2/codecarbon/issues) and it will be +fixed. + + diff --git a/docs/how-to/development.md b/docs/how-to/development.md new file mode 100644 index 000000000..87788ed88 --- /dev/null +++ b/docs/how-to/development.md @@ -0,0 +1,325 @@ +# Development guide + +This page holds the deeper development material. If this is your first contribution, start +with the [contributing guide](contributing.md). + +## Python versions + +Between April 2024 and July 2025 we used Hatch for managing the development environment. +Since August 2025 we use UV to manage the environments, Python versions, and dependencies — +it's a fast, reliable way to work with Python projects. + +We have dropped support of Python 3.6 since version 2.0.0 of CodeCarbon. + +We have dropped support of Python 3.8 and 3.9 since version 3.2.4 of CodeCarbon. + +## Some UV commands + +UV simplifies Python package management with fast, reliable commands: + +```sh +# Show dependencies +uv tree +# Add a default dependency +uv add pandas +# Add a dev dependency +uv add --dev pytest +# Add a dependency for an extra feature +uv add --optional api logfire[fastapi] +# List all task for CodeCarbon +uv run task -l +# Run a specific version of python +uv run --python 3.14 codecarbon monitor +``` + +## Tests + +You can run the unit tests by running UV in the terminal when in the root package directory: + +```sh +uv run task test-package +``` + +Run a specific test file: + +```sh +uv run python -m pytest tests/test_cpu.py +``` + +You can also run a specific test: + +```sh +uv run python -m unittest tests.test_your_feature.YourTestCase.test_function +``` + +For example: `uv run python -m unittest tests.test_energy.TestEnergy.test_wraparound_delta_correct_value` + +Some tests will fail if you do not set *CODECARBON_ALLOW_MULTIPLE_RUNS* with `export CODECARBON_ALLOW_MULTIPLE_RUNS=True` before running test manually. + +To test the API, see [how to run it locally](#api) first. + +Core and external classes are unit tested, with one test file per class. Most pull requests are expected to contain either new tests or test updates. If you are unusure what to test / how to test it, please put it in the pull request description and the maintainers will help you. + +## Stress your computer + +To test CodeCarbon, it is useful to stress your computer to make it use its full power: + +- 7Zip is often already installed, running it with `7z b` makes a quick CPU test. +- [GPU-burn](https://github.com/wilicc/gpu-burn) will load test the GPU for a configurable duration. +- To test the CPU : `stress-ng --cpu 0 --cpu-method matrixprod --metrics-brief --rapl --perf -t 60s` See [our documentation](https://docs.codecarbon.io/latest/how-to/test-on-scaleway/) to install it. +- To do useful computation while testing [Folding At Home](https://foldingathome.org/) is a good option. +- [OCCT](https://www.ocbase.com/download) is a proprietary tool but free for non-commercial use and available for Windows and Linux. + +To monitor the power consumption of your computer while stressing it, you can use: + +- `nvidia-smi` is a useful tool to see the metrics of the GPU and compare it with CodeCarbon. +- [powerstat](https://github.com/ColinIanKing/powerstat) can be used to see the metrics of the CPU and compare it with CodeCarbon. It's available on major distribution, like Debian-based Linux distributions with `sudo apt install powerstat`. Run it with `sudo powerstat -a -R 1 60`. + +## Update all dependencies + +For multiple requirement files: +```sh +uv sync --upgrade +``` + +## Debug in VS Code + +Here is the launch.json to be able to debug examples and tests: + +```json +{ + "version": "0.2.0", + "configurations": [ + + { + "name": "Python: Current File", + "type": "debugpy", + "request": "launch", + "program": "${file}", + "console": "integratedTerminal", + "justMyCode": true, + "env": { "PYTHONPATH": "${workspaceRoot}" } + }, + { + "name": "PyTest: Current File", + "type": "debugpy", + "request": "launch", + "module": "pytest", + "args": [ + "-s", + "${file}" + ], + "console": "integratedTerminal", + "justMyCode": true, + "env": { "PYTHONPATH": "${workspaceRoot}", + "CODECARBON_ALLOW_MULTIPLE_RUNS": "True" } + }, + { + "name": "PyTest: codecarbon monitor", + "type": "debugpy", + "request": "launch", + "module": "codecarbon.cli.main", + "args": [ + "monitor" + ], + "console": "integratedTerminal", + "justMyCode": true, + "env": { "PYTHONPATH": "${workspaceRoot}"} + } + ] +} +``` + +Then run opened test with this button: + +![vscode_debug](../images/vscode_debug.png) + +## Coding style && Linting + +The coding style and linting rules are automatically applied and enforced by [pre-commit](https://pre-commit.com/). This tool helps to maintain the same code style across the code-base such to ease the review and collaboration process. Once installed ([https://pre-commit.com/#installation](https://pre-commit.com/#installation)), you can install a Git hook to automatically run pre-commit (and all configured linters/auto-formatters) before doing a commit with `uv run task precommit-install`. Then once you tried to commit, the linters/formatters will run automatically. It should display something similar to: + +```log +[INFO] Initializing environment for https://github.com/psf/black. +[INFO] Initializing environment for https://gitlab.com/pycqa/flake8. +[INFO] Installing environment for https://github.com/psf/black. +[INFO] Once installed this environment will be reused. +[INFO] This may take a few minutes... +[INFO] Installing environment for https://gitlab.com/pycqa/flake8. +[INFO] Once installed this environment will be reused. +[INFO] This may take a few minutes... +seed isort known_third_party.............................................Passed +isort....................................................................Failed +- hook id: isort +- files were modified by this hook + +Fixing codecarbon/__init__.py + +black....................................................................Passed +flake8...................................................................Passed +``` + +If any of the linters/formatters fail, check the difference with `git diff`, add the differences if there is no behavior changes (isort and black might have change some coding style or import order, this is expected it is their job) with `git add` and finally try to commit again `git commit ...`. + +You can also run `pre-commit` with `uv run pre-commit run --all-file` to check all file. + +## Dependencies management + +Dependencies are defined in different places: + +- In [pyproject.toml](https://github.com/mlco2/codecarbon/blob/master/pyproject.toml#L28), those are all the dependencies. +- In [uv.lock](https://github.com/mlco2/codecarbon/blob/master/uv.lock), those are the locked dependencies managed by UV, do not edit them. + +## Build Documentation 🖨️ + +No software is complete without great documentation! +To make generating documentation easier, we use [Zensical](https://zensical.org/). + +In order to make changes, edit the `.md` files in the `/docs` folder, and then run in root folder: + +```sh +uv run --only-group doc task docs +``` + +to regenerate the html files. For local preview with live reload, run `uv run --only-group doc task docs-serve`. + +## Rebase your branch on master + +Before creating a PR, please make sure to rebase your branch on master to avoid merge conflicts and make the review easier. You can do it with the following command: +```sh +# Be careful, this command will delete every local changes you have, make sure to commit or stash them before running it +TARGET_BRANCH=master +current_branch=$(git symbolic-ref --short HEAD) +git switch $TARGET_BRANCH && git pull +git switch $current_branch --force && git fetch origin $TARGET_BRANCH +git rebase $TARGET_BRANCH +``` + +In case of a conflict during a rebase, "incoming" refers to your branch, and "current" refers to master. This is because the commits from your branch are being applied to master, so they are incoming. In case of a merge, it's the opposite! + +Check if everything is fine: + +```sh +git status +``` + +Push force +```sh +git push --force-with-lease +``` + +## Contribute to a fork branch + +When a user open a PR from a fork, we are allowed to push to the fork branch. + +If you want to do so, do the following: + +```bash +git remote add https://github.com//codecarbon.git +git fetch +git checkout -b / +``` + +## API and Dashboard + +### CSV Dashboard + +To run locally the dashboard application, you can use it out on a sample data file such as the one in `examples/emissions.csv`, and run it with the following command from the code base: + +```bash +uv run --extra carbonboard task carbonboard --filepath="examples/emissions.csv" + +# or, if you don't want to use UV +pip install codecarbon[carbonboard] +python codecarbon/viz/carbonboard.py --filepath="examples/emissions.csv" +``` + +> **Note:** The `viz-legacy` extra is deprecated but still works for backwards compatibility. It will be removed in v4.0.0. Please use `carbonboard` instead. + +If you have the package installed, you can run the CLI command: + +```bash +carbonboard --filepath="examples/emissions.csv" --port=8050 +``` + +### Web dashboard + +To test the new dashboard that uses the API, run: + +```sh +uv run task local +``` + +Then, click on the url displayed in the terminal. + +By default, the dashboard is connected to the production API, to connect it to your local API, you can set the environment variable `CODECARBON_API_URL` to `http://localhost:8008` : + +```sh +export CODECARBON_API_URL=http://localhost:8008 +uv run dashboard +``` + +### API + +The easiest way to run the API locally is with Docker, it will set-up the Postgres database for you. Launch this command in the project directory: + +```sh +uv run api.docker + +# or + +docker-compose up -d +``` + +Please see [Docker specific documentation](https://github.com/mlco2/codecarbon/blob/master/docker/README.md) for more informations. +When up, the API documentation is available locally at the following URL: http://localhost:8008/redoc and can be used for testing. + +If you want to run the API without Docker, you must first set the environment variables described in the .env.example file, and run the following command: + +```sh +uv run api.local +``` + +In order to make codecarbon automatically connect to the local API, create a file `.codecarbon.config` with contents: + +``` +[codecarbon] +api_endpoint = http://localhost:8008 +``` + +Before using it, you need an experiment_id, to get one, run: + +``` +codecarbon login +``` + +It will ask the API for an experiment_id on the default project and save it to `.codecarbon.config` for you. + +Then you could run an example: + +``` +python examples/api_call_debug.py +``` + +📝 Edit the line `occurence = 60 * 24 * 365 * 100` to specify the number of minutes you want to run it. + +### Test the API + +Test dependencies (pytest, pytest-asyncio, etc.) are in the `dev` optional group. Install them first: + +```sh +uv sync --project carbonserver --extra dev +``` + +Then run: + +```sh +uv run task test-api-unit +``` + +```sh +export CODECARBON_API_URL=http://localhost:8008 +uv run task test-api-integ +``` + +Database restore, deployment and the release process live in the +[maintainer guide](../maintaining.md). diff --git a/docs/how-to/troubleshooting.md b/docs/how-to/troubleshooting.md new file mode 100644 index 000000000..a4624f47c --- /dev/null +++ b/docs/how-to/troubleshooting.md @@ -0,0 +1,351 @@ +# Troubleshooting + +CodeCarbon rarely crashes. When something is wrong it usually warns, falls back +to a less accurate method, and keeps going — so the first symptom is often a +number that looks odd rather than an error. This page is organized by the +message or symptom you actually see. + +If something looks wrong, first turn on debug logging so you can see what +CodeCarbon is doing: + +```python +from codecarbon import EmissionsTracker + +tracker = EmissionsTracker(log_level="debug") +``` + +or from the CLI: + +```bash +codecarbon monitor --log-level debug +``` + +Then dump what CodeCarbon detected about your machine: + +```bash +codecarbon detect +``` + +Please include the output of `codecarbon detect` in any bug report. + +## Nothing was measured / emissions are zero + +### "Another instance of codecarbon is already running. Exiting." + +CodeCarbon takes a machine-wide lock so that two trackers do not double-count +the same hardware. If a previous run crashed, a notebook cell was re-executed, +or a `codecarbon monitor` process is running in another terminal, the lock is +still held and the new tracker does nothing — `start()`, `start_task()`, +`flush()` and `stop()` all return immediately. + +What to do: + +- Check for a leftover process and stop it (`ps aux | grep codecarbon`). +- The lock is a file named `.codecarbon.lock` in the system temporary + directory — `/tmp/.codecarbon.lock` on Linux and macOS, and the equivalent of + `%TEMP%\.codecarbon.lock` on Windows (CodeCarbon uses Python's + `tempfile.gettempdir()`). If no CodeCarbon process is running, it is safe to + delete it: + + ```bash + rm /tmp/.codecarbon.lock + ``` + +- If you deliberately want several trackers at once — for example in a test + suite — set the environment variable `CODECARBON_ALLOW_MULTIPLE_RUNS=True`, or + pass `allow_multiple_runs=True` to the tracker. This is what CodeCarbon's own + test suite does. + +At debug level you will also see the related message +`Lock file /tmp/.codecarbon.lock already exists. This usually means another +instance of codecarbon is running.` + +### The tracker ran but `emissions.csv` is empty or missing + +The CSV row is written when the tracker stops or flushes. If `stop()` is never +reached — because your code raised, because the process was killed, or because +the notebook cell was interrupted — nothing is written. + +- Use the context manager or the decorator so that stopping is guaranteed even + on an exception: + + ```python + with EmissionsTracker() as tracker: + train_model() + ``` + +- Call `tracker.flush()` inside a long-running loop if you want partial results + written as you go. +- The file is written to `output_dir`, which defaults to `.` — the *current + working directory of the process*, not the directory containing your script. + If you cannot find `emissions.csv`, set `output_dir` explicitly to an + absolute path. +- Very short runs can legitimately produce values that round to zero in the + displayed precision. `measure_power_secs` defaults to 15 seconds; a run + shorter than one measurement interval has very little to report. + +## The numbers look wrong + +### "We saw that you have a ... but we don't know it. Please contact us." { #unknown-cpu-model } + +Your CPU model was detected, but it is not listed in +`codecarbon/data/hardware/cpu_power.csv`, so CodeCarbon has no TDP for it. It +falls back to a default power figure per thread, which is an estimate, not a +measurement. + +This warning literally asks you to contact the project, so please do — adding a +line to that CSV is a one-line contribution that improves accuracy for everyone +with the same chip. See the +[Contribution Guidelines](contributing.md), and include the exact CPU name from +`codecarbon detect`. + +On Linux, enabling RAPL removes the need for the TDP table entirely, because +the energy is then measured rather than estimated. See +[Improve Measurement Accuracy with RAPL](enable-rapl.md). + +### "We will use the default power consumption of ... W per thread" + +This is the fallback that follows the warning above. CodeCarbon multiplies a +fixed default power per thread by your thread count to obtain an assumed TDP, +then scales it by CPU load. + +Two independent approximations stack up here: the assumed TDP, and the +load-to-power model. Expect the result to be indicative rather than accurate, +and treat comparisons across different machines with caution. Comparisons +between two runs on the *same* machine remain meaningful, because the same +approximation applies to both. + +See [Power Estimation](../explanation/power-estimation.md) for what the +estimation model does, and [Accuracy and +validation](../explanation/accuracy.md) for how the backends compare. + +### "We were unable to detect your CPU using the `cpuinfo` package." + +CodeCarbon could not identify the CPU at all, so it cannot even look up a TDP. +The same per-thread default is used. This happens most often in containers and +virtual machines that hide CPU model information, and on some ARM platforms. + +If you know your hardware, the accurate route is to stop relying on detection: +enable RAPL on Linux, or supply your own power figures. If detection fails on a +platform where the model *is* visible, that is worth reporting as a bug — please +include the output of `codecarbon detect`. + +### Emissions seem far too high or too low + +Emissions are energy multiplied by the carbon intensity of your grid, and +energy includes a datacenter overhead factor. Check all three inputs before +concluding the measurement is wrong: + +- **Carbon intensity.** Read the `country_name`, `country_iso_code`, `region` + and `cloud_region` columns of your `emissions.csv` and confirm they describe + where the machine really is. Online mode geolocates by IP, + which is wrong for VPNs and some cloud regions. Use + `OfflineEmissionsTracker(country_iso_code="FRA")` to pin it. A grid can + legitimately differ by a factor of ten between countries, which alone + explains most surprising comparisons. +- **PUE.** The `pue` column shows the multiplier that was applied; it defaults + to 1. If you set it, everything scales by it. +- **Power.** Compare the `cpu_power`, `gpu_power` and `ram_power` columns + against what you expect for your hardware. If `cpu_power` looks like a + suspiciously round fraction of a TDP, you are on the estimation fallback + described above rather than on a measurement. + +The full column list is in [Output](../reference/output.md), and the +calculation is described in [Methodology](../explanation/methodology.md). + +## Linux: RAPL + +### "RAPL - Permission denied reading RAPL file ..." { #rapl-permission-denied } + +This is the most common Linux issue. Since a kernel security fix, the RAPL +energy counters under `/sys/class/powercap/` are root-readable only. Without +them CodeCarbon falls back to estimating from CPU load and TDP, which is +significantly less accurate. + +The warning itself suggests the quick fix: + +```bash +sudo chmod -R a+r /sys/class/powercap/* +``` + +Note that this does not survive a reboot. For the persistent udev-rule +approach and the security tradeoff involved, see +[Improve Measurement Accuracy with RAPL](enable-rapl.md). + +### "RAPL - Permission denied listing ..." / "scanning ... for RAPL domains" + +Same root cause, different point of failure: CodeCarbon could not even +enumerate the powercap directories to discover which RAPL domains exist. You +may also see `RAPL - Permission denied reading name file ...`. Same fix as +above. + +### "RAPL - No package domains found, falling back to psys" + +The full message explains the consequence: *"psys includes CPU + platform +components and may not match CPU TDP. Power readings may vary significantly +from CPU specifications."* + +RAPL exposes several domains. `package` covers a CPU socket, which is what +CodeCarbon wants to attribute to your process. `psys` (platform) covers the +whole system-on-chip and adjoining platform components — chipset, PCIe, memory +controllers and more. When only `psys` is exposed, CodeCarbon uses it, and the +readings will be higher than the CPU alone and will not line up with the CPU's +TDP. + +This is expected on several laptop platforms and on some newer Intel +generations where the package domain is not published. It is not an error, and +`psys` is generally a better answer than a load-based estimate — just do not +compare those numbers against package-domain numbers from another machine. + +If no domains at all can be selected you will instead see `RAPL - No package or +psys domains found, using all available domains`. + +See [RAPL Metrics](../explanation/rapl.md) for the domain hierarchy. + +### "RAPL - psys domain detected but not used (rapl_prefer_psys=False)" + +The mirror image of the previous section: both `package` and `psys` are +available, and CodeCarbon chose `package` because it is more consistent with +CPU TDP specifications. + +Set `rapl_prefer_psys=True` if you want total platform power instead — for +example when you are budgeting the energy of a whole machine rather than +attributing energy to one process. Expect the reported figure to rise, since it +then includes components outside the CPU package. + +### No RAPL at all: containers, VMs, WSL + +`/sys/class/powercap/` is not exposed inside most containers, in most virtual +machines, or under WSL. In all of those cases CodeCarbon silently uses the +CPU-load-and-TDP estimate; `codecarbon detect` is the quickest way to confirm +which mode you are in. + +- **Docker on bare metal.** Mount the sysfs path read-only into the container: + + ```bash + docker run --device /sys/class/powercap:/sys/class/powercap:ro + ``` + + See [Docker and Containerized + Environments](enable-rapl.md#docker-and-containerized-environments) for the + Compose form. +- **Virtual machines and cloud instances.** The hypervisor does not usually + expose RAPL to guests, and there is nothing you can do from inside the guest. + Estimation is what you get. +- **WSL.** Same situation; run on native Linux or Windows for measured values. + +Note that on shared hardware — a VM, or a container next to other containers — +RAPL would report energy for the whole physical CPU, not for your share of it. +The estimate is not merely a degraded measurement in that case; it is a +different question being answered. + +## macOS + +On Apple Silicon, CodeCarbon reads power through Apple's `powermetrics`. On +Intel Macs it can also use Intel Power Gadget, if it is installed. + +`powermetrics` requires root. CodeCarbon runs it through `sudo` and, before +using it, checks whether that `sudo` call would prompt for a password. If a +password prompt is detected, CodeCarbon logs at debug level *"Not using +PowerMetrics, sudo password prompt detected"* and falls back to estimation — +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 +`log_level="debug"` to confirm the check now passes. + +### "Returncode while logging power values using Powermetrics" + +`powermetrics` started but exited with a non-zero status. Run the same command +by hand to see the real error, check that the binary is present at +`/usr/bin/powermetrics`, and confirm the sudoers entry above is still in effect +after any OS upgrade. + +## Windows + +### "Returncode while logging power values using Intel Power Gadget" + +Intel Power Gadget exited with a non-zero status, so no power values were read +from it. Intel has discontinued Intel Power Gadget, and it does not work on +recent CPU generations, so on a modern machine this is expected rather than +fixable. + +CodeCarbon's supported path on Windows is the Energy Meter Interface (EMI) +exposed by the platform driver. To see what your machine actually exposes, run: + +```bash +python examples/emi_channels.py +``` + +That script prints every EMI channel and the power each one reports, which is +the fastest way to tell whether Windows is publishing usable counters at all. +If it prints no channels, your platform does not expose EMI and CodeCarbon +falls back to estimation. + +## GPU + +### Nvidia + +CodeCarbon reads Nvidia GPUs through `nvidia-ml-py` (NVML). If NVML cannot talk +to the driver, no GPU is registered and you will see `There is no GPU +available` — GPU energy is then simply absent from the total, rather than +estimated. Check that `nvidia-smi` works as the same user; if it does not, +the problem is the driver or the container's device passthrough, not CodeCarbon. + +### AMD + +AMD GPUs are read through `amdsmi`, which ships with ROCm rather than from +PyPI. Two distinct warnings tell you which half is missing: + +- *"AMD GPU detected but amdsmi is not available. Please install amdsmi to get + GPU metrics."* — the Python module could not be imported at all. +- *"AMD GPU detected but amdsmi is not properly configured."* — the module + imported but failed to initialize. This is almost always a version mismatch + between the Python `amdsmi` package and the installed ROCm, or an outdated + driver. + +For a working ROCm setup, including how `amdsmi` is made visible to the Python +environment on a cluster, see [Run on SLURM (ROCm/PyTorch)](slurm.md) and the +scripts in `examples/slurm_rocm/`. + +### `CUDA_VISIBLE_DEVICES` / `ROCR_VISIBLE_DEVICES` and `gpu_ids` + +If you set `CUDA_VISIBLE_DEVICES` or `ROCR_VISIBLE_DEVICES`, CodeCarbon +populates `gpu_ids` from it automatically, so it measures only the GPUs your +job can see. Passing `gpu_ids` yourself overrides that. If your reported GPU +energy covers more or fewer devices than you expected, check both — one of them +is winning over the other. See the note in +[Parameters](../reference/api.md). + +## Cloud API and dashboard + +If runs do not appear on the dashboard, the tracker is still measuring +correctly — only the upload is failing. Errors from the API client are logged, +so run with `log_level="debug"` and look for messages from `ApiClient`. + +Common causes: + +- The API key or project token is missing, expired, or belongs to another + project. Re-run `codecarbon login` and `codecarbon config`. +- The API is unreachable from your network — a proxy or firewall blocking + outbound HTTPS is typical on clusters and in CI. +- The run was too short to reach an upload. An upload happens every + `api_call_interval` measurements, so a short run may end before sending + anything. + +To isolate the API from the rest of your program, run the dedicated example, +which does a tracked run with frequent API calls and verbose logging: + +```bash +python examples/api_call_debug.py +``` + +Setup and configuration are covered in +[Use the Cloud API & Dashboard](cloud-api.md). + +## Still stuck? + +Ask on [Discord](https://discord.gg/GS9js2XkJR) or +[open an issue](https://github.com/mlco2/codecarbon/issues), including the +output of `codecarbon detect` and a debug-level log. diff --git a/docs/images/og-card.png b/docs/images/og-card.png new file mode 100644 index 0000000000000000000000000000000000000000..12253dc9986bf568ec2f24e338ac8c7d271e20d7 GIT binary patch literal 39931 zcmeEtWm{WK*EUjIi@OwecZUKcxVuAfcZXud-Q9`@io09TV!@#}6nBR=*Yp03`_s;m z96PgT&CEJwtsSMJB#VYjgbW1*g(mmys~Qy4r+O$T7*9kP$TxjoPEnwsat7qSN@#fJ zp1oltk}d`uz1;uX@JryPB2@i~`lXWMYd@1){3lsjkN!IJfstHcg*?PqrhA%C!yKX` zk7;lRxaiB`pldOMa0#Hfv489tBDPsCCv+KL8w^b{RLdENTO6FKR`vIb{KFmv{4kA6 z8R69F#>UBFoga5>>FoKU-$TLT#+LH&Bj)mIqt(Z8ci_o`es6(V(4!mz@rRgoQT-!vp=vm-EF1Y*qEiPZpnifUH}5t4b_k=TDmcDVUfr3{#KSab}N(UyLb918T<8gg3+k zb=RUfs$Tr>ms-P&tVcmMOd3KR__W%Htp;{CBjRpeaf1Ni@eq2N<9=>;On{4D#f!iC z(UG4)71?A7q7O9?cY$H2!tAH-L_wmhLny*HljZjgoPd?|7WWjwuD&#oJDB8&w zH!qFl?TcoPS?suxLkfey#L@??;20OdJ=#5wep&z>Z0IbnA(XO|nkf%Wr^X0J4!Wes zhTAW{#;E>4zQuU*BXdIm66^QVW)WaWI;YTMqEK1QS(k~IcxKxpJ;g^(7mZWD5USQ} ze7@2RuGv=9q5CU$ul=-zR}1i1!qQn{nn$QSB|*Ln*a%alB!p$C!&-5oshUMjnhMl* zq0fva|AuPgZ=;#lhz4KdCNt6+ECAg( zB5(`B*lXOni$U+&(-Wd+bhTwwopsQtfHiW*74io3b=`$mVxPYz;&vkDB=T*5`09S8 zba$@{jk3Nc1OtoZ_-_Rq`GJ(6=_azM^E$HqjOVFIB3_M!7*aQ#nn zN%#vZGfD2a4Z7RCv0OA_&nzxsAh~SlVC2xq@36Qyu!*ih1t2e3tv~qB9#;B^QY@(X z*DqqHG4YBrnc>`}Dl^Iu!(oE7K=c>+Tt3ylvaxpsM3pXzCSRJw%;h@T!A2d|c#7({ z7vM^LfBJ-+BxE}h`+iga;uu}dwmjt53>k4tgy^JxioUXMc$BMyc3i7@H>-4FlRi5P zC(yt6!O|PK@_6Vnx(vBM0a`v{vz4rT6tx1--q5S!^XtkxXxRRy-DcGR6yx+m zP_kB}DMtzaG}jD?M-cJUZ^mcVyFwnYe-T|r1&9_Hndo%5jg^HP^L@)*PnN?JYtdHI zvS`adPac28RhzkrGvHtYaM~dU=83eLrxvlk_(f4OcV}6^sVS_!asTGRA+sW%Xp3vE zA%ok~8jSTdRM5fyfQ3G8B2-SfzpE!oficVypmHzPPCF_pvSib`@RKe`Fn`{0iq-`1 z6nG4%VqZj_GQrbU=@q@fYqCQ8u@_lnaYctF9Br(qmxwCCGx^modc=l_M{PgkPQ>^{ zb!!1js-c3QWMIbBcU-gmql(s8PYW0wvi{VgYN{IIK4ao(Elrc~DMjLkz?<_1N;1`) z)&$ZV_vZ>X%!<<4X&cEN?DsUVQXx$(DUME1c{3NbgN2vgAor)aqV&8U`SwyrZ600Q zi{f(15;g%A94IgYZMNK3p@;2^_LgZ+X?$V*0>D+zb4gM6r3?nzC}Z;*WTaaTp%DR8 zy;#-jq_HZoNIJQ77j8lge(L+Kke=Z%JT%Pl5|cSB01a!iWq5jl_tTPBXw6U34!}@I z<-s7xM6PVdF)1*SbunF2hv4p0a_|z;p~+f>v{^JaNAMn?-SVQQXIM#hr%gZFQx;Y<=Y2NZx!^#b{GNCb1_=z*JT&@ zt()=K6M^nVMxK`?HNX7K9J$_;)AHAnXN1qZfVWu~YOw>?dH`B)-Hs@8X>opHPkb!w z@pg$unUQuas=}7ykZ;Fk1JVvpMi;%~32d5IzolqMaL@FHZD?!)G&VT{0y!dtyhYON zAD$cC4m~izH3U?RNf`M#PNaTwSh8y(iD>ktSVKPsX03ng>C-AZZF0Hp=`AsOMNZ4) zGoc)#Zhc&%ZF=ezg@wb3WfhU}iX9`U3E2{8S!AqKIf+~6Qwwi42PKAyH`#J4JCx_J zA)p$3`#Zj9u`LP1S7mF>;!mW{;%tM0!qUgj!yHSMo^ID;lFZM(;G!BB7sC-dBog<6 z&*0-2zePh^M`Z5($5{<&j;UAv>lE^Va`NK>UOm_F=91jb7+q`2$M}iP+=y^qeqCD1 z&Z)v^N-fC?mQu^7FeOY|G4@(PbwHdaH7lPzdzxq`sbb5vS+GR7-b|^0?M}0X2BUz6 zNCb}9B%a9_LRF1SVe)>=kBFn-kXNVFncO$SoygLXx|@0@vqqy~f?tw(e*(dA@Lvua zc^sRj7##Y7zsE31;CTT82_oQVHB-GIX)LP-K1O`Wg~(Ofy+qh(GwLe7+@Li~V>_`! zRUxyuypXP$H~kFgt0ht-^D<9|OD>zVH^9>qO6pzvAmM;Ri%3JuonW<^KQ~(xzAb;V z|Ht$8n0<>I6pCZ09-DM6a5FfhhqQR_o+uSypBtle4*h+}i$LW7Xk313>K=fs@UJ%b zf``>(6y#c5-`a6lr3I(H#l`S4xwb^L@hezG=s0@FjOC&GPTfC~@%_Y&FbaC;0Tva6 z(NwL5i&fz%Icy^6qO*Ks>8~ZdHRr)FGUke!px~2iEw)y`oZ_AQtK&ezpa zq~Xu?n$a?`S#M*2`{;Sx3oa+!80z*4G}Ve@8CZ_P4tO+X!36F{V+43ooq`E;gSC`h z);E;TcDE$sk*&9Z7*g@uAluj~j(MRP$5OOC%GA}_Yx&-B(hv3cSGMc4HzH8-As#83 zmTl%sWxzqTQ2k~z2c^lNOvaM!=05II=GBC;%!F!+Qux*!LxEIth53v$FG~qZp{p9c z`fYn-ixp=E4;g(fNcq!0s_e0B$6YoI(wSsaf|YN2n$<8Z{E7!X5zQB0)Qvo;^Iwf> z>s<3(BgcMi%{F^A=pNg8`bhiig*Bl~JqsB{AB;g@rNj(Xp0%MekNb`Ha7y|{PWXaH z1jad;(737>pol9Iuu$zYE2O!DP1wq0w+YkEKR5(oE$j>ZS7NYrLB&)JO3l{CN%_T? z3)x$<;DIq9cL}{n5oKI(MY5!p!#KXjIGd%tEKAq-D)7LGH-}8ec33*@k(F2k-8(4) z*>QC-EHENN_n-mhExgD*>gPmEL)*f{w|pSmLwd2FsjRZs1e@tpFl+aq)wOin&{gkK z3O&vKnzv`BbG`TsOhA`YmT-0DxmLEUmII@AX3tC)+-NY%Ce-mWmPJN@;lyhVdNv7m z`gq4-I*x%TT^dVXs@8ujs)95H>W$47x23Imo;(Dh z^>qTwRe*A4YMo0mckUUHU#U$3Wa4Wn^Ou?i``V2J=7r1>ndkcK`J7m`D><<-o(G$B zbKU|~!M5#{87h;OaPZjpda^el_a=OSM!5Q_@aN>W>cnaZJg%O`h~;J2%dwCqK-QS^P4oNwo06PJ+k$-wdqlt`$oY!rQ8VCCUxSqugI++ zppvd%xJosBP^!N5uiXCDX-V(q)nK1&0KzU0c8IW_BgU02rAMgk?|Zghgs|1BGmtCm z4N9tXm0smv&GtoG{eqiz-)%;~@OQ+4E-Ll7%Qh&|-e53$tmT=o}7g zVG^+K+fYvGo^ytA@uY_SsSj(`jCS~JZQrYkAlH-nC1Xr}HheM6=Ce62hJl49Q_h(H z45^0_r|Rr}G;oB&q;99|Np;}8d+v1??)jkXX>Ne&TM}L65hVQm{%mfS&28Xh7F5L` zEe(FX6}_W>IOaI&8Y%k;G(?}6_YhX&;717lc{dPMS>Mi~D72_> zJp80wViw7Kda9&Iy$O>u-mluI`gg6b{g=R*r{oNdvAtxQE|4N4!Ds4v(=VRjY5VA2sm0E0)w5p=cQ6s<+vtFIgu-~61hU%d195rZ^ z@+R!+H^rb3q5W<38)}U7%}+Ij=%UoB%_XlSk-6PNt8-;_&ajn#(s98=4f_$v1c5DF z6O#m9yBZE&mr1%U_FW#fn-l;$pGmk=8RA~bVS92K$_EDoBqEB2e^jcA+Kk;R@&pUIBZl>M@|w2OOo2enJHas>_X zrEI#e!lKB7UnGNVVDErKYaNnTjt6{-4&&P7-0!`}VrR4S{pFC+F z0?P9i1b)py7jgNa@(&9%x0F+BpevEllC;t(_|MCP6h~J}l=yc-vc;DSzu^38RXs_T^T|L#C5ed3PaQd~6SdLJR`j7D_;MWhy-TCHZgCl$`?9Wd zn;bT~b~o(Mh>!j1>Y&Ab&P_eqStQa#nzvMop3`4-j{s5(N_h`d z{_1|>tMU>zSJu2>=MdV9W7fK%F!rS7HX6s=9E9UYJX+yk{)5VAY2FmG*!1M1jnY}A zt}1KkjJr01!G!#OWxZLr?7Gd z^qgXm0CzHmjW!+b=-JdYVYcQgiOaCY)$z(v{pE49SjjI^NRRO|CL;T5-J9}+g`l1D z@If5e1(!?v2#XNymp#oLd=qG8@sgf$_i?ggfhc^I@$yQA-U+*Cx&=FqT+H&cN1G+p zIa+Nw#jlp-RlL*$@{QmqVm>Q+M|%qVmFxV)M@60o1o1eu^~z$k+gRh<#8Pi8WwdcC z&c26ltQ5x0zSfu?^chmN%wjiF_zcqR+SXHZ3B7C310`6XE?c0m|rNdY|RH04(<(~>fsfYV0 zryvC14u7snSyGwz_Xs$SNx9iKL@07kVtA7;WsjmFNB&7*+lnst89O~Lm|c#~Fks-+ zhnm9S2H2IB$71Kr0nb4h{3DubQeQ?I_JpY&IU}m3yK7u_chkgk&mlVHu@evTb9NW5 zU3(^ydcOJuw4!B$*l3N6pl^rWFegr0f9G&~{^`@PH{+pE!(F471=(^&$p~pwD(IA=yX;fyWfK(;vY zBkzBj0KwSRh>7~CU7O9vv~1#X`}<=Gib*Szw=eKus<^qqG+NYThBrA{+Ecq@9q;X` zEEH{zC7`J1?9&GBE{c6d&+pb7u+S#4pRDJ(QIp%l&bkp};W#IWj#%q2dqQR=OU){p zLPl3917x92R}CTCLb1oUp%PucDO5Nr+HO=~?fl|(>fRq^t?*+B(W2H_sMzV+oW4A7 zM;}}-?(}z&1qvS(#Bu{&+*78zgumjt8%)SRDtB+W@;endZq31mfcc4@+r3zIZ6hXW z<$K}T`g09CZSv0Ze^lB=9L?^`!x?W$at~!0vaP-KsbiS@hX2@xWppT{GwI^$6Rty! zTjz@9%H3e=VyL#OnT$OTl8V4{E{gAe;#O2k^1?D+O8MkkQj-z>WZ3hi8tP@GXej3I zDGKeDzxX$=zqh-?3~(14HsNrXTFYNhq{@H!-BU|znPWwJNHu(xZh-PEf>>MLp%j1y zR#Rr}EXvP26dhGGjODl>BVCy}XuvG{B=-A#;{+($V8Z!lJp}3EH{Hzo`@o#(`KJm> zKE0+)S@%E+AuShreuWl?chYpVz{*sEtX(lRA--OzU-`8zacrl#=W>l&Q60@5ajsEM z{<2G+vYIpqgE#1H@S$QzR*L?8MjVz znNgkt`{nn{B?_4IMcWVdrLH2}t5mp_6v5GLkT|w(x?7K_IExNQQiSgORI%FfYO1Nn z^y$)feSYVR`DwA%5${Bu)+8xZM1GvuN#LODfv58u6Z6-S>wX&7(hYvT6@e8&DXSO* zsL)bM9&109TO(Dj{kvnQqojbXJM}g+j!KJOnprG#HgYL5dBquTc?j@w>E}1{QAr_i3XZgEzyt!yFX{OyhWTY+=Fjt*)093ydgOiC# zyj{$k6$@h?#9vJkL+$`O;>~Ri*+l1DhrUfzPK9%wv;&Dvuu4QvMBP%ZzA~C?CLdo0 zQQ|CP1K--uT21pKfmg1w(s8{+2jCp^4X?v~gOYxJ*r&hRw2s}!DnK!~imR{tT}>j* z{FM$!0L!K}P1WkI8Y5ZltgoRiG`D`qE>e89RG}HaY>wTrCZst>q(SnUw zgsZ{;dZKMXiaxO4Rdpxm5*z_ z-VjZbh%s+J`lqfWc_?B9nYZBQ82mW3Nap+nnHQY^kmDrD{~KfB?S$QWHba0|0M59D zoBVgf@2@-z-JcY$&3U>P##4^7aLp|`@4NvVBPz``xY}kWo+pZtqV4Y+YJuD)`+dG! z(Gn=Gk7b{=djZLK5k*hWZi#Bmk7y>ZWQlEMBhm_QHhH@skWWB9B4T8k1i~`&H#x@r z65jdvb3y}CODK)+zq^NsY3b5V^rAR|Qsu9P321X_Xq&$@6R@f7z_#sj^%{0Q>qKEd z)qkUPIB5oNpBD7@3Rx)d60f8SR%WtG7*ri(1>})l{7U)~r@D1pNEI$wH3M#c$SG zLDC<;vLlLqjsBMANj5DF6uw1&4pq5sGbj%9!oZ zx*UA?9(5#;a0F2c1&TbE)#aH7`W3cJ(gqhK79J5UVfwN#XmM_l&p0;`HSlW4zcQz= z8WJO40f>=!1=J_a>0i=-x`YvE_|3e{C3sv3S6$Zb+fV_rWs&-$+(fZ<`6uIwo4{$l z630J4#Vnj=t$7b^a*hP}R!|Z@I+&%x~a`ESCi*hF45EaE=>+5B}))e_=d+U@TMwUEzon490Su#-mxB zQr6k0DPv6+2-x&U<2O5{TmSL)(n7b*2yCB80L`bn`Duo_P&L0*5aGp;Wx4Z-b@|^= zS(URK#|{q2270G&pIvnBnpC|Y*p68st1`#BdR5A>j=FPaOuUww4Z>e?pAXsO0WLW+ z+N=!w+=5=M7Cc$h(CwZnDwPz)gn1G@JFGQ4>EpU&y5M{h$F|Y5J-Sv@*fl+5<1lPz z0J&!=AN#y6e#-moi6byeh5;7C-mVO%5O}=z)3Oih=brVdDsT|mRqnm)r0w5}cKPV2 zR@caaxieo~;V0=hVnCcSN9veZp2*LvPxq1BUk(jsm&a4g)|&HbIz*zY6F^V1% z@e3g`0e+sW>`JetCeOq{;fS+}+W1r0N_SPTi_JQz_^F)G=A9_=_0I3ojsQz-A$#eX!N zOFaAlc0OD0IJ7{vjn&%p*k$Cq{TiW=7aO1}%cp?2P^Ku9z&OE?eDc#L1pT0krnya`8g7^57)&B&LjlV{vVDV5@z@T$LtV*^P; zYIC8#&)Asdy3j@uXTL^ebh1zWMoO2fkXs6kjtGZkF>%NIM+oAZrcLY&tU7J_=k3{q zL=cZYF>|8AnO&wH39VD685597H1W~aV&IcdM#yS_5mmWV%`wr zA`=cgj4|F?I}XZ0nJEArm&2xW(vK-{PZB2N~9<<#TnKjnQ`Y#5MU66_u% z8N$YR!~Wl0md$Ahp!<<&KJbIJ{MorXkDYx-4EjVQ54T)BLk876gj<7H!Y;p5{L$F z>>R%!B(+W>kn(m^ii0}DptTgEMM+;0Sa84gKnpKAZ9-!&t9P;qlq!)Rj^@yCW%DrX zln9R^92i2~OvIAaWj36v*APe)PUHCiPG}l6Q^D0*qG5-{2fZri3UK?tTFWYz>%uUU z;c5Gu1X$Or9Pe@My$>oL*@u z{~EaP;$mB84tf7E(G+A(A>S5Eww6qhenT%7E?Sw>1NHgZB&n!M&{t2D8uPwvsa8H6 zOTY{;YkUDQdbn7M!TYrVU|84~k~3z?UG-)^V)(j$)VEOzs|A04YfDc~9EpQjnx8L< z*Mq?!?$jzK7;G*kD4~QdlDlT)-wFEcL!O$BGyZ?iYfJ|x>7U2091-`Ss4 zn0C+*=@{?WAEH)OV$0$-87$g>ve~f#c|@)$lqA`vwQiNE)onw_Gigb`Lc1+yE99c6 zcR4x(DNnp>ORycU8^6onZ1{LQxs^SA4KBwHQ<}rnU%aw-)&kE!&UREx{VFL%O*D-> z42z6-Fw*YDeD$sVxqq_ zts1lGO?EXShy5VDv$JVWt~vRtPJ&E;&rx8(!1A;S{P}qFgRzKUkRwx(ak!gaYEu^g zA*{Mo^%4;RfrV)Rj{VMExxn^lj-qt3UYXwG%idkd_I@3>2K|=CJRyXG9xVGc5(<+ufK?gADjlSP*HvI z9Q@RjguCXQdh@;_C)l-;2D;BFG7ooMzJ~FZ`pk3}>fpqsc$dkE+KX_fkoSK_SDL$1 zSG1W9^wbWKlbk1RHIVz(q|bnNuX;k-+%|s*qLkwALhiZAmLR&rOYDzP56>FHqJI!v zBNU|Nj)#}VWzt6_Q3PPoPL~nPtuD^LIbF(3yRilTlMaK6Ew9?)B<0P#QojS${p)+q zFDM4(Mr$$0tRX?y-_CiJ0UVoBGKFX;zKVb%OUEc29(7>Sam<;?Zaaq{Rj@1;UyF&x zzpxe3EA4(RAHTS6Hjlr4kBkwrZNkoriw1X}rT_DG5oLdWh#+@>87R`+f7VPJ&4PQ)?fO-*zT!F z1$)5PB};0w8fAQJ^mYHTuYo#RB^odTrpJuTr%gUp-8&X7U)#%au;Q*v}Wn z6A?!VjsrwSuEelD$$U2dmoBZwtT#U&`Wuqc={y~tj%4HaX6@XE+k!S^<8OPXAhOJ3 z8nf8PVzml{Z%tO*-ME+Y8FRqIBp~|2g>PyB+0R}5UTX@GX{ku_3%E-BPd40=_2U|- z2^?5x)(6r~J+v)VSp-Eo zZgUiYKZQwEOA0UljN`MZ%5hw7LYItsi@*Esr}I=Kta?Q*mdZWtuB_~_|1YKLBQhGD zV?Z*nLOyo1DVX z0Q_Hf4a4BTk$c^7oas&Bn*EH}a!~Eq!1P9pB9@Dn|3Ry+$Nwo|h~6Lj8^R*Up5(ZO zSbt@nub()eDwL)gtJ-JTt*2S?8=;poiJ#`nEQE)*UrdJ*iZT}Fzy3L5 z#A#NX{`)e~Rvqd88yuMSV-5j)2&hTfOby+hG#C5)7}?0w0qQbcrC3vb+hX`hh`EXz&L z7V$}K%UoJTYAXHlW8mbX*p`1EeHHB35@)tRwVNudbP~gCZF6LQT(o!;ce0Re>Z{VT z+yF@T45&qgXR176H;u><94jJ_8t|K$3Y4N#lI5ci+xnm`GVN>eyt|zjdnR8iFuuO& z6`|s=*M~ek9k{pi{F>ngp%LQDd<_*}?~!J4Rd2FAA&3kIfSQ{&04@R!GP{<00`)!R zcv)rd@fbBhY_z#Cu=D_8Xc zl-zU<9aU@lVA5&!6Pqm*rb;+9?MGH>8=D zM$dPd+`&E&9cAp8#ZVY^B4R~@Eg&A52gTB#FmZEWGeF`fxGQIiLoc~Yo z?w~^rN*&Pokt(Z0f z1KcbLu6sxM26m_5NT(>!O7dJMJb&2VKqkRVh%$i&f_PDwhB6`87y=If`Kqs-PF*Bp zY7pjbEche6nriWfCW(;2J+?NN$kx@uUrPc)+i8ijQY)b?XxS^IjUGBA&57D&O=!h` zAdT43d||HmaezPS!-mggpU~!iQ}Ra*mflIx7#PWiVoSddqpxHvA?)Z}Q*$uk&oLKv3UvE;A`g z??)W$P*|T|(;iaH)0g?cK6|4*C89tq>|ZkCnXtx!3jAd^dT2k9un!{-f_f(Q{8DEK zF?}F1_Us|Q^X;k3QiPmoO z-eJv}(NpunK1387d{)?*iZ(zGh9Swk*-$ZDT9Bn~j zDT7i@U}52FRDT65mr5%Ag@c4;gWCQ>JBO5bW)9G4cp)-AV;vq_s$shrR%2Ygm#lMp zzPI;jd;cJ0{rGgf`v{`(*C8k2Wu=7+^KH^7VMH*Fm3UP6_y&?8V5Lgrc zfucQCV<-~vfAxoMNHIcL@Yu^_K#21gQi*7UNUyw)uA{yo7~^~Sk%5b#o7xyH543`l z|J)THqtDhPkc^r9_m5^(kiJu`M}=k9XJvC3Kw6u+aTCROx_)eMJ-v~9{7u^9dCmSF zd0m=HZtef?IaY}$Qba}UjBSKPwkN(x_i1ps8JuYCRnbc!F66WbOI{F}dcQgS{A{iI z`KY6oRmCQsY0)Qr){_lxL-722don-K)k1NL-n!4lOmPd5jl3ktP2T$Yvxk1V5?5B~ zaTBfA_rCPI` zo&)IzYu9id2cC@5wd~ukiMtbo{}}bxS<9CVo8G6Uue}L*qVDF{GSrdopMD1eV^Ux<7rWs^YJ|8N;^a?Ef z`aOPnHs5DMj`N_G32f9Fgy3;E30^&!IC?_VLXCuqo=`wBK>Y!!z@-54NDn0=p95Lc_1)9Tk- zh#A@q@s+2ZK0`yf7BK@sxt1qXH9F$-OfO0Pcn>$tB^svtW&^+iM%vWDFrJ@Cp?~OU zCtvqU)o9U5Ecn?-ErZoWUI!bEw$?bhuIQF}{UDaOYv5O?V3OtRpq!FZdvEo!VD|Gw zU7aFx=}2z(fJz=kT$8nDFn4)CiBZXl76?GJvR5g8;|eu4IQyS9I4#0%+QH-w`5%Z2 zhFAv&r4gk+qS4-DAWy|6PYxcKHR(V6N<%w&n`uUQJ89!P!~6}>oL&x7Q1JfhzQ z?`wrd4xhJ2WN^H#tn$k-JXrhOi-cK@U^EPtTq-VQB!bdXSnHEGPb4-gDGDLv#x&J5 zj~ct(-6m0rTZ2LSNjY}{Td|+;&!hzxODOK_Dv=mv)C?LdSIz?(9HwOm;2b)neMMZJ zDn*_cNxiYJK@*SWHJyRcF9<3>GXh2K$1r99JoWk8VNk)7%LEvDWSH4JuDQwxMMHt= zhKD*cSyv??fun~n9syQ@**Bsa^ET>y`TE@vxq9+v)>R>QY4J#YmFUVcH_|dfY7(nK z@}RUcC?vS~Nnj!$!7c(c(;LKhtb_-PJ*Ks~K^7G%6;wMlD}e-8?g!!dX1(vKDwu+* ze=-@FC+*S=fph3J4|-{2I&5ejYL>xi$;flF@vnqYzRQ3;wjgT6SgMgW8SXRrnPZ7Q zGl&&_Fw>F;IqA*SXPc5c`eWz0idG(T70V=iglqcUP0w&tX?>V&*qgyX=KTn*tRWbXbAW1&%h zlQ&3->Ur%UnChpZ4-Yli>#M%}1nK9g?tq<;A=Ih!db^BhJg>+fh8;WXOG`OywS z;op|Osb^Lc38P`@wH9~}YH@deY2Qs@ux&v~tUX@Koky?d0=w_hhcnE&SkpYE0uT@p znGHN4y@>11~AgPXR=SU(*J>)7?D}3ZTH?6m|!#X*d#PJ zpGc1dk{Aa*9xZHxn=zIj<@O{hAyO-LjE@PdGN)@+{xq5nyw#L~}CE=*37JL|>a(0iCY~zN@absIX{lK@kFt=0ZlHkxAPC~ zt}BQ}zxUH7$xIA5g1`ktC%i=6U7!nR??A!{DJ+x(fnC^7#JpDq3%Y5}w)9#oZZBsU zMqPF&gcA^jXP*w4gSPu$a@A~Voz`FTv}2yrl2Eh{q~%EX`%KA6r6V>YBL5%ttj1xRxg*#Z&feH3zNSUyGeC-?0q}FGfv^9iH-ixW=e6*5+XZ+1$h%`i%z= z5U6mojhFsm&I$_`MH=iNOJK}z;i&v4dnZaw(2D{~|DFTA1=aY&zId5Ei1E%LM3;Ir4wR=VWDtA!@33aNcy(q@40!0=grACaR339m6j|t^ceELpA+&(M41=# z9$;4f%JB@jvU12P{QR!Z%lmw3<@A(;aLy^llzIN15i9C^GwKz1p&z>OK_w<^_j0pB zeki{WDYyqH6BTIvH5nMc+7An_mwZLb@m{sAAg9cGvIxq(aLGA7O3bfy2VPA$pQ6|+ z00`XzetTTMF?A}}5Jf4=mpiRiuChcOMXZFW1ERZVGH(GWC|0lT8K||*3M9vJ`<@ z8-EEYmTV;;43U{>G~?p+y_I*<*{PZ9;2@^ggq;buI(uYl` zb}h*JMsqZ(iCQ|7^{)O;VgVQW#6aDQdp5E>_ukt(H*|%)EL(QxbAiBcGs#hx_tR_- zHsQy#{vXbTfZAPtShIGDCa&|jp2bm>S`aJFD_7G;SXQ;fNStS zkxKfp4k5bB<6Xe&f`lFsff4*IH}j9p8%=~-p|ECRNoVwTAd+mph7@I=d z8j-HKnk>}ka>#r;QD1tAyPmwS6qL01qKoABD&nA>3JF7XgQ^9ruiQ)qjS zo){0LsAw-LmI!#Y|EVxSEkFg!_W!OsjJOyW|UXN-P?7Xa4ftURi^&n0z2sT93l-jAf9!Lm*3IR0)Eit7V^O6jMdRNGl%J6eDcsrRmw(*@_`aWLrUk{d?e0T7brtc5$|8fW~-mBH=lnNN6 z@(XexE;PpdDKR<%=A1irr(^H&BBBl6hhebDaamUv&p&?(gIll#|03b`P|z{D8zxc4 zvcio+@>t41Z;DWtOSO0AtS95Sw^}l=F%t3ic6FVY|9D9+{{Sk#-mm7o82h<>7>l-= z?>}CnE!C_!Po)JEPeC#_kHWGuqz_FQ7_{^|SZ!LHmu$WM>Y;(fRK)-G2z=<7x$X*C z?mE4jcgSsA31>3~g7*WV#!r2R7liyo7gBcJ$5G{+z4{svYD(GRJ>0k#aBsGVWsA9< zuN9`^!d4UBETw|^>%DgqZ?ylO)DUcjZ@-=C%=9GB4$&#DT-7XG3>J9)lnks|i{^^1 zCe*?tQ%6$iYXoL047^w4axfqD|^~ zvy4H%$n%T;h`KJGhhV9_?@pyAanEH@C!ZJuR#88|PlvgNeLp{QC)yhk&*`BLD?F&> z(%-<^us8<7f0a|P_BBs+rmlLmtR?zg_^Hknx=1M+Q4_l4I~G^iesAY>lIPC>$pb}4 zf*l)M?Uu7~C=Q6CQk8w1_qy45I@%@g@wT7>~(9o$F6+E}5AO&`d ze}7h-igQR5<2<_1;f50p2=BIUUwPXWV5!}QNp&<4d5Nh=Yb%}KOE2&}nq`?07Mh${ zy`F3&@9=XY!FKd{UcuOSTii|nZurhqj&fe;W2I+O=U%WCthwB6!m<6u&s_2aMl$p^_4?X>nRXBg5Tw2TwGyY(;0j@~i`&?) zvY#I;pTuI{uE|0|TKJwGa#!&=Wj<6tKhSe*{ja}}=NahVCPc1Pn3qAA*y`dai4aM4 zZpX7cD7*(yiAHY9Oxa+{UB?f^nX00c^hcik3Y1qzl}oSLfUa8P{C-t|^|IL`SfzJb zmuBAHqWaZhrE8*QY;B>2+jye#guA?H>m^68A$+6oJ-h_9MpnI+>l?<1p(zOyq>q8Z z4Fv^4864efTp$^yAn0&*D97P(EG=A^WFb$DRw9F$y}=;?Ka4g8uS&+DoK!bxI(6gw zNMn;zF=Qb$DE~ExdXIDc{5g;f(dqKFvwj*aIuW^*b2o|;0u~JgaTjwW2w?5!2T11u zgqTuX*(~1{YSYW07f~M!1B=r#xMi!?dNYV#R`ZqtUCD9d(d2X2qI4 zhlS5Lj+!q)%YjW(LGvl(F0TchP$vOdzxCkl&8gmL$AnfE;l{bl^7E1$q&^` zb$409?yx%OZb36(SEMZWPTQb`ioub)Jk|H_0r8Ymkhkl<%h~Y8p1SiiP`a)N`yQYf zRm)lcjRV|p4|!pr9f+;r(>{mkR%V-*z84bF!*?bd%DOiDNnVOnnKQjCqkfePGoolH zOfJvRg20;Kd*&05$cgT}uWE@A=WIEBW5H3R9vnz}*?Vbn1I}s!nrX7adsKNMKK9F=<(7B1y0^t086z zX$-@tu1kw0sY?NCFTD>e3wF(HjXm-xUL=YM3S$@8hR4en$YdOG4MsG{H2X*Bh zwtZj2tEK<_b770R?Lf>_yUo2jA)a|c7 zhl>g&*Q_qn9G1GW7Ob*2B?x>hxgT2Lo`y$u4^$J%8k8;PtRb5OC;$F)0O=!enShZz ze@Amf(U|wfJiDOAT3>jguu|{Z*M)oURnK$R+WeAU7bRF2?OgZmB~>A=lH$Am8YkReGib=2vFPfb zj_$7D9bkKXEY4`7+5DpkgBS}$o*N?e0k!+tA#3b`Q z9G=pc&@bz}l;AVIqfV)?toAA}5s51zXyFv!suN}1GWzK{Yx;EC?FAj>DaLHkYsN29 zqdq4X6e{{_Z!my~z}!xmClE~`in~TLkLA$bN`WRPEB)kh3ypW%F^JJjT{2Bl*Nq<8 zugt=t5dLC;&&+xx>IljEGekf4S=NAkj-MU)aLD~=CQ%U&{*>`O!D%C;yl3py8uD0;W z+rzC)$ERmGggYRBC~H*3*HK-5M-{sPxuq68D-qhA`Ozceldkd#L{h7_6K7WhJ+AWA zmM=%f)29<^JJ;%27BYqD!a7v$krLw3j{c=Dr+F-FMLyV-TZqeZeVnrD zRwS&eBBDC0z;av;D&P9x4h3=A)}PNg*hccqlfO$a^fNBWy}1iYX!p&V#UYdu6(_{& z$;$sG^HCyN^;H6AR;e)Rf+^1gkKI^3;{j`j$s3>Y$!F~z;c8{tLXKC%g6#IhC53|K z+Am~`H`=l{N=4AoyI-=0R#L~T+2{50$cUE93kDYM$-JevS(rw%{3>avSEv*aF&w=L zi*&$AzUugD1ZtbEH81VCbH?~NtdrQvuVImbT%jIJ36w)^?HD%%Kv6Z2%#;82Xr zJr`A<{@y#L(3F71sjT9omFM#7u}Zn$xvz6LE-zut<&+t*+BORX;v97C0CWU`%$9>? z9QdZSC{f~vPPLkwm4_NG4+q-TcfRVopazT{Z%TU$xyU*_-~*1&{%Mb-{N3JPBb?_s z+$or?KMTlS=SlRDCj;d+jvXxm^$KGZ{uCi7=_bw?Kdja zWV|FeF{y1f(McQmk!`&yQdlxGRnvN3AX69eNnR*;b}lSj_?5>uq3XA5YWP;kE5VCz zWDIEAdX6h_27f`nG1ZZ5iHVVu-pKFs!eCKnKlZ$Fx_6Lr(v00`q;+X>mH-&|{F#9k z>~6e*x?eB3en|XP8Z8;wr|Va23ofJT#jn}#eroqNju5L3RqHg3z~j}xmNcK>`@Xfj zv23y!yb%B~ka6%Fp>iSi@KldNxMihNbyLz&3=%e_zXo>2dbOr0<%@@4e_CWUC0|^P zNE4!$9XXC#loh2-CQ+$`_lfdP()cI(J)lnY%g-%K_;)wja^wg!QT;Qp8CR+nUH;7A z@UVX<>BikzCh!@DgEp(bYy?m~p;MDpMgs!(6t+6fjj>2M$w<^(nVp;f> zjvF>59ucW%UJHhwyh+jhXYI7g^V}*m4r;1w#fQ4J%n@(G2;;g_WJ&#&Ju%I{;!b-r zk(?g-V3YQ@ZfGwX8LUZORuwnj-1KhfEAx}z`GpFLaH+6_J@^ZeVD2zHi;R6t&k_0y zDC+|j1JpSxbhuB7o|)IRxA`7CjxXK{(j|!s7i5;5x`n7qz+&~@do1%X1djWpW4`MeV zk9}rN&ME?Grd{r<2%KW;021J3H)%=N$r@)`cK<6YBpM+2uJZ|j%Ilm04=ctvsWwID zDydhA=_Iccyn<|;zOc^T`4$lQCBRyV=GRm3rmBel3Wv1s={ZL&ve2n}TS-_S@a<(t~071Lx7<=Nij zr%IMSA95c>zY%p0`(+!giTeGA1*o>$DD)ILZ|Xp^Zhf`s0={g{A0h;Pk5_1iEWR1iQDS-8mQA;)zLenPSe3lrR8)}}XuLGe-zD1Z@s0>9 z(ODL}*(i-B6%KE#pu|x;ab=4(jKi_YAY`i3rs z)t7JS2g`RlUM+?;i|!+dn3>e4SdVj6xuOsCDc}rO2fn3uix73{*Bkxxn6{eBg3Gvy zm9h2rp_81QWhLeg?3gOY)zTe(y$1a|3KImIL4)Py$g+mlW0oG-@B-kH{CCqD4-d1K zjD&=V@IzAQeoX3|<7|1EoUtrWhg8#w=Mryl@9Gpu8Erz>( zHZoJQJ)!kNUK2gtl42X8Fif9b+rqv*5`TU823P#8FVVQ$=~@&Oy^ooDy)`#6XL4(# zLZa7Al-_3C74LF3wO84`N(oU)l0@S@s3j42?mdV1okMsx00Tjtee8d@z-#ll{5re> z0bVl@SFEg5tga82kN5x#oUv}Haku2k=bW_hqtehrV$I_94md|n)6$y7g(B^$r_YV+ z?LSv|58G3|Gw?ejL*ZEMg}*I98cz@5pU~1(q7wWYv8t654_aO>8mhzD&x7;Pb^_JS zG@?CT^vYioZ_tGTVrldD30|n2A=Bjb`%+U&M_OlBMcqy$E-T2PT(cL=QR7csA<#G4 z{;uZM$jDi*^Bz{18>N{Nw`2X1VXJrjNoL{F_8jPxHB!VGw6%s1G+L*z*KX84oPWn^ z>Z-xC6U%ESfzI1ua`R?x>1a3)49NWU!!Cdlje~-<+?ri|lDl~w&bCnby$^MVuRuS4 zzoBCV1jg10My)(Jdp8vVyNU^)qCq|P4a`I8VYF>;5TE~zYNJGx3hQE2aA;vQhoJhh#AjW0$IgaB$Y@2lzhYt43^F;NxteJMpW%+yRwjV`a zPaUPS)5)AWa+EeZeNRIAcPhgt+DTsaz{I7$B(!b_@zfhD6OA8JB(t??$hy*xE-CEZ z=$J`mTSa}i$ERR223!Im^d_uZuSJ2Lkg&n5za0Q+3QEHUS2aI$!uzpOJ$2Ln*8M}5 z4>|3GBMgVp(^11Ar<+)O+%}({z1hw(ec(7W>2R;8T6LW{{<<$mP)={;ZVKBo#P-8g zS~r>a=a!Wz()-n^h%bt^(Lfm2pyjMzZ=V+{rtea!mxNa(&{V_hukx3a#(;iRmRvp^ zb}^qaNCH+yo;H9({0|m*RUw>>|6xw9%m0y~gY+C!yq839cO3!UZqtqjOeyJy>o1{J zo>pShSWnKW^SDl*o{2|AST9@+GD+^JpT&z&qD~kW1v#d)Nm?}}wkkX7Hn~RFus6?# zFOD6_=SG>;r0Y?13_@^5d@e9D4t>7A91lYC4?;0OrQ!u-skP^|Wv3O)^)?x6u_@l< zWek;)oxBhKX{m6oB(H<=fP|tW?bWhr*T0)eqc_@g3{7EV-pz|DfuA&KMpWlwnvhN> z`S{sppG?4HGNp;%3;=vW$NiS?m*X}kFV{7g3jO)#zh5vfqfS5;ojZ9#ibxQ+ipN9B zWJv1ulLTCx2x?)fpQVL8wGk7pv^{A`Qa8Xjo7@}+#b^!wCe3h=TNZw~bFvw`fn_7x zFd4*LVudTcmG_|()vp3{#moemo;auiQRi5J1}(%e5|8TTqvG4HVIam^Q{89>n3BWg z2IIq$ZKUhF&wK_#YEP9wk_tCQ;ow(SLillCn*?T|3=WQRSr}Qd0ZCkHHbIDn*1C2``MeN|d3&37ct)RBx>|<%11td&2+gK3a#acXI;oLTjz1-K=DJ#3>LMo0Y z7J=T->2AgZ!+-=JfL8a1jC`l%eSsf$nUT=je6wok0nF>h{OQ{xWOI}Wtdg-OH5gvD z1(R$P^#_@M(8L;dr0h<8k##;&qW^(m7)v_`AP>S4m=j>-2wgITX^y)A z_Xe~>I?m_e;L}#>^ZM+LUu?$S&7(`g&ZE&HmD44-bfBwCKir!pFGDy*0*r;A7aLwH zpQq?ZBIb^JEXF5}4G$~r=rpFpoINSWoqVA2RdGexAv@=ai!Dw1sH3S8=6vIh`MCf} zY>J8c{95UJrqN``c7}diz>se~>o-1^ddM8w*c;pE77^W>2Spe8#$wKOJ*k1DytD&)UtvL<1=Yp`zG_k8H3!-Q1I zI3vyp3vhOG`^=D1s$WV0j`Y~N&F};7P)kXAj-L)L9rxk|*Ry9oT$Mu@VW41R z(4sWh8vOIKl~b74!{Ov;ZR{by$3IlMsovSU`JF^MK;FDn|D$U0ixcWhlweu|;H3%O zdBEAeQ0I;i^vT?m0H}2h%7gU{7w0k8YbToZs*(Qp&xNMx~uYvl~iDniH4g zhmuM`IHVyV_>R5Sjy@6!!)Mo(iTHm0rt71_38|7bWMqX7^11t2JYO`YGrqp}S2G-cfkNBG>vqGgXblH0EK0WB6^ zgY9|WKd^bo$05!1n9DBubp=MeJpiBcbc9-lqQwsK6)N6S52^dh1kad@Lt zW`sJ@O$a<@4-c^N>yG=OGOR5rzc?&IBS024*f$oWphbhltWq1MyKqazdQ7GHg$km` zO;(xaPdJ2ukRERo2?Oh$Auz*j-u)iZjraY@?)$6k>Wxw*QVcxV{S6fwgn)WN+BTbx zBxi1F{Y9V}k)NDc){n(+oxMo(oQ;`8Ia&GYY;URWqYP5`B2ifNrb@y-w<8lKjmB*F zs~@CY?Vt;UIaoRAEn9F5`{~fp;TW>r8a;k~1|%Em+<0$OBpVON#X=#~B-hJo*8&Wm zg5*yeQ(YIOX%x+)m6<3D7_~H0uSNFL@ODj3wwoWPH&08)YgKg}ZLUkzg(&oAPJiy# z8&4m2{OgH?IK4I9&FG~yPtg{y^vgAGnvW9$TG~KziF_WtIO5?+k%y}oszP%6K~@7ZKd0^ z$noWl_TLQHy2E{-C+b>G6sCo#crWKJDTOS#7}Y%Ns)Gxa3c_J2;5nMMTTGHUN@GKd zOHXfSO-1dKz#}59-YYtz|^e=|Cy;PO2V+JYV5Tk`ur`C*wQ`1{Uc@l z?;Vw_S4MLb+4x;NY!3nfD7OOk7aZng@K=u=&n9NU?$2yBC9FuZ3G-V07ifE7C7Wl$ zMy=48C_;U1>=&^fzEx|y&CliNnv0px>biMBn z7kfxFZH>F6M}u8D>I*5-z7295fm~Pkju}2>gA>bfW?T@>?{Pd7qCGPw5AypFHDI2_ zc@|`6T0qVqt{buXw5I~}avM^@8MLFwlAU`^ng8TdG}J%62@l>=9>=4f&kg>`Ot2Jw z*$q)lF2JBYDIes0^>Tyk*(fgdnww^#qzrD?J3jO2yQ%Nru7Gf}FY$K@;EjqHVaccD zLibdVsKqPcV2Avg@MJTxS_n(w(&6ux+}w5(0y@#hLOjpW?67s*exvQLMX@KHy+@yKseUo(*(9#3{y^T?5mlin7Dxd2hMsKG~$YF0FBStS7O)C#%c~BV%my zjs8n)ie*kpXE%@ZUdP?=8f;t`sX;*4a|ZT{j6{kvE; zfA=rNDMS9q;)@sIzkmg|*;~Y)BbB~d=fTvjH1jilh5NjT!c?t}LT}C(nwUM?sxJinhigQWnPO*p6UVl*W;>llWLFhBL7~jQu z=bt~$RX_Ju83))sHu{t@E9%w5e~MAq=@EebZw{F2JQfPO0}UZG4H0(ZdyC^Axz``` zr9<_(rRLGUx7@EDwEs^J$^1@Q-`-6^Ex!I!%Rjd@f>+$vrR2P&?iT?28o!vCfc6~0H>}4S_j-|abpR>_sEJ-^1R&GD^J#1wHktrYdHWcbi@dp)?=R-8-aUq0Wg+x!JhN=OoJbVpdHESE?Hnc>k z@G+_LVCV@|b$F?Kj=Y#^u0Xo!syI6;Sh6Sf1L`*HEKcAK68L!7gN7={dyW^?KxGiU z+6$U)0qZ;8yT0WNg&T`&TTZAxALd|k!7D-oOaKt%chf2p(V!H-ReYFZRIeLwdad{t=pR3$8E5$Z?Pfx`yM_vOC zepcJdXtu;6019dY1h5dYijeK(khoPYr9SjpFV>v2UAewFR-j$JY-?6ooP!xmrrfn0 zg7Ri>d2k`3TZ@>V+YwOU2jQ?%w;zvz12e=ptC zHHF+@Ahd0X=dXl|i*?T;(`r@}O7EckMf{5TS~QY1HO;9-_BQPfnXc)%d`9vE>QhHX z8#_)7MJK<~VxIvE9zGZBL8Ez{M_M}mdV)pY*Y@69ipW$0WTFJNg2jH9L)X9hj|=dc zS3vqB0FVvsxhmsbd!B0k%Ls*hP%kq-_yb1%ws%u|rp#O>T!3#N@0*J?^?f4Zd+e`! zPu>UmtD;=OH7aQ}H6-f6m@6#npB_K{O{ zWV~3_oSC(j81imLoNPQ+p};#Thv>E++KPPUT}mOd5wNVPq|e(=lOE;0DY}Kt^_X|} zCOB3e#sF8QBz-|i1$!!aC)%AXl<1))g+SN)8WPUWcZy;N%i&(kli_Ga(TmG;|c6-zfUY@nN z`*=CeN`A8lJ1nQ&eqE-4(tE|*Nz$`E(=U^*L6B?Sm63_NX6OxA03ZWS$(&)}$O^3I z(AF0DmgvYpk`i_M<9diUkGn4jXM&m zaL}JAJ%aKq&=kgQ-+P}N*F%H<{Qdp%BZ(@E^ZC;X+AYgEv&+2LU`QM;?&-F3BfJU9lG@g>$<0)7I%PX_(SUrZ& zu6}zXf-gVf?jQSTCyqD|74=f4LAM3DK3l-qA$$zwt-p7f)1S#=dQkbfN{GLn4|&eC zW{qoCY0Y1h9RIPUg_DkRJi7J9aXy@&tAnukKIahkl?d1oJK-A-4bLU@=DkllW$t6a zT&iuuo@fd5DtJSW_!%_>O~>1QEgbF?qmYlNtl<5~eyvP?4nR8v-};kEzl1t2w>pXR z6urfPSx-7W`QUk>T%y!1%I5Zrb~WAg$YUXz{Ne&yi$^WDef{8)j#$J6-y*1r;Y&vCnWYHX-{^gmzd%EpontgG&nVpy`_uR# z0*u7GRP7*dNEJR(QLQm8f2XZ@lG}$YwSnrI{P4Bu$$P3w4c#$akzk55aw)-RMPY`D z0W;p~dTb5GeqI4G5H={mRN%siiO%Dba!pdsXH7&PeM!N&_munDx=d~;9F6kvUb~>K zGdtzhq{H5(4dOvi@gy>o_W{oIjr~A@1Zi!4phGVGyd(l5ZDH|rW-@#=rXX=AB&rhRjGbb4p`HmO6Tn<&JBD?;(f!l`hBg$L5W2`F<0oYVZifRT)@w z9tLo{Ji-#V2{6CsX~LU(^QYVl*RQ5qc`f3Rk3Bk8AE6#?Ct~6bxdAP|Pi~*HkP%5P z&9!{B?h6RSgu}Ad_9ZN&^%HS0YlqdbX?iJC3^g?JQF^JwTqoCL#n=fsXR#I^LX*if zyaY9Axq=4x9RE9h)L6|?Vmb*Fhl8YSWq-k!AiWy3`?>sHsmv!t9!NGHR+%o3s>EUr z=bB97+&P(+o1h5iO#0-d6Uweq29fb9fG~#h38;;Us0uCrDuw&U2Oiu=n=}ux=?f)3 zm0vS*kjzTQ{Jky1)E*zPYAn%&y-_v>)em+Vf^INB&HBQVVy9I%G5H{+jSil|d$ZbM z69vEl2=oK=#&Ey`NheT1EiBF=(3Q2l+l|0}7_awkD>;-O3hI?)`Ha7aRk^-x%$mBH zGQkSU0f0EiEqbkA9TsZXmo1xwx%72_zmh}*L~fzid^&c`J<-PSfh_#OPj$yPz3_jw zUQ9>Gqag@CP-WzQNjV5-Py>Ebne$pEnN52w>|&8rqrvT_J~Z>|O_R0wRgpV2yTx5o z<7{e&KN<($u_B0H`c<$Rn}<<&m?v@_^)c&xnXk%bHc9lvdDi%^<&$yqyqMjy$EIH+ z=Q0)ZT%a?JJd8|a7>Tc5DF5E&WRscT?lI>gZ$EHzDYlz!9H&c@X^mFcPvoox>N5a} zkODY}-o*!C>pbaGO9Q05P^*q*Vvj$r;DW|f)C6U1RD=2tz1Udy^ z2mbjNm=Kp)>KH3;y=?<;&kckR7o=a&bp+Q_Axc9a*v7LdJbnkRpN`6UP2sMiQwSo{7%w%l?e7h$#VK)0Qse1xr;6oO3y|$4F}&sN zMu^PLhcY-k&eNW%oC5jI>2d?dX1#^fICE9K{SE+t?xwQ+A@92j7|OQrgKCgdT)DW& z>n*y!Q}bpipH6tQHXZ|obj@PL3NWthMOWY`~ zii0?8LSE3g^(;DF_#Iad-neM&#i$l#k(gfD)11>}GXQ8;)hx2A>TLp2w*2A(#%Vea z0~1-m<8nh&@)LfNuntVY$sVB&Yhz7{E#w|7>&s4kc)v}x69nZ0p-fxjViQ_cm9EF3 z4S1GQSMGjkOCEs8?GOFP(RWL;?qOu`%BXuY)&4@^(!1G>h!(&bAS8D{z*b9Hc7P~gT$%)zvy#D_wFHZpa-pyK-FeCW zynqd3`a_f*f7ydpq=uEZ*ET?noa0745w+Qde{jmwe>EBZ#@b=NvM-?CqXF0RQo!a*fSdSo8G2k6uRma&Vx)X~F@t z_~Ihv8cY!!>r()7#B>hWv6KUVL15NNbfv$z>85L>>^;<-6JkPvQMB+D^F;!sa`+x& zEE6&>fw)kjIF~WUB-S2{wFJGnj%OjAV`kF_OOwxmyLX=l&oV!M(pKV7qb$~U^bC%Y zw`80rdVSiDUX)zSNc!TG6>4WQ*CXleb006r7_YxlvqC+5sWQ)XZmfKyM;*fJI!?Cl zN}H8LSMoJiczou1CT1O#YDU_rn%8OKM}V*S;RvN(emHIppahowh))Y(;v@_~o6K?N z3pQy*s_3<`D=i>e`Y}#t%xL{d9!U?L1isiaQkNjuvCW^AZ~lB;X6#s0zrC0dZkG+5 zHxXL#$HbA-_`p0UDxR4?`Z0?kpxFRoQ_DL zu|z(Xjb6R$gj4#c2%6t-AMz&D8nx=R7($B_>>V9`XTj?_F0Z=-}IL^_BwLi9!Of|qfrRscNg zQ-HSlW*Kb2h(ecl*voPe)h4ZZSriA5MN0_Tr4GM~PU@^p98-LZaMCZanKhpJn1a`X zE(0z!isV#^Dk-V9v$1bHzn#8%!Y)u#Ww|~lPqp2Qk>0HO0XK(=IW1|N%G=5xhy~+a z66s~ISPauO5eHPj=`=VW$EtPiTm9e;Mycf^h!O)-U^0AXxeCOfA7y851Mf3r<7cS; zUprWs!)exey`yvAkK`>!LtPfR2@s@mdc5QeV&l$|&Kp!;5YqsBx*)@e;GwhWY6d8% zdB@>P<{oiJ$f+>u92FR*$D*kQUY5!JSf#ci=1cYBal4-=F9Ie1Mtyqiprxjfub>=f zizT%Em9z@$qa8XqgVHX_wC^wVU9!rDA~m_0gD-CLZU^u;$s|$s->2_y(%88-{}vWw zn?^Yn;7lBVtwE>mE>3)LtQPa)XiZVSwgJdia1#_@%L#O(4;DgZirNz#mptQTU^7Q$ zP(N7IQIu@T-Trj_h+?tMj)B*ks(|!z9Cq{6AB|NUo0~n{$clc{CnTegbYB-v{?RO| zl7nZ;GjqREquiH=ZE=bA`f@)8@`E(kdT)K^*_q)NXXNNsg(j2FOF@J0*%+^rZ~Msj zRC^^Y8-GO#FI-MP-0)>ENCZx>IE76kdm=uOdWcex@w<*@(UmKn+u7h^B6u?J*<6G9 zd>PrmZ)|sKoX;w^8lIDSzw)=0HZn)bWD^9paMp3TjRF0?Tq@y*NK{jZxqquK!4~eP z>zS`(GedcFx2(R!M%ybt>sZ>==?}iXjAO5iRFEv<4Ek7^xTMJ;YkGxU<+gom%fEoC z_+ETFe=3>ptA(|i!Fv0b{f~X<=Vs~{x<|*o+p0fPj>lIrTZWY#!i$VPXwy(t{gTf5 zNJz*A!Rmk$QKOM=-KrELdPX*Ue6Egt;3!&vzk88~O}-5%Rj>0L8B5o1M}g~<8EACd zj^nPS#sV)eqSIO8)Q^Bx5=bObynG2qx#Npw7PgN6eT&^_-Qf(Z(l-OyDYmT_4R~74;kP<;l(W{y)avc5RiPY>y!}E^&c+oHz7Zsa+er=?-2O>sMpZH7KLc{x za)mx$?JUra13us{&o<<*c*Ia9W(6&%xiYYA9~#7RuytY|2Do^Zv12(2^j%OF#IIg$ zCM6$d8+yc*!Y$(Kc$mVS*V@ye9u_T%t!$Mjef z>AkfkiOWsp6VgWBcE;~YET-A|rsjpd9QII4Er_;>6KZ&sNrH!v{(ihplbyWR9^jU; zQ>i}wWo%k%1f#sIxCnN(fZ?B%0B(YJYo*Qt_~4rFRE*jlJ|f^&=^GPGMAi3sutp!h zVLz|hQA?&mE*jT4V?|I!+-k0OpPpJIb6=t_qxj#K!cV8Bj!%d9{*XXAqMCWQCYN}h z;SB29C={nGt^;won z>z&$si5WYey2aCCo2tojX=03rVEeejp|ldX>ac#n&r?w>7&K(XruK59&;64ss<_MW zCMnE*oogBAvjsq9sti{w0(J)SQY%1#jAAvZR=g!^biuqFkr{tJ?)aaQoO+p_Sca-l z7H7Vr!&lx!G;9vx3rYAJDOX;w$t1@U+*%h2dDEtvW3+Pv5 z2ivLA;z2BZIM=!N%`s0T-gLDq;Fv-E<-%Nnsxwzbt%N=bG#s1B6&~x;F#GMNbdjTK z@O7CQ&`9+8=fgai^j7WyEA9d17(OSBodAh;;mOwn8(+Q5jR|J`r}ko^D(ln*yb=W(K}spa+j@|W;-6A9RJs*nB5`w!vtK2+&|DFumI zWsx{Ob*Er?{Z{uEfLw(x-YUZ<8dS7S4%dSD+I&NS$_LyV#!auJZy6rpM|vKJR`7R0 zrg}35??uN<${LnME)nl5%8XwfzUzZtN+FaLek|FvcCXfvRjS+au)&!$_3&Q2Rs4)f zE=j!fc(uXG-mZFoT?J*}3&Q2e9N2?UB>=re9j!kKqURX<5=zAn*HKMfd9yhzUgLcs zLt{L@o_*c%NV77(LBCOl(Yz>G6ZKsJDU}Dnz4FU%72Xyl6X*<{ed~-hax59~>A%8^ z#&}-Rot{N|t`-lSbCMe_ZLX}G5X^P|k+QwLz3TnVjawoUt5oCNuMDG2e`yiF($DH* zx0c%Rx0Y=4cBASVN7Lm?@5s6fP>g)@>{y(n(?d;Kx1WO2ohnn|dAtG)w9c{g1gv|R z*!~URUk^ebCi4nQ9>y6#f$5z}v0qMUK$GIJe>Zy2uA;qQ9k*PiLW&3;u+M*czY4#z zTCrTXlFEr|^$qa1fNwwfugHTGqQXfS;`T9}aMk1-0;Q zKQh3^0pMv5Vo*lXbRp)O);eBUGM{s!?8g%>2~m7!Y#(ed(;pr!1L7rB6@CCv^KHX@ z;70{vxaY8bKnn)eh)ufHF zZh;q_3WWn2#XFUIyspvGZ(rQVoAHD{lho&> z-E9ap-TL-gq!z$QK(@za{F9CgcUzSf!jp(o1x5A;QaH>jk+@9YA;UR#gUNd(6n@%p zzbis10w1g+)*&)sS$+W@?1h#-z?J&G$f=iSnMBWwI>@A209OZ^3&hcv zCGK!{){m$kX0pk|l~;++;shGcwT{iT)|5RnbF6+IfQFMQnq>!HqO`i*=2p>H{4Gu$5IpHUHlUl)=DLox z=p@OULw9)Byh^o+WR8OaO^o`Z?J6C(c>He`k@j=5xZx?;BD3hw)jb9rn5mknvW)pv z6|0lxUckT*GV=&qdS=~Yo9AR?iDlPEG*MrNSzA%OfJfzCt0;-#rd?FKv;en z#=4{de}?+rxT??2xqi2|!Fg$1eUf8-JIvGuPec!oFN!Kc>bFIU(7xD<=8*HXg}2q~ zR1p4pE^0KK$gu4e$F*<3AOz+gPNXjauqWsc_#cyk#}vRX0LBBq+5rmS351XAi+;;# z$KZ~)59>%HR@Lg>m7`t65yDJa4~@>sO9Hq_Qwc2Er2-_YhP;qQO@{n_-p?BZ;wM~( z<^O$M)7zr;Ekypk%laatd11&XiEq({#!4w7?E6qcsZBlg>8^q{O{Tg5747F_u9SJJ zrIz?>b=b6#_NV}5CMf1vAZ!VzpL^GRE%z44 zgD4jEz?^~K1S5ax@I53%hh6s9FhJgll1`Qb6e~}Gv$fUtgIwSNaRu@&&&8UOe1$8e ztWPx`Iy23$QNg(5l0{ZGm05*svCk%{wFHw8%>;oG1_3402S^WnO}O;0nz{EgG6S%6 z$8YlauP_ecG1Ax&ILA%iux`Kys>VSZuhySGdcbp_E@LBHa%yeS+YwvvM1po4v@){j{%vi&&(xh(-gF{ z3;1ux*J97rT|JHGxsIP&aodnd}< zT?3u_0iemQfJ0ii5Y#Gy90RQI;>}$MP}aFxraii(>-07TmR=6c3I^s)oY>G}*a}M| z#uONjm`qdy;99?o1qle3NjF>wkdKG?7&Ww_wUc_Q< zLl}I81&dMOZV$&evVn)Fp@bC{vaju2Dh22 z9_Z0hpl}Aljj=&jK&W$A+zc#w-$L6o@JMmAt2Xo_ue@#452yIL6pE@&@fSIbcVoQS zLgyy5_^SYs4s>Z?cV3v{u>(=t}im zUkT6fzZ+U@?O))gTb}{ul!By>P@%b8!}iblBmy5sVvJL?stqkl-*!nder0C3a{y}) zTJ8M9&|pUVEHTA}w&0}+Kv%((*d|@mtlC%sum}T)_Q68G2G!XE$PK`@R+C6!5&%;G zS})5z(*|&00k@Mr9{$r=MQ?C<$r~|0X}ky9$@I>kKPrE?AhoA3uZlg?pQC?JCL~LE z5NW(KeouOl+I?gCe#RJ=B$$3Tq~*?3F?$qyfjlli7DQ?|&Gc?J8=|9Z97V1}Sb&>2`olT#z7RfoY(4Y5jQ}@idA6Rd%8s;ku#Ls#8tEybP^bB=D+P0daUADXBbm zEe}t1wDrD}ZHZU&o?5KT5>oD7OMxwl4Nm9nDhs5?IY#Aey|+i16DUoh$s?~`vWtR5 z(A}-)=3i;pMx>oP%I71PMtIeWolU)h=dOJ^S+NTINOO-2e4ckBl1juZGsPu4P^}(Z znC*8qX8~UO78{3z+wBa|m_#5l^zfIx+k+*>>+>$(Ub%B7O7sXod^kv&(6=1UL zl#yE(xPIa*H6m5BP9<`OOfe*}Ddt1eFP>kTA<9#sL!IOG(Wvil0bUj5>%BNC6oZlB z*SKY1Xy#11R4M^|MgA~0cKI*?11j;4}?@J(4E0f`d(KFIv$LMMa*}z44$Ud z*&v*GS>QmuQg8Ken&3qTCUJ88asgKK{f97$apCnq%)(Ht^C(griPY;IK6io-Yw?pt zFenOb?OZ35VY+8NJ;&;v+ITNhlpVz_zKCB2@wNYz%$;{gWtO;Nk9pPs!DLAbCc=OK zOcP0q;VmvdTapD_?v4!YIx!>VaO%FX)$rb1tRVdZHCE*ng`VcR#%kOVhc)c<$KYba zl1Zcmv4iifDS2J|Qene7Fj5hhYUd8CI7 z#P%~-g_9u|F>g5EC`hy{T@C()HH_8EepDAkY@Z+!Kmc?<_^g&`u0W77iX&6hbkB&v zW1j#?U*xc5nsnj>Y8~uQ<({M)0eWN66+qiP>?Gcr;o)=~Mk18sNi5?asHpCK!M;Wy zwKwRhX5qpLX58K?z}uVm3=?Oui>!$%WqaWA%T_fsG460eOQ>y_F;NckESx)pZM~n)M>yd`cD8`IjbEL9tww)us-f zsn1wal>`UT!Tqmy&cE!|1$0*;shU@7j<@&bfr#4tCQwr#f*+@fU@-ej{Tyyii0a@I z^`z~5B6`a55$&qEmh_#A_(5xi_9JY`{g1g+227shY{1O;vS&}_)K*jtO<~~rdl7iB z3ZUTuswRZ;fgXkWW@GmMF0f2g|J!KBZm_f`;NQTZ zH3|Q>3=XScSzJN{qR(g3)B1d+$e)7RAuH19yMkcm zmWW_K$M=m6vR^qq7BS*9rW2vRvLMIWozV2ye!5x2>wZl+tawuwG`gG-35EeAS$Q~? z2=nu3&ng_1SJn%HFOX&DKAourgkvX{i;B`0c2oUGgazooaipmfbix|wi zIJuMJQ@N2n^Ttq^ADIk(;@cm?uDT;~juDN6AGWqgELPv8czpc`sZ)&`Pa7zRT9oGh z45X?tA21T2$A&Y42Bn@PxAl+#c*xYe7gp7hCxdDFsQ{k0?I-WMJ> zvkn@>|W!dZhHB*J#KrUbT3nDVlR8hl_1JR%gQ^wGr@y|T zhoZWikgv{tYYdayY4X<#`!}YgBaZ4g**Mr3q`=USVEaKZ?hSH5b&ijw5KD${wY3Pt zkMA#s*OmCO@)$}y4O(0*eC(t(5=hNx`mxqYpL;&~DNDNB^R$UL`VU61>`SEYLhbYV z@@>9qDx4s^{r1cQbSKYZ43{(gcWL4jMO^hO0?aynsj@WaCr$JAB7G$Ynk4r>Fp!f8 zXYTe|MP9F0t@evLP~T}}HRk5O(<7DU`cG1=_t){|>w3WzRP3&9FFl7aT_jsmuh0!< zB&65$TG@2e;1a}ow`Tb-H+_S{a&6I3q{`&eolT;AVJ$2duknOF|5js36U5X=#K(4^ zn8kyI8pY*teC6}ANEOyI6q8s$PyV-x8gker6XfitNx4~lJEY!8(?1%ufegN8X1~Ka z1@}8;5ZX6v()rqRBZDm^g$AB9t6ND*acQn3;iyg!LMvyP0hWznHS(i(3#>ewosG2~ zwi+#6S5Z!Q0gm01K<}!THeqY{0q)W`;&^e~r@Si`nt8RLkAy^408z2ZRQQuWvjm-v zV@g}>cIQ>uR;Ik|>-0b4=H|LNSEn6|Zw$eHk&r|ZMa!k}xJABVzx4QICx>$qKh(Z! z?e7Iq31x$FR*+$^K)lyk4t^JD0B#06(&Qx)(k;q_#_`kS^2mJx&y*bS6-i2zC*XjzmR58jobp@0J1XDVIRMI_ZQwT;=27 z3gYB<6REZdZ8nP86CrHp=}&x1U3!bwCGt!6AU0IXRH}^ zUNd}rN2yee4|Xya*(nI6)>p*QCAH%gx0vRj9(#Mnd}gf!6<6Zp5s3J)2PT;~zYkkcF|vK@UoQ z9MMsUxlbk83H#1(2EQh9ycaqU;~M#8TPnmZX*YBF^5x2z8&sJFWnxo|p+spe*?HU$DoZ!-wgh><*Ba6%K+2wo|U{zEZKOShZsZAAg-UhT6fh zDYbrUZl`QbwH1pY6Sk)&J(4`ls9bQ*x*m>#)iMZJM({Dn#eu8gUn3!Pg&NnVhgH8v zmKhnHi9fDlr%Q|=cP*xPS77uVxV8#IqmLG%Nth@FFc>S!{Nu^Ugt5gVa(XGiZ75&j zx1AG~q#lY2lAP$Y3_pE;;?G@_@>_!@S=>B!pwfQ|9f`w}0$%4zm_$xCq)cj*GNb2Kmp zjc0!q-M>{OT5#sPlg3uA9`p#D?L_@Zm$R$EtY2i%`zF*3swVe_(v`P$)=U80?Mv4G z`&rRCE7v$1qXvfnEfNxvS)a+6u;PCFy3GC!l_YVzsH_~g$DXQ^Dqp-a6f-JjzEn0? zHGC&`FA?__6DiNTr^u`{GN!Q0*s7F+gz7c3I(82`B&5&reN>cP=>q06hRv(WAZo?D z>z`XusG}s~j3W8^r4$93a0?RNDBWsU|5wLAo_FHUkUo!A^#zKYen1Dwa~L~mBcatL zR;jMrMP_y_U-PEF@~3Q)z>hVrIjqi711F>9t1J?CBsqeIYW0C#)$MxCXNLzPkz{PX zFS{c#L^L?kS8zId5quJge4|o{?vhu@hdkoF6ytW1gZwXyIyfM$B!?)A;m2+{qD$bK zg!3lbvB^}294yMe&o|u0Hf6+u>Fa-t22c%S*$UVP<2v%jXS~lKMtf%KMiu?WE6mcs zC;&~WT3nr;I#*qV4hbnNx=lnP&Nx+Oze~L3fi6qJ_}L<%>LUBSNIGN^O)j{A0(vs{ zdYe&&>)<%I4~rrl6CDd(Bf>?Ne$~II3iG`C#l@z2bwa1_dcO)eS=;u)TNF}cQ#714 zIlu6@Bt>mWmPqh2KMV_Pg`(d3ke+1T%j)~_D@GB~HASzNBJbV{vS`uLzGv%WNC^Su zu)0{g$u%_La9I8M;+=`8IuVY<|Fw7K-%x&W93LcPB$Aj2$&77e%QCWM$=Dhbng&JI zK_N_|C>nfaEsU~_bugA8OQJ%Ah8RnX(L&i`ELkEX->3hhpYA!&dG5LAdG7rz_n!Ct zyb>XMJ1g+SBAJiL8rfap0h5~#>&Z!FeHZlbSW4omwDj9{VOo$7%alH)&RnOQ(p)`d zH6jlJ^%a9p39Oqq6V-4P%Z~fey`W5*|HZco6!UT03r|pX;@~~eLk%BV;J(>kzW6Ed zMJCWRY2hj-!jQ^>ako)xZuJ%3Yo5T$%2HTKPu0?yc4Qe8m3lR!54eYIT(GlD!Yk^o ztMU%Z?TueuZG~F-DxdC8*^(>uT0bf_ee%Cb6QJuc(u)HFc5NArZ}U)_I#JdMA7^uPxm0Qw-IBt&0 zQSr&E@_1K$4jIw#Yhtgs*J%$gzS<|G3jo6`0(UHq>z0hE=@0v)WQGKf1G%YPt9zC~ zjn83+P2(lP5#X&h=|fj0v;u{4)rUz>d=}ki3dKJE92Th5UO^!w8f)^R6_MwRFh(_A zYZ~tYfw(rD6#g!o9!c9lX3qCmX>QhBw@bu(4CROQ%dTfsm43C9TjuS6JwgldG_VN* z+#pbg2EBIrwclcvjX$j@B$GBQ7|b*->j>-kGz`QDy^+G#x0jLJZ1&1X|rA5}2;TFsQ{$7mlY0vjn4eyWf zcd){iS3a{=|7^l9&$HR7uFWJ>`HF)Nma$8S=y|W1&W=&yFpR56 zuqbgcp(quvWQG~roF=c|#6R8hxd5CEFqA}>2t7e@d@{l(!kf1ngS9gC7}`1@BZZkJ z+n1!~a!ZBogg-Z7*oSxl^T~Dy0!;d-4FzyL^8E_7kF-;qX5Y@DskZTvHV+c)HNbmD zJEO&X{0_iv2?lkt?T+WY!0IIhZm(&-XeNb9j%at77T zzw^K6cN2r3&;xl*!erggF^w&7MvKc|)`dRl|HSZx zIb8p+f#3&OCf4B=stqJ9yA$WMgt9g0uCmUL*+~>-E2`O>kOoD#8i-83JZ5Nx4 z6FJ(oU9M;_=W?wb)=}*DJLB+6R_x`QYZ$?Kf56gioE^&O_H8*fXU;A57p;>oIV0WaAVCEePPCK9%v^y*D^{cdE5;gkKp7SL#D(di^r-+;gwNS7Rou++#16 z*vES{-f+*$^Jo7-uBeZ34xqMpKb9Z6X z@cMzgxb)#4hwe&xPeegedaH5WSZ02tl^-l+u`GxCETPKDL4Y?G%`H+riNkg0MN+_7f-c@DvH5`;=)Xg@Pw6q|&N@ z^jlm~%gnYueHIo_+Mpk%;km`q$o20C6ao1p6VAMPn`ih9=h8rD0&u1*vhW60y{d&> z>eTqIR?n=UR>XQVB*O0lhe0crC7nea*ot|?WKz{7JELc*tr-=Fsx)Gt_5#;}TvgeW z*16Ojtun6u-c|Q_iazMH`mRNVtygu?SL3B|{4ytK+FAUp>e(TMJvCQ1eM7S&!rISO z=WmN8k@J>H+*`UD z&kdTg_ki-+KoE>%Me&``^rH{GI>kblK5>I|NR^@fV+I)~H6kkNP$%`LO4-kZ@>~(* zkW^#G)j)SXSPo10Wz>$3#kP|RePgxQTW4kO4z4q^-Z7*?H~jjGsGo{7Udj4xE7sB# zjd(efSJUQVS!(}eTKFZK^dW%W1oAmVK}&qrxFD z`8+O~aA+oD!fC`qi^%=)LZ@J*)EWY3+`*8D#Yt-z%jedDJ6$|3wWJT3ga^exL@)Mp z8U?4{4XJstIW9()I{ZQ@S4bCuS>Sq?OSgot5HKea3ut|?diL?>d($Oi#HV>W#s~B$ zQ!G1VcsCew0A|uJE)iiY5&gmm7t;G&^&o6)F*Bz-32SGW1!I!H* zmf0_AgC&hN4X9N31a_TSgN?@vnr&`ae+bc>GsvIE4oq8yo|9y>5O4fOHF9QlXT{gg zrOMJ+=MMJe2!nit(}cz-sMSghE%_TGKPh^NU!~)%70p;xWX~C%=L>+QVGk&qzdGMQ zU6fgvaM&ca;ogf}?tYce0i$z*%8?B>u!T7m<*u%E_WAx<1e?k zGF9!~ZL2t8)?PxPJ-(TS16an!+U|%lKkp5F)yOGbwlFP1I1Zi20ZLN;u37bMY@fG) z(t-qFQKo2o$dR5~J|&|^p`yhdYlg?67v>jsnxhe|WaOqxzlAVX>ga6a1imL_c<3ti zi%kv(s8kga<)m0{u4Llp-3+UEQqa-PLe(Vp1u1=>#wcwuV(5F!txmfpN!faJNv5wZ zM_-n*))ZTHe`-ABF-K%qZC&pBa9!;^ldcCSb0wf^+)F1*2>Ap6+1tNkGS#+yJ~!}r zZ&EomxOY%4;26&3Be-C0_OeBQyHVMz412zIFa2w~#`oYUUUS^4w#Asd zTaNX(N2*knQ}?H`Y@b`2%eLN@xAzNk@bq!qXf=-DO56y^cj$FRdD}L)93&z`Q6G<6 z&4iWr*EJN>rCyY_z2{Y!5`}09h0HBa%~5|r^$d+aj*6)hHyo8HDQ{1NE@PgWYv_ID$=n=~iG_^gwRy|9yZovo)oicYW|b Ddg>Xb literal 0 HcmV?d00001 diff --git a/docs/maintaining.md b/docs/maintaining.md new file mode 100644 index 000000000..d058f2889 --- /dev/null +++ b/docs/maintaining.md @@ -0,0 +1,135 @@ +# Maintainer guide + +Maintainer-only runbook: releasing CodeCarbon, deploying the API and the dashboard, and +operating the production database. Contributors want the +[contributing guide](how-to/contributing.md) and the +[development guide](how-to/development.md) instead. + +## Release process + +- Merge all PRs. +- Open a terminal and make sure you are not in a venv with `deactivate`. +- Create a PR bumping the version with `uv run bumpver update --patch`. For a release candidate, use `uv run bumpver update --set-version 3.0.0_rc1`. +- Run `uv run python .github/pyproject_versions.py -c` to check version consistancy. +- No manual step is needed for the citation: `bumpver` also updates the `version:` line in `CITATION.cff`. Only `date-released:` may need a manual touch. +- Update the dependencies with `uv sync --upgrade` +- [Build the documentation](how-to/development.md#build-documentation) with `uv run --only-group doc task docs`. +- Push the changes. +- Merge the PR. +- Wait for the Github Action `ReleaseDrafter` to finish running on the merge commit. +- [Edit the Draft release](https://github.com/mlco2/codecarbon/releases/) on Github and give it a tag, `v1.0.0` for the version 1.0.0. Github will automatically create a Git tag for it. Complete help [here](https://docs.github.com/en/repositories/releasing-projects-on-github/managing-releases-in-a-repository). +- A [Github Action](https://github.com/mlco2/codecarbon/actions) _Upload Python Package_ will be run automaticaly to upload the package. + +### Test the release + +After the release on PyPi, please test it in a fresh environment: + +```sh +cd /tmp +rm -rf cc_rel_test +python -m venv cc_rel_test +source cc_rel_test/bin/activate +pip install codecarbon +# Check you have the last version +codecarbon --version +codecarbon monitor --offline --country-iso-code FRA +# Stop it with Ctrl+C if it works +# Then clean up +rm -rf cc_rel_test +``` + +And check if the doc looks good on [docs.codecarbon.io](https://docs.codecarbon.io/). + +### Test the build in Docker + +If you want to check the build is working, you could run: + +```bash +rm dist/* +uv build +docker run -it --rm -v $PWD:/data python:3.13 /bin/bash +pip install pytest pytest-mock requests-mock responses pandas +pip install --no-cache-dir /data/dist/codecarbon-*.whl -U --force-reinstall +cp /data/tests/test_package_integrity.py . +pytest test_package_integrity.py +``` + +## Restore database from a production Backup + +```sh +docker cp postgresql_*.dump postgres_codecarbon:/tmp +docker exec -it postgres_codecarbon bash +export BACKUP_USER=upwnpbdktjvnoks0foxq +export BACKUP_DB=bnrwiktgr4hzukt1xseg +psql -U $POSTGRES_USER -d $POSTGRES_DB -c "CREATE USER $BACKUP_USER WITH PASSWORD '$POSTGRES_PASSWORD';" +psql -U $POSTGRES_USER -d $POSTGRES_DB -c "ALTER USER $BACKUP_USER CREATEDB;" +createdb -U $BACKUP_USER $BACKUP_DB +psql -U $BACKUP_USER -d $POSTGRES_DB -c "CREATE DATABASE $BACKUP_DB;" +pg_restore -d $BACKUP_DB -U $BACKUP_USER --jobs=8 --clean --create /tmp/postgresql_*.dump +psql -U $BACKUP_USER -d $BACKUP_DB -c "GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA public TO \"$POSTGRES_USER\";" +psql -U $POSTGRES_USER -d $BACKUP_DB -c "ALTER DATABASE $POSTGRES_DB RENAME TO \"$POSTGRES_DB-backup\";" +psql -U $BACKUP_USER -d $POSTGRES_DB-backup -c "ALTER DATABASE $BACKUP_DB RENAME TO $POSTGRES_DB;" +``` + +### Clean the database + +To remove orphans (elements without run) from the database, run: + +```sql +CALL public.spcc_purgeduplicatedata(); +``` + +## Deployment + +### API + +The API is available to everyone from https://api.codecarbon.io, but if you want to deploy it for yourself, here are the instructions. + +To deploy the API we use [Clever Cloud](https://www.clever-cloud.com/), an IT Automation platform. They manage all the hard ops work while we focus on the Code Carbon value. + +Here is the Clever Cloud configuration if you want to reproduce it: + +```conf +APP_FOLDER="carbonserver" +CC_PIP_REQUIREMENTS_FILE="requirements.txt" +CC_POST_BUILD_HOOK="cd $APP_HOME/carbonserver && python3 -m alembic -c carbonserver/database/alembic.ini upgrade head" +CC_PYTHON_BACKEND="uvicorn" +CC_PYTHON_MODULE="main:app" +CC_PYTHON_VERSION="3.13" +DATABASE_URL="postgresql://secret_do_not_publish_this" +PORT="8080" +``` + +_CC stand here for Clever Cloud, not Code Carbon_ 😉 + +To deploy, + +```sh +git remote add deploy git+ssh://git@push-n2-par-clevercloud-customers.services.clever-cloud.com/app_.git +git push deploy master:master +``` + +Yeah, not so hard, is it? + +See [the doc](https://www.clever-cloud.com/doc/getting-started/quickstart/) for more informations. + +Please note that Clever Cloud host Code Carbon for free because they like our project. + +### Dashboard + +Same as for the API, for example to deploy the branch `fix-unit` to CleverCloud: + +```sh +git push clever-dashboard fix-unit:master +``` + +Config on CleverCloud: + +```sh +APP_FOLDER="dashboard" +CC_PIP_REQUIREMENTS_FILE="requirements-dashboard.txt" +CC_PYTHON_MODULE="carbon_board_API:server" +CC_PYTHON_VERSION="3.13" +CODECARBON_API_URL="https://api.codecarbon.io" +PORT="8000" +``` diff --git a/docs/reference/changelog.md b/docs/reference/changelog.md new file mode 100644 index 000000000..2dbceee3c --- /dev/null +++ b/docs/reference/changelog.md @@ -0,0 +1,103 @@ +# Changelog + +User-visible changes in CodeCarbon: new features, behaviour changes, deprecations, +removals, and anything that requires action on your side. For the full list of merged +pull requests, see the +[GitHub releases](https://github.com/mlco2/codecarbon/releases). + +Deprecated features and their replacements are listed on the +[deprecations page](deprecations.md). + +## Unreleased + +### Added + +- CPU power estimation now recognises the Apple M2, M3 and M4 chip families, so + measurements on recent Apple Silicon no longer fall back to the default TDP + ([#1256](https://github.com/mlco2/codecarbon/pull/1256)). + +### Changed + +- Updated the RAPL access instructions and the Ansible deployment role. + +## 3.3.0 — 2026-08-04 + +### Added + +- Windows support through the Energy Meter Interface (EMI), including multi-die + machines. Windows measurements are no longer estimation-only where EMI is available + ([#1263](https://github.com/mlco2/codecarbon/pull/1263)). + +### Changed + +- The API client now raises on HTTP errors instead of silently returning `None`. + If you call `ApiClient` directly, expect exceptions where you previously got `None` + and had to check for it ([#1277](https://github.com/mlco2/codecarbon/pull/1277)). + +### Fixed + +- Documented that `tracking_mode` affects RAM and CPU measurement, not only the + process scope ([#1163](https://github.com/mlco2/codecarbon/pull/1163)). + +## 3.2.9 — 2026-07-19 + +### Changed + +- Substantially faster startup and lower measurement overhead: the tracker defers + initialisation, caches hardware detection between runs, skips redundant GPU detail + queries while monitoring, and reads only the header row when validating an existing + CSV + ([#1251](https://github.com/mlco2/codecarbon/pull/1251), + [#1252](https://github.com/mlco2/codecarbon/pull/1252), + [#1254](https://github.com/mlco2/codecarbon/pull/1254), + [#1264](https://github.com/mlco2/codecarbon/pull/1264), + [#1235](https://github.com/mlco2/codecarbon/pull/1235)). +- When using the hosted API, the run is only created on the first emission upload + rather than at tracker start, so aborted runs no longer show up as empty runs + ([#1253](https://github.com/mlco2/codecarbon/pull/1253)). + +### Fixed + +- Dashboard: run sums now exclude filtered-out runs, the date picker month arrows are + aligned, and hovering the emissions chart no longer errors + ([#1229](https://github.com/mlco2/codecarbon/pull/1229), + [#1233](https://github.com/mlco2/codecarbon/pull/1233), + [#1230](https://github.com/mlco2/codecarbon/pull/1230)). + +## 3.2.8 — 2026-06-07 + +### Added + +- Optional, opt-in telemetry ([#1171](https://github.com/mlco2/codecarbon/pull/1171)). +- BoAmps output format, with documentation and an example + ([#1154](https://github.com/mlco2/codecarbon/pull/1154)). +- Documentation of what the hardware measurement actually covers + ([#1204](https://github.com/mlco2/codecarbon/pull/1204)). + +### Changed + +- The web dashboard front end is now a plain React application + ([#1076](https://github.com/mlco2/codecarbon/pull/1076)). + +### Deprecated + +- The `save_to_file`, `save_to_api`, `save_to_logger`, `save_to_prometheus` and + `save_to_logfire` parameters. Use `output_methods=[OutputMethod.CSV, ...]` instead. + They now emit a `DeprecationWarning`. + +### Fixed + +- `codecarbon monitor` in offline mode now uses `OfflineEmissionsTracker` + ([#1202](https://github.com/mlco2/codecarbon/pull/1202)). +- macOS: CPU detection through `psutil` ([#1211](https://github.com/mlco2/codecarbon/pull/1211)) + and importing `amdsmi` when `libamd_smi` is absent + ([#1201](https://github.com/mlco2/codecarbon/pull/1201)). +- Carbon intensity lookup for Nordic regions with the online tracker + ([#1224](https://github.com/mlco2/codecarbon/pull/1224)). +- The CLI now warns when authentication validation falls back to the API + ([#1209](https://github.com/mlco2/codecarbon/pull/1209)). + +--- + +Changes released before 3.2.8 are listed on the +[GitHub releases page](https://github.com/mlco2/codecarbon/releases). diff --git a/docs/reference/deprecations.md b/docs/reference/deprecations.md new file mode 100644 index 000000000..274d2a138 --- /dev/null +++ b/docs/reference/deprecations.md @@ -0,0 +1,71 @@ +# Deprecations and Migrations + +Everything in CodeCarbon that still works but is on its way out, when it was +deprecated, when it is scheduled to be removed, and what to use instead. +User-visible changes per release are on the [changelog](changelog.md). + +| Deprecated | Since | Removal | Replacement | +|---|---|---|---| +| `codecarbon[viz-legacy]` extra | 3.3.0 | 4.0.0 | `codecarbon[carbonboard]` | +| `save_to_file`, `save_to_api`, `save_to_logger`, `save_to_prometheus`, `save_to_logfire` parameters | 3.2.8 | Not scheduled | `output_methods=[...]` | +| `co2_signal_api_token` parameter and config key | 3.1.1 | Not scheduled | `electricitymaps_api_token` | + +## `viz-legacy` extra + +The old Dash-based dashboard extra is replaced by `carbonboard`. + +```bash +# before +pip install codecarbon[viz-legacy] + +# after +pip install codecarbon[carbonboard] +``` + +See [Visualize Emissions](../how-to/visualize.md) for how to run it. + +## `save_to_*` parameters + +Passing any `save_to_*` flag raises a `DeprecationWarning`. Pass the outputs you want +as a list instead. + +```python +# before +EmissionsTracker(save_to_file=True, save_to_logger=True) + +# after +from codecarbon.output_methods.base_output import OutputMethod + +EmissionsTracker(output_methods=[OutputMethod.CSV, OutputMethod.LOGGER]) +``` + +See [Output Formats](output.md) for the full list of output methods. + +## `co2_signal_api_token` + +The CO2 Signal API became part of Electricity Maps. The old parameter and config key +still work and are read as a fallback, but they log a warning. + +```python +# before +EmissionsTracker(co2_signal_api_token="...") + +# after +EmissionsTracker(electricitymaps_api_token="...") +``` + +```ini +# .codecarbon.config — before +[codecarbon] +co2_signal_api_token = ... + +# .codecarbon.config — after +[codecarbon] +electricitymaps_api_token = ... +``` + +## For maintainers + +This table is the checklist for the next major release: everything with a removal +version scheduled for it must be removed, and every "Not scheduled" row should get a +decision before the release is cut. diff --git a/overrides/main.html b/overrides/main.html new file mode 100644 index 000000000..1d3956df5 --- /dev/null +++ b/overrides/main.html @@ -0,0 +1,34 @@ +{% extends "base.html" %} + + +{% block extrahead %} + {% set title = config.site_name %} + {% if page.meta and page.meta.title %} + {% set title = page.meta.title ~ " - " ~ config.site_name %} + {% elif page.title and not page.is_homepage %} + {% set title = page.title | striptags ~ " - " ~ config.site_name %} + {% endif %} + {% set description = config.site_description %} + {% if page.meta and page.meta.description %} + {% set description = page.meta.description %} + {% endif %} + {% set image = config.site_url ~ "images/og-card.png" %} + + + + + + + + + + {% if page.canonical_url %} + + {% endif %} + + + + + +{% endblock %} diff --git a/tests/test_readme_links.py b/tests/test_readme_links.py new file mode 100644 index 000000000..0d2f5d691 --- /dev/null +++ b/tests/test_readme_links.py @@ -0,0 +1,23 @@ +"""Guard against README indexes referencing files that no longer exist.""" + +import re +from pathlib import Path + +import pytest + +REPO_ROOT = Path(__file__).parent.parent +# Markdown files whose relative links must all resolve. +CHECKED_FILES = [REPO_ROOT / "examples" / "README.md"] + +LINK_RE = re.compile(r"\[[^\]]*\]\(([^)]+)\)") + + +@pytest.mark.parametrize("md_file", CHECKED_FILES, ids=lambda p: str(p.name)) +def test_relative_links_exist(md_file): + missing = [ + target + for target in LINK_RE.findall(md_file.read_text()) + if not target.startswith(("http://", "https://", "mailto:", "#")) + and not (md_file.parent / target.split("#")[0]).exists() + ] + assert not missing, f"{md_file} links to missing paths: {missing}" From ac5b5fa08139c3567f1ecfef0c827c0e82c55ae7 Mon Sep 17 00:00:00 2001 From: David Berenstein Date: Wed, 12 Aug 2026 16:28:59 +0200 Subject: [PATCH 2/9] docs: drop the blog surface, the website already has one The changelog and deprecations reference pages stay; the release announcement post belongs on codecarbon.io, not in the docs site. Co-Authored-By: Claude Opus 5 (1M context) --- docs/blog/2026-08-04-codecarbon-3-3-0.md | 39 ------------------------ docs/blog/index.md | 10 ------ mkdocs.yml | 22 ++++++++++++- 3 files changed, 21 insertions(+), 50 deletions(-) delete mode 100644 docs/blog/2026-08-04-codecarbon-3-3-0.md delete mode 100644 docs/blog/index.md diff --git a/docs/blog/2026-08-04-codecarbon-3-3-0.md b/docs/blog/2026-08-04-codecarbon-3-3-0.md deleted file mode 100644 index 8bb3f4178..000000000 --- a/docs/blog/2026-08-04-codecarbon-3-3-0.md +++ /dev/null @@ -1,39 +0,0 @@ -# CodeCarbon 3.3.0: measuring energy on Windows - -*4 August 2026* - -CodeCarbon 3.3.0 is out. The headline change is that Windows machines can now be -measured rather than estimated. - -## Windows Energy Meter Interface - -On Linux we read CPU energy from RAPL, and on Apple Silicon from `powermetrics`. -Windows had neither, so CPU power there fell back to a constant-fraction estimate -based on the chip's TDP — usable, but never as good as reading a counter. - -3.3.0 adds support for the -[Windows Energy Meter Interface](https://learn.microsoft.com/en-us/windows-hardware/drivers/powermeter/energy-meter-interface) -(EMI), the OS-level API that exposes hardware energy meters. Where the platform -provides it, CodeCarbon now reads CPU power from EMI instead of estimating it, and it -handles machines that expose one meter per die. - -Two caveats worth knowing before you expect a change in your numbers: EMI reports CPU -power on Windows 11 running on bare metal, so virtual machines are still on the -estimated path, and not every platform exposes an EMI device at all. The -[troubleshooting guide](../how-to/troubleshooting.md) has a script that prints the -channels your machine exposes, and the -[methodology page](../explanation/methodology.md) describes how the measurement fits -into the rest of the pipeline. - -## If you use the API client directly - -`ApiClient` used to swallow HTTP errors and return `None`. It now raises. If you wrote -code against the old behaviour and checked for `None`, that check will no longer fire — -handle the exception instead. Nothing changes if you only use `EmissionsTracker`. - -## Everything else - -The full list of merged pull requests is on the -[3.3.0 release page](https://github.com/mlco2/codecarbon/releases/tag/3.3.0), and the -user-visible changes per release are collected in the -[changelog](../reference/changelog.md). diff --git a/docs/blog/index.md b/docs/blog/index.md deleted file mode 100644 index c4769b0ff..000000000 --- a/docs/blog/index.md +++ /dev/null @@ -1,10 +0,0 @@ -# Blog - -News, release announcements and deep-dives on measuring the carbon footprint of -computing. Want to write one? See the -[contribution guidelines](../how-to/contributing.md). - -## Posts - -- [CodeCarbon 3.3.0: measuring energy on Windows](2026-08-04-codecarbon-3-3-0.md) — - 4 August 2026 diff --git a/mkdocs.yml b/mkdocs.yml index 19c451331..03eee181a 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -9,6 +9,7 @@ edit_uri: edit/master/docs/ copyright: "© CodeCarbon" theme: + custom_dir: overrides favicon: images/favicon.ico logo: images/codecarbon-logo.svg palette: @@ -133,6 +134,18 @@ extra: - icon: fontawesome/brands/github link: https://github.com/mlco2/codecarbon name: CodeCarbon on GitHub + - icon: fontawesome/brands/discord + link: https://discord.gg/GS9js2XkJR + name: Join the CodeCarbon Discord + - icon: fontawesome/brands/python + link: https://pypi.org/project/codecarbon/ + name: CodeCarbon on PyPI + - icon: fontawesome/solid/globe + link: https://codecarbon.io + name: CodeCarbon website + - icon: fontawesome/solid/chart-line + link: https://dashboard.codecarbon.io + name: CodeCarbon dashboard nav: - Home: index.md @@ -147,8 +160,10 @@ nav: - HuggingFace Diffusers: how-to/diffusers.md - LLMs and Agents: how-to/agents.md - How-to Guides: + - Troubleshooting: how-to/troubleshooting.md - Examples: how-to/examples.md - Configure CodeCarbon: how-to/configuration.md + - Get accurate CPU measurements (Linux/RAPL): how-to/enable-rapl.md - Compare Model Efficiency: tutorials/comparing-model-efficiency.md - Dashboard & Visualization: - Use the Cloud API & Dashboard: how-to/cloud-api.md @@ -157,7 +172,6 @@ nav: - Log to External Systems: how-to/logging.md - Integrate with Comet: how-to/comet.md - Deployment: - - Improve Measurement Accuracy with RAPL: how-to/enable-rapl.md - Deploy as a Linux Service: how-to/linux-service.md - Deploy with Ansible: how-to/ansible.md - Run on SLURM (ROCm/PyTorch): how-to/slurm.md @@ -165,6 +179,8 @@ nav: - Contributing: - Code of Conduct: how-to/code-of-conduct.md - Contribution Guidelines: how-to/contributing.md + - Development Guide: how-to/development.md + - Maintainer Guide: maintaining.md - AI Policy: how-to/ai_policy.md - Working with Coding Agent: how-to/working_with_agent.md - Agent Instructions: how-to/agent-instructions.md @@ -172,7 +188,9 @@ nav: - Explanation: - Why CodeCarbon: explanation/why.md - When to Use CodeCarbon vs EcoLogits: explanation/when-to-use.md + - CodeCarbon vs Alternatives: explanation/alternatives.md - Methodology: explanation/methodology.md + - Accuracy and Validation: explanation/accuracy.md - RAPL Metrics: explanation/rapl.md - Power Estimation: explanation/power-estimation.md - Model Comparisons: explanation/model-comparisons.md @@ -181,5 +199,7 @@ nav: - API Reference: reference/api.md - Output Formats: reference/output.md - CLI Reference: reference/cli.md + - Changelog: reference/changelog.md + - Deprecations & Migrations: reference/deprecations.md - Track GenAI API Calls (EcoLogits) ↗: https://ecologits.ai/latest/?utm_source=codecarbon&utm_medium=docs - Join Our Discord 💬 ↗: https://discord.gg/GS9js2XkJR From 0924792b4d1c881475f84bc128c344a3ea842869 Mon Sep 17 00:00:00 2001 From: David Berenstein Date: Wed, 12 Aug 2026 16:34:18 +0200 Subject: [PATCH 3/9] docs: keep the changelog on GitHub instead of in the docs site Move the changelog and the deprecations table into the root CHANGELOG.md, which GitHub renders, and drop the two reference pages. The `Changelog` project URL now points at the releases page, and the deprecation notices in the configuration and output docs link to the deprecations section of CHANGELOG.md. Co-Authored-By: Claude Opus 5 (1M context) --- .github/ISSUE_TEMPLATE/bug_report.md | 31 - .../documentation_improvement.md | 16 - .github/ISSUE_TEMPLATE/feature_request.md | 19 - .github/release-drafter.yml | 13 +- CHANGELOG.md | 176 ++++- README.md | 40 +- codecarbon/core/cpu.py | 25 +- codecarbon/emissions_tracker.py | 28 +- docs/explanation/faq.md | 25 +- docs/explanation/methodology.md | 93 ++- docs/explanation/when-to-use.md | 4 + docs/how-to/agent-instructions.md | 2 +- docs/how-to/configuration.md | 3 +- docs/how-to/contributing.md | 599 ++---------------- docs/how-to/enable-rapl.md | 2 + docs/how-to/examples.md | 99 +-- docs/how-to/installation.md | 4 + docs/index.md | 6 +- docs/reference/api.md | 33 +- docs/reference/changelog.md | 103 --- docs/reference/deprecations.md | 71 --- docs/reference/output.md | 3 +- docs/tutorials/first-tracking.md | 1 + examples/README.md | 116 +++- mkdocs.yml | 2 - pyproject.toml | 33 + 26 files changed, 590 insertions(+), 957 deletions(-) delete mode 100644 .github/ISSUE_TEMPLATE/bug_report.md delete mode 100644 .github/ISSUE_TEMPLATE/documentation_improvement.md delete mode 100644 .github/ISSUE_TEMPLATE/feature_request.md delete mode 100644 docs/reference/changelog.md delete mode 100644 docs/reference/deprecations.md diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md deleted file mode 100644 index eebce894a..000000000 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ /dev/null @@ -1,31 +0,0 @@ ---- -name: Bug Report -about: Create a report to help us improve -title: '' -labels: bug -assignees: '' ---- - -**Describe the bug** -A clear and concise description of what the bug is. - -**To Reproduce** -Steps to reproduce the behavior: -1. Go to '...' -2. Click on '....' -3. Scroll down to '....' -4. See error - -**Expected behavior** -A clear and concise description of what you expected to happen. - -**Screenshots** -If applicable, add screenshots to help explain your problem. - -**Desktop (please complete the following information):** - - OS: [e.g. iOS] - - Python Version [e.g. 3.8] - - CodeCarbon Version [e.g. 2.1.4] - -**Additional context** -Add any other context about the problem here. \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/documentation_improvement.md b/.github/ISSUE_TEMPLATE/documentation_improvement.md deleted file mode 100644 index 7db54df18..000000000 --- a/.github/ISSUE_TEMPLATE/documentation_improvement.md +++ /dev/null @@ -1,16 +0,0 @@ ---- -name: Documentation Improvement -about: Suggest improvements or report issues in documentation -title: '' -labels: documentation -assignees: '' ---- - -**Where does the documentation need improvement?** -Provide the URL or section that needs improvement. - -**What needs to be changed?** -Describe what's currently wrong or what could be better. - -**Additional context** -Add any other context about the documentation issue here. \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md deleted file mode 100644 index c0e03d002..000000000 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ /dev/null @@ -1,19 +0,0 @@ ---- -name: Feature Request -about: Suggest an idea for this project -title: '' -labels: enhancement -assignees: '' ---- - -**Is your feature request related to a problem? Please describe.** -A clear and concise description of what the problem is. - -**Describe the solution you'd like** -A clear and concise description of what you want to happen. - -**Describe alternatives you've considered** -A clear and concise description of any alternative solutions or features you've considered. - -**Additional context** -Add any other context or screenshots about the feature request here. \ No newline at end of file diff --git a/.github/release-drafter.yml b/.github/release-drafter.yml index 0ce0984f5..81c1df2ff 100644 --- a/.github/release-drafter.yml +++ b/.github/release-drafter.yml @@ -1,8 +1,17 @@ # Release drafter configuration https://github.com/release-drafter/release-drafter#configuration # Emojis were chosen to match the https://gitmoji.carloscuesta.me/ -name-template: "v$NEXT_PATCH_VERSION" -tag-template: "v$NEXT_PATCH_VERSION" +name-template: "v$RESOLVED_VERSION" +tag-template: "v$RESOLVED_VERSION" + +version-resolver: + major: + labels: [breaking] + minor: + labels: [enhancement, feature] + patch: + labels: [bug, bugfix, fix, refactoring, dependencies] + default: patch categories: - title: ":rocket: Features" diff --git a/CHANGELOG.md b/CHANGELOG.md index 4b90aad1e..15af798f2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,11 +1,175 @@ # Changelog -The curated, user-facing changelog lives in the documentation: +User-visible changes in CodeCarbon: new features, behaviour changes, deprecations, +removals, and anything that requires action on your side. For the full list of merged +pull requests, see the +[GitHub releases](https://github.com/mlco2/codecarbon/releases). -- **[docs/reference/changelog.md](docs/reference/changelog.md)** — rendered at - -- **[docs/reference/deprecations.md](docs/reference/deprecations.md)** — what is - deprecated, when it is removed, and what to use instead +Deprecated features and their replacements are listed under +[Deprecations and migrations](#deprecations-and-migrations) at the end of this file. -The full list of merged pull requests per release is on the +## Unreleased + +### Added + +- CPU power estimation now recognises the Apple M2, M3 and M4 chip families, so + measurements on recent Apple Silicon no longer fall back to the default TDP + ([#1256](https://github.com/mlco2/codecarbon/pull/1256)). + +### Changed + +- Updated the RAPL access instructions and the Ansible deployment role. + +## 3.3.0 — 2026-08-04 + +### Added + +- Windows support through the Energy Meter Interface (EMI), including multi-die + machines. Windows measurements are no longer estimation-only where EMI is available + ([#1263](https://github.com/mlco2/codecarbon/pull/1263)). + +### Changed + +- The API client now raises on HTTP errors instead of silently returning `None`. + If you call `ApiClient` directly, expect exceptions where you previously got `None` + and had to check for it ([#1277](https://github.com/mlco2/codecarbon/pull/1277)). + +### Fixed + +- Documented that `tracking_mode` affects RAM and CPU measurement, not only the + process scope ([#1163](https://github.com/mlco2/codecarbon/pull/1163)). + +## 3.2.9 — 2026-07-19 + +### Changed + +- Substantially faster startup and lower measurement overhead: the tracker defers + initialisation, caches hardware detection between runs, skips redundant GPU detail + queries while monitoring, and reads only the header row when validating an existing + CSV + ([#1251](https://github.com/mlco2/codecarbon/pull/1251), + [#1252](https://github.com/mlco2/codecarbon/pull/1252), + [#1254](https://github.com/mlco2/codecarbon/pull/1254), + [#1264](https://github.com/mlco2/codecarbon/pull/1264), + [#1235](https://github.com/mlco2/codecarbon/pull/1235)). +- When using the hosted API, the run is only created on the first emission upload + rather than at tracker start, so aborted runs no longer show up as empty runs + ([#1253](https://github.com/mlco2/codecarbon/pull/1253)). + +### Fixed + +- Dashboard: run sums now exclude filtered-out runs, the date picker month arrows are + aligned, and hovering the emissions chart no longer errors + ([#1229](https://github.com/mlco2/codecarbon/pull/1229), + [#1233](https://github.com/mlco2/codecarbon/pull/1233), + [#1230](https://github.com/mlco2/codecarbon/pull/1230)). + +## 3.2.8 — 2026-06-07 + +### Added + +- Optional, opt-in telemetry ([#1171](https://github.com/mlco2/codecarbon/pull/1171)). +- BoAmps output format, with documentation and an example + ([#1154](https://github.com/mlco2/codecarbon/pull/1154)). +- Documentation of what the hardware measurement actually covers + ([#1204](https://github.com/mlco2/codecarbon/pull/1204)). + +### Changed + +- The web dashboard front end is now a plain React application + ([#1076](https://github.com/mlco2/codecarbon/pull/1076)). + +### Deprecated + +- The `save_to_file`, `save_to_api`, `save_to_logger`, `save_to_prometheus` and + `save_to_logfire` parameters. Use `output_methods=[OutputMethod.CSV, ...]` instead. + They now emit a `DeprecationWarning`. + +### Fixed + +- `codecarbon monitor` in offline mode now uses `OfflineEmissionsTracker` + ([#1202](https://github.com/mlco2/codecarbon/pull/1202)). +- macOS: CPU detection through `psutil` ([#1211](https://github.com/mlco2/codecarbon/pull/1211)) + and importing `amdsmi` when `libamd_smi` is absent + ([#1201](https://github.com/mlco2/codecarbon/pull/1201)). +- Carbon intensity lookup for Nordic regions with the online tracker + ([#1224](https://github.com/mlco2/codecarbon/pull/1224)). +- The CLI now warns when authentication validation falls back to the API + ([#1209](https://github.com/mlco2/codecarbon/pull/1209)). + +Changes released before 3.2.8 are listed on the [GitHub releases page](https://github.com/mlco2/codecarbon/releases). + +--- + +## Deprecations and migrations + +Everything in CodeCarbon that still works but is on its way out, when it was +deprecated, when it is scheduled to be removed, and what to use instead. +User-visible changes per release are listed above. + +| Deprecated | Since | Removal | Replacement | +|---|---|---|---| +| `codecarbon[viz-legacy]` extra | 3.3.0 | 4.0.0 | `codecarbon[carbonboard]` | +| `save_to_file`, `save_to_api`, `save_to_logger`, `save_to_prometheus`, `save_to_logfire` parameters | 3.2.8 | Not scheduled | `output_methods=[...]` | +| `co2_signal_api_token` parameter and config key | 3.1.1 | Not scheduled | `electricitymaps_api_token` | + +### `viz-legacy` extra + +The old Dash-based dashboard extra is replaced by `carbonboard`. + +```bash +# before +pip install codecarbon[viz-legacy] + +# after +pip install codecarbon[carbonboard] +``` + +See [Visualize Emissions](https://docs.codecarbon.io/latest/how-to/visualize/) for how to run it. + +### `save_to_*` parameters + +Passing any `save_to_*` flag raises a `DeprecationWarning`. Pass the outputs you want +as a list instead. + +```python +# before +EmissionsTracker(save_to_file=True, save_to_logger=True) + +# after +from codecarbon.output_methods.base_output import OutputMethod + +EmissionsTracker(output_methods=[OutputMethod.CSV, OutputMethod.LOGGER]) +``` + +See [Output Formats](https://docs.codecarbon.io/latest/reference/output/) for the full list of output methods. + +### `co2_signal_api_token` + +The CO2 Signal API became part of Electricity Maps. The old parameter and config key +still work and are read as a fallback, but they log a warning. + +```python +# before +EmissionsTracker(co2_signal_api_token="...") + +# after +EmissionsTracker(electricitymaps_api_token="...") +``` + +```ini +# .codecarbon.config — before +[codecarbon] +co2_signal_api_token = ... + +# .codecarbon.config — after +[codecarbon] +electricitymaps_api_token = ... +``` + +### For maintainers + +This table is the checklist for the next major release: everything with a removal +version scheduled for it must be removed, and every "Not scheduled" row should get a +decision before the release is cut. diff --git a/README.md b/README.md index a9aef3b32..cc1aa4073 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,9 @@ pip install codecarbon More installation options: [installation docs](https://docs.codecarbon.io/latest/how-to/installation/). +Something not working, or numbers that look wrong? See the +[troubleshooting guide](https://docs.codecarbon.io/latest/how-to/troubleshooting/). + ## Quickstart (Python) ```python @@ -78,9 +81,11 @@ We created a Python package that estimates your hardware electricity power consu 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 for local code-level experiments and are not exposed through the same low-overhead measurement interfaces. +On Linux, CodeCarbon reads Intel RAPL hardware energy counters when it can. If those counters are not readable it falls back to estimating from CPU load, which is less accurate — see [getting accurate CPU measurements](https://docs.codecarbon.io/latest/how-to/enable-rapl/) to enable them. + ![calculation Summary](docs/images/calculation.png) -We explain more about this calculation in the [**Methodology**](https://docs.codecarbon.io/latest/explanation/methodology/) section of the documentation. +We explain more about this calculation in the [**Methodology**](https://docs.codecarbon.io/latest/explanation/methodology/) section of the documentation, and we document how close those numbers are — and where they are not close — in [**Accuracy and validation**](https://docs.codecarbon.io/latest/explanation/accuracy/). ## Visualize @@ -97,9 +102,12 @@ You can visualize your experiment emissions on the [dashboard](https://dashboard | [CLI Tutorial](https://docs.codecarbon.io/latest/tutorials/cli/) | Track emissions from the command line | | [Python API Tutorial](https://docs.codecarbon.io/latest/tutorials/python-api/) | Track emissions in Python code | | [Comparing Model Efficiency](https://docs.codecarbon.io/latest/tutorials/comparing-model-efficiency/) | Measure carbon efficiency across ML models | +| [Accurate CPU measurements (Linux/RAPL)](https://docs.codecarbon.io/latest/how-to/enable-rapl/) | Read real energy counters instead of estimating | | [API Reference](https://docs.codecarbon.io/latest/reference/api/) | Full parameter documentation | | [Framework examples (scikit-learn)](https://docs.codecarbon.io/latest/how-to/scikit-learn/) | Task-oriented ML framework examples | | [Methodology](https://docs.codecarbon.io/latest/explanation/methodology/) | How emissions are calculated | +| [Accuracy and validation](https://docs.codecarbon.io/latest/explanation/accuracy/) | How accurate the numbers are, and why | +| [Alternatives comparison](https://docs.codecarbon.io/latest/explanation/alternatives/) | CodeCarbon vs other carbon tracking tools | | [When to use CodeCarbon vs EcoLogits](https://docs.codecarbon.io/latest/explanation/when-to-use/) | Choose the right tool | | [EcoLogits](https://ecologits.ai/) | Track emissions from GenAI API calls | | [Discord Community](https://discord.gg/GS9js2XkJR) | Chat with us and the community | @@ -126,29 +134,18 @@ Feel free to chat with us on [Discord](https://discord.gg/GS9js2XkJR). ## Citation -If you find CodeCarbon useful for your research, you can find a citation under a variety of formats on [Zenodo](https://zenodo.org/records/11171501). - -
-BibTeX +If you find CodeCarbon useful for your research, use the **Cite this repository** button in the GitHub sidebar, or copy the BibTeX below. The DOI is a Zenodo concept DOI: it always resolves to the latest release. All versions and formats are on [Zenodo](https://doi.org/10.5281/zenodo.4658424). ```tex -@software{benoit_courty_2024_11171501, +@software{codecarbon, author = {Benoit Courty and Victor Schmidt and Sasha Luccioni and - Goyal-Kamal and - MarionCoutarel and Boris Feld and Jérémy Lecourt and - LiamConnell and - Amine Saboni and - Inimaz and - supatomic and Mathilde Léval and Luis Blanche and Alexis Cruveiller and - ouminasara and - Franklin Zhao and Aditya Joshi and Alexis Bogroff and Hugues de Lavoreille and @@ -161,20 +158,15 @@ If you find CodeCarbon useful for your research, you can find a citation under a Michał Stęchły and Christian Bauer and Lucas Otávio N. de Araújo and - JPW and - MinervaBooks}, - title = {mlco2/codecarbon: v2.4.1}, - month = may, - year = {2024}, + {The CodeCarbon contributors}}, + title = {CodeCarbon: Estimate and track carbon emissions from computing}, publisher = {Zenodo}, - version = {v2.4.1}, - doi = {10.5281/zenodo.11171501}, - url = {https://doi.org/10.5281/zenodo.11171501} + version = {3.3.0}, + doi = {10.5281/zenodo.4658424}, + url = {https://doi.org/10.5281/zenodo.4658424} } ``` -
- ## Contact Feel free to chat with us on [Discord](https://discord.gg/GS9js2XkJR). diff --git a/codecarbon/core/cpu.py b/codecarbon/core/cpu.py index e21c39fd8..2c44ff9af 100644 --- a/codecarbon/core/cpu.py +++ b/codecarbon/core/cpu.py @@ -28,6 +28,13 @@ # default W value per core for a CPU if no model is found in the ref csv DEFAULT_POWER_PER_CORE = 4 +_TROUBLESHOOTING_URL = "https://docs.codecarbon.io/latest/how-to/troubleshooting/" +RAPL_PERMISSION_HELP = ( + "You can grant read permission with: sudo chmod -R a+r /sys/class/powercap/* " + "(this does not persist across reboots, see " + f"{_TROUBLESHOOTING_URL}#rapl-permission-denied for a permanent setup)" +) + @lru_cache(maxsize=1) def is_powergadget_available() -> bool: @@ -175,18 +182,16 @@ def warn_permission_denied(energy_path: str): nonlocal already_warned if not already_warned: logger.warning( - "\tRAPL - Permission denied reading RAPL file %s. " - "You can grant read permission with: " - "sudo chmod -R a+r /sys/class/powercap/*", + "\tRAPL - Permission denied reading RAPL file %s. %s", energy_path, + RAPL_PERMISSION_HELP, ) already_warned = True else: logger.debug( - "\tRAPL - Permission denied reading RAPL file %s. " - "You can grant read permission with: " - "sudo chmod -R a+r /sys/class/powercap/*", + "\tRAPL - Permission denied reading RAPL file %s. %s", energy_path, + RAPL_PERMISSION_HELP, ) return warn_permission_denied @@ -583,8 +588,7 @@ def _validate_domain_readable( return True, is_required_main except PermissionError: msg = f"\tRAPL - Permission denied reading RAPL file {rapl_file}." - suggestion = "You can grant read permission with: sudo chmod -R a+r /sys/class/powercap/*" - logger.warning("%s %s; skipping.", msg, suggestion) + logger.warning("%s %s; skipping.", msg, RAPL_PERMISSION_HELP) return False, False except Exception as e: logger.debug( @@ -1009,9 +1013,10 @@ def _main(self) -> Tuple[str, int]: ) return cpu_model_detected, power logger.warning( - "We saw that you have a %s but we don't know it." - + " Please contact us.", + "We saw that you have a %s but we don't know it. " + "Please help us add it, see %s#unknown-cpu-model", cpu_model_detected, + _TROUBLESHOOTING_URL, ) if is_psutil_available(): # Count thread of the CPU diff --git a/codecarbon/emissions_tracker.py b/codecarbon/emissions_tracker.py index 96ed00c91..368f06917 100644 --- a/codecarbon/emissions_tracker.py +++ b/codecarbon/emissions_tracker.py @@ -686,6 +686,22 @@ def get_detected_hardware(self) -> Dict[str, Any]: return hardware_info def service_shutdown(self, signum, frame): + """ + Signal handler that stops the tracker on SIGTERM/SIGINT, for use when + CodeCarbon runs as a long-lived service. Register it with: + + ```py + import signal + + tracker = EmissionsTracker() + signal.signal(signal.SIGTERM, tracker.service_shutdown) + signal.signal(signal.SIGINT, tracker.service_shutdown) + ``` + + :param signum: Signal number, passed by `signal.signal` + :param frame: Current stack frame, passed by `signal.signal` + :return: None + """ logger.warning("service_shutdown - Caught signal %d" % signum) self.stop() @@ -1600,7 +1616,7 @@ def track_emissions( def _decorate(fn: Callable): @wraps(fn) - def wrapped_fn(*args, **kwargs): + def _wrapped_fn(*args, **kwargs): fn_result = None # Handle backward compatibility for co2_signal_api_token @@ -1699,7 +1715,7 @@ def wrapped_fn(*args, **kwargs): logger.info("Done!\n") return fn_result - return wrapped_fn + return _wrapped_fn if fn: return _decorate(fn) @@ -1711,9 +1727,9 @@ def track_task_emissions( ): """ Decorator to track emissions specific to a task. With a tracker as input, it will add task emissions to global emissions. - :param: tracker: global tracker used in the current execution. If none is provided, instanciates an emission + :param tracker: global tracker used in the current execution. If none is provided, instanciates an emission tracker which will read default parameter from config to enable tracking - :param: task_name: Task to be tracked. If none is provided, an id will be used. + :param task_name: Task to be tracked. If none is provided, an id will be used. :return: The decorated function """ @@ -1725,7 +1741,7 @@ def track_task_emissions( def _decorate(fn: Callable[..., Any]) -> Callable[..., Any]: @wraps(fn) - def wrapped_fn(*args, **kwargs): + def _wrapped_fn(*args, **kwargs): fn_result = None tracker.start_task(task_name=task_name) try: @@ -1741,7 +1757,7 @@ def wrapped_fn(*args, **kwargs): logger.info("Done!\n") return fn_result - return wrapped_fn + return _wrapped_fn if fn: return _decorate(fn) diff --git a/docs/explanation/faq.md b/docs/explanation/faq.md index 624ebd90e..afd207a7d 100644 --- a/docs/explanation/faq.md +++ b/docs/explanation/faq.md @@ -6,7 +6,25 @@ Use **CodeCarbon** when you run code on hardware you control—training models, ## How accurate are your estimations? -It is hard to quantify the entirety of computing emissions, because there are many factors in play, notably the life-cycle emissions of computing infrastructure. We therefore only focus on the direct emissions produced by running the actual code, but recognize that there is much work to be done to improve this estimation. +It depends on which measurement backend your machine offers. With hardware energy counters (RAPL on Linux, NVML or amdsmi for GPUs) CodeCarbon reads real energy consumption. Without them it falls back to estimating CPU power from load and TDP, which on the machines we profiled deviated from RAPL by up to roughly a factor of two in either direction. Carbon intensity is a separate and often larger error source: without regional data CodeCarbon uses a world average of 475 gCO2.eq/kWh. + +We also only cover the direct emissions of running the code — CPU, GPU and RAM — and not the life-cycle emissions of the hardware. + +See [Accuracy and validation](accuracy.md) for the measured figures, the known gaps, and how to improve your own numbers. + +## How does CodeCarbon compare to other carbon tracking tools? + +See [CodeCarbon and the alternatives](alternatives.md), which covers carbontracker, eco2AI, experiment-impact-tracker, Zeus, Scaphandre, ML CO2 Impact and cloud provider tooling — including the cases where one of those is the better choice. + +Accuracy also depends on your own machine. On Linux, CodeCarbon reads real CPU energy counters when they are readable, and otherwise estimates from CPU load and the processor's TDP, which is materially less accurate. See the next question. + +## Why are my measurements estimates instead of real readings? + +On Linux, CodeCarbon reads the Intel RAPL hardware energy counters under `/sys/class/powercap`. Since a kernel security fix these files are root-only by default, so CodeCarbon often cannot read them and falls back to estimating CPU power from CPU load and the processor's TDP. You will see a `RAPL - Permission denied` warning in the logs when this happens, but the warning is easy to miss in a notebook, a training framework that reconfigures logging, or a CI job. + +The fix takes about two minutes and persists across reboots: see [Improve Measurement Accuracy with RAPL](../how-to/enable-rapl.md). The one-line `sudo chmod -R a+r /sys/class/powercap/*` also works but is reset on the next restart. + +Note that RAPL counters do not exist at all in most containers and virtual machines, so estimation is expected there. ## What are the sources of your energy carbon intensity data? @@ -55,7 +73,10 @@ CodeCarbon supports various CPU architectures, GPUs, and cloud providers. For de ## How do I report a bug? -Please open an issue on [GitHub](https://github.com/mlco2/codecarbon/issues) with: +First check the [Troubleshooting](../how-to/troubleshooting.md) guide — most +warnings CodeCarbon prints are explained there, along with the fix. + +If it is still a bug, please open an issue on [GitHub](https://github.com/mlco2/codecarbon/issues) with: - Your environment details - Steps to reproduce - Expected vs actual behavior diff --git a/docs/explanation/methodology.md b/docs/explanation/methodology.md index fb51122d0..58f37f8da 100644 --- a/docs/explanation/methodology.md +++ b/docs/explanation/methodology.md @@ -229,16 +229,18 @@ the 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://github.com/mlco2/codecarbon/issues/457). +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) -. But has been discontinued. There is a discussion about it on [github -issues #457](https://github.com/mlco2/codecarbon/issues/457). +. 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, ...)** @@ -248,9 +250,10 @@ 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 (if you know of any -solution for this do not hesitate and [open an issue with your proposed -solution](https://github.com/mlco2/codecarbon/issues/)). +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: @@ -304,8 +307,12 @@ will read two files : RAPL (Running Average Power Limit) is a feature of modern processors that provides energy consumption measurements through hardware counters. -See for more -information. +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 +[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. @@ -343,12 +350,7 @@ 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` -## References - -[Energy Usage Reports: Environmental awareness as part of algorithmic -accountability](https://arxiv.org/pdf/1911.08354.pdf) - -### How CodeCarbon Works +## How CodeCarbon Works CodeCarbon uses a scheduler that, by default, calls for a measure every 15 seconds, so it has no significant overhead. @@ -433,3 +435,64 @@ These estimates are approximate and subject to regional variations in: 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. + +## References + +### Foundational work + +Strubell, E., Ganesh, A., & McCallum, A. (2019). *Energy and Policy +Considerations for Deep Learning in NLP*. Proceedings of ACL 2019. + + +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. + + +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. + +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). + + +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*. + +Luccioni, A. S., Viguier, S., & Ligozat, A.-L. (2022). *Estimating the Carbon +Footprint of BLOOM, a 176B Parameter Language Model*. + + +### 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. + +Weaver, V. M. *Reading RAPL energy measurements from Linux*. + + +Microsoft. *Energy Meter Interface (EMI) driver documentation*. + + +### Carbon intensity data sources + +Our World in Data. *Carbon intensity of electricity generation*. + + +International Energy Agency (2019). *Global Energy & CO2 Status Report*. + + +Google Cloud. *Carbon free energy for Google Cloud regions*. + + +### Citing CodeCarbon + +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. diff --git a/docs/explanation/when-to-use.md b/docs/explanation/when-to-use.md index 83366037a..5acdb681e 100644 --- a/docs/explanation/when-to-use.md +++ b/docs/explanation/when-to-use.md @@ -21,3 +21,7 @@ CodeCarbon measures actual power consumption (CPU, GPU, RAM) and converts it to ## Both are complementary Use CodeCarbon for training and local inference. Use EcoLogits for remote API inference. Together they cover the full lifecycle of AI workloads. + +## Other tools + +For a comparison with carbontracker, eco2AI, experiment-impact-tracker, Zeus, Scaphandre, ML CO2 Impact and cloud provider tooling, see [CodeCarbon and the alternatives](alternatives.md). diff --git a/docs/how-to/agent-instructions.md b/docs/how-to/agent-instructions.md index 8b3ebfd0e..3327ed6cd 100644 --- a/docs/how-to/agent-instructions.md +++ b/docs/how-to/agent-instructions.md @@ -159,7 +159,7 @@ uv run task -l - **[carbonserver/tests/TESTING.md](https://github.com/mlco2/codecarbon/blob/master/carbonserver/tests/TESTING.md)**: Comprehensive testing guide ### VS Code Debugging -The repository includes VS Code launch configurations in `docs/how-to/contributing.md` for: +The repository includes VS Code launch configurations in `docs/how-to/development.md` for: - Debugging current Python file - Running pytest with debugger - Testing codecarbon CLI monitor diff --git a/docs/how-to/configuration.md b/docs/how-to/configuration.md index 9f6766aa1..d49828fb5 100644 --- a/docs/how-to/configuration.md +++ b/docs/how-to/configuration.md @@ -120,7 +120,8 @@ EmissionsTracker(electricitymaps_api_token="your-token-here") The old parameter name `co2_signal_api_token` still works for backward compatibility but is deprecated and will be removed in a future version. - Use `electricitymaps_api_token` instead. + Use `electricitymaps_api_token` instead. See the + [deprecations list](https://github.com/mlco2/codecarbon/blob/master/CHANGELOG.md#deprecations-and-migrations). ## Tracking Mode diff --git a/docs/how-to/contributing.md b/docs/how-to/contributing.md index 243bb80d2..827e3486f 100644 --- a/docs/how-to/contributing.md +++ b/docs/how-to/contributing.md @@ -1,88 +1,32 @@ -# Contributing to Code Carbon +# Contributing to CodeCarbon -(New to open-source? [Here's a guide to help you](https://opensource.guide/how-to-contribute/)) +New to open source? [Here's a guide to help you](https://opensource.guide/how-to-contribute/). +Want to talk to someone first? [Join us on Discord](https://discord.gg/GS9js2XkJR) — we're +happy to help you find something to work on. - +## Where to start -- [Contributing to Code Carbon](#contributing-to-code-carbon) - - [Have a Question?](#have-a-question) - - [Found a Bug?](#found-a-bug) - - [Have a Feature Request?](#have-a-feature-request) - - [Alternative ways of contributing](#alternative-ways-of-contributing) - - [Ready to Contribute!](#ready-to-contribute) - - [Installation](#installation) - - [Some UV commands](#some-uv-commands) - - [Tests](#tests) - - [Stress your computer](#stress-your-computer) - - [Update all dependencies](#update-all-dependencies) - - [Branching and Pull Requests](#branching-and-pull-requests) - - [Debug in VS Code](#debug-in-vs-code) - - [Coding style \&\& Linting](#coding-style-linting) - - [Dependencies management](#dependencies-management) - - [Build Documentation 🖨️](#build-documentation) - - [Release process](#release-process) - - [Test the build in Docker](#test-the-build-in-docker) - - [API and Dashboard](#api-and-dashboard) - - [CSV Dashboard](#csv-dashboard) - - [Web dashboard](#web-dashboard) - - [API](#api) - - [Test the API](#test-the-api) - - [Restore database from a production Backup](#restore-database-from-a-production-backup) - - [Deployment](#deployment) - - [API](#api-1) - - [Dashboard](#dashboard) - - [License](#license) +- **Have a question?** Check the [FAQ](https://docs.codecarbon.io/latest/explanation/faq/), + then ask on [Discord](https://discord.gg/GS9js2XkJR). +- **Found a bug?** [Open an issue](https://github.com/mlco2/codecarbon/issues/new), and feel + free to send a pull request with the fix too. +- **Have a feature request?** + [Open an issue](https://github.com/mlco2/codecarbon/issues/new) describing the feature and + its intent. Please search the existing issues first to avoid duplicates. +- **Want to write code?** Look for + [good first issues](https://github.com/mlco2/codecarbon/labels/good%20first%20issue) and + [help wanted](https://github.com/mlco2/codecarbon/labels/help%20wanted), or pick something + from the [prioritized board](https://github.com/orgs/mlco2/projects/1). +- **Your CPU isn't recognised?** Adding it to `codecarbon/data/hardware/cpu_power.csv` is a + genuinely useful first contribution: it improves accuracy for everyone with that hardware. +- **Documentation unclear?** Every docs page has an edit button. - +## Set up your environment - - -## Have a Question? - -Please see the [FAQ](https://docs.codecarbon.io/latest/explanation/faq/) for questions. You can also ask on our [Discord community](https://discord.gg/GS9js2XkJR) – we're happy to help! - - - -## Found a Bug? - -If you've identified a bug in `codecarbon`, please [submit an issue](https://github.com/mlco2/codecarbon/issues/new) to the GitHub repo: [mlco2/codecarbon](https://github.com/mlco2/codecarbon/issues/new). Please also feel free to submit a PR with a fix for the bug! - - - -## Have a Feature Request? - -Feel free to describe your request by [submitting an issue](https://github.com/mlco2/codecarbon/issues/new) documenting the feature (with its intent) and a PR with a proposed implementation of the feature. - -Before submitting a new issue, please search the issues to make sure there isn't a similar issue already. -New issues can be created within the [GitHub repo](https://github.com/mlco2/codecarbon/issues/new). - - -## Alternative ways of contributing - -You have a cool idea, but do not know if it fits with Code Carbon? You can create an issue to share: - -- the code, via the Github repo or [Binder](https://mybinder.org/), to share executable notebooks -- a webapp, using [Voilà](https://github.com/voila-dashboards/voila), [Dash](https://github.com/plotly/dash) or [Streamlit](https://github.com/streamlit/streamlit) -- ideas for improvement about the tool or its documentation - - -## Ready to Contribute! - - - -### Installation - -CodeCarbon is a Python package, to contribute to it, you need to have Python installed on your machine, natively or with [UV](https://github.com/astral-sh/uv). - -Between April 2024 and July 2025 we used Hatch for managing development environment. Since August 2025 we use UV to manage the environments, Python versions, and dependencies - it's a fast, reliable way to work with Python projects. - -We have dropped support of Python 3.6 since version 2.0.0 of CodeCarbon. - -We have dropped support of Python 3.8 and 3.9 since version 3.2.4 of CodeCarbon. - -Please install [UV](https://github.com/astral-sh/uv) following [installation instructions](https://docs.astral.sh/uv/getting-started/installation/#standalone-installer). - -Then, clone the repository and create the environment with: +CodeCarbon is a Python package. We use [UV](https://github.com/astral-sh/uv) to manage +environments, Python versions and dependencies — install it with the +[standalone installer](https://docs.astral.sh/uv/getting-started/installation/#standalone-installer), +then: ```sh git clone https://github.com/mlco2/codecarbon.git @@ -91,490 +35,61 @@ uv sync uv run task pre-commit-install ``` - -### Some UV commands - -UV simplifies Python package management with fast, reliable commands: - -```sh -# Show dependencies -uv tree -# Add a default dependency -uv add pandas -# Add a dev dependency -uv add --dev pytest -# Add a dependency for an extra feature -uv add --optional api logfire[fastapi] -# List all task for CodeCarbon -uv run task -l -# Run a specific version of python -uv run --python 3.14 codecarbon monitor -``` - - -### Tests - -You can run the unit tests by running UV in the terminal when in the root package directory: +## Run the tests ```sh uv run task test-package ``` -Run a specific test file: - -```sh -uv run python -m pytest tests/test_cpu.py -``` - -You can also run a specific test: - -```sh -uv run python -m unittest tests.test_your_feature.YourTestCase.test_function -``` - -For example: `uv run python -m unittest tests.test_energy.TestEnergy.test_wraparound_delta_correct_value` - -Some tests will fail if you do not set *CODECARBON_ALLOW_MULTIPLE_RUNS* with `export CODECARBON_ALLOW_MULTIPLE_RUNS=True` before running test manually. - -To test the API, see [how to run it locally](#api) first. - -Core and external classes are unit tested, with one test file per class. Most pull requests are expected to contain either new tests or test updates. If you are unusure what to test / how to test it, please put it in the pull request description and the maintainers will help you. - - - -### Stress your computer - -To test CodeCarbon, it is useful to stress your computer to make it use its full power: - -- 7Zip is often already installed, running it with `7z b` makes a quick CPU test. -- [GPU-burn](https://github.com/wilicc/gpu-burn) will load test the GPU for a configurable duration. -- To test the CPU : `stress-ng --cpu 0 --cpu-method matrixprod --metrics-brief --rapl --perf -t 60s` See [our documentation](https://docs.codecarbon.io/latest/how-to/test-on-scaleway/) to install it. -- To do useful computation while testing [Folding At Home](https://foldingathome.org/) is a good option. -- [OCCT](https://www.ocbase.com/download) is a proprietary tool but free for non-commercial use and available for Windows and Linux. - -To monitor the power consumption of your computer while stressing it, you can use: - -- `nvidia-smi` is a useful tool to see the metrics of the GPU and compare it with CodeCarbon. -- [powerstat](https://github.com/ColinIanKing/powerstat) can be used to see the metrics of the CPU and compare it with CodeCarbon. It's available on major distribution, like Debian-based Linux distributions with `sudo apt install powerstat`. Run it with `sudo powerstat -a -R 1 60`. - - - -### Update all dependencies - -For multiple requirement files: -```sh -uv sync --upgrade -``` - - -### Branching and Pull Requests - -To add a new feature to codecarbon, apply the following workflow: - -- Master branch is protected -- To contribute to an already [prioritized](https://github.com/orgs/mlco2/projects/1) feature, you can create a branch from master and open a draft PR -- Documenting the intent & the limits of a contribution in a dedicated issue or in the pull request helps the review -- Once automated tests pass, the PR is reviewed and merged by the repository maintainers - - - -### Debug in VS Code - -Here is the launch.json to be able to debug examples and tests: - -```json -{ - "version": "0.2.0", - "configurations": [ - - { - "name": "Python: Current File", - "type": "debugpy", - "request": "launch", - "program": "${file}", - "console": "integratedTerminal", - "justMyCode": true, - "env": { "PYTHONPATH": "${workspaceRoot}" } - }, - { - "name": "PyTest: Current File", - "type": "debugpy", - "request": "launch", - "module": "pytest", - "args": [ - "-s", - "${file}" - ], - "console": "integratedTerminal", - "justMyCode": true, - "env": { "PYTHONPATH": "${workspaceRoot}", - "CODECARBON_ALLOW_MULTIPLE_RUNS": "True" } - }, - { - "name": "PyTest: codecarbon monitor", - "type": "debugpy", - "request": "launch", - "module": "codecarbon.cli.main", - "args": [ - "monitor" - ], - "console": "integratedTerminal", - "justMyCode": true, - "env": { "PYTHONPATH": "${workspaceRoot}"} - } - ] -} -``` - -Then run opened test with this button: - -![vscode_debug](../images/vscode_debug.png) - - - -### Coding style && Linting - -The coding style and linting rules are automatically applied and enforced by [pre-commit](https://pre-commit.com/). This tool helps to maintain the same code style across the code-base such to ease the review and collaboration process. Once installed ([https://pre-commit.com/#installation](https://pre-commit.com/#installation)), you can install a Git hook to automatically run pre-commit (and all configured linters/auto-formatters) before doing a commit with `uv run task precommit-install`. Then once you tried to commit, the linters/formatters will run automatically. It should display something similar to: - -```log -[INFO] Initializing environment for https://github.com/psf/black. -[INFO] Initializing environment for https://gitlab.com/pycqa/flake8. -[INFO] Installing environment for https://github.com/psf/black. -[INFO] Once installed this environment will be reused. -[INFO] This may take a few minutes... -[INFO] Installing environment for https://gitlab.com/pycqa/flake8. -[INFO] Once installed this environment will be reused. -[INFO] This may take a few minutes... -seed isort known_third_party.............................................Passed -isort....................................................................Failed -- hook id: isort -- files were modified by this hook - -Fixing codecarbon/__init__.py - -black....................................................................Passed -flake8...................................................................Passed -``` - -If any of the linters/formatters fail, check the difference with `git diff`, add the differences if there is no behavior changes (isort and black might have change some coding style or import order, this is expected it is their job) with `git add` and finally try to commit again `git commit ...`. - -You can also run `pre-commit` with `uv run pre-commit run --all-file` to check all file. - - - -### Dependencies management - -Dependencies are defined in different places: - -- In [pyproject.toml](https://github.com/mlco2/codecarbon/blob/master/pyproject.toml#L28), those are all the dependencies. -- In [uv.lock](https://github.com/mlco2/codecarbon/blob/master/uv.lock), those are the locked dependencies managed by UV, do not edit them. - - - -### Build Documentation 🖨️ - -No software is complete without great documentation! -To make generating documentation easier, we use [Zensical](https://zensical.org/). - -In order to make changes, edit the `.md` files in the `/docs` folder, and then run in root folder: - -```sh -uv run --only-group doc task docs -``` - -to regenerate the html files. For local preview with live reload, run `uv run --only-group doc task docs-serve`. - -### Rebase your branch on master - -Before creating a PR, please make sure to rebase your branch on master to avoid merge conflicts and make the review easier. You can do it with the following command: -```sh -# Be careful, this command will delete every local changes you have, make sure to commit or stash them before running it -TARGET_BRANCH=master -current_branch=$(git symbolic-ref --short HEAD) -git switch $TARGET_BRANCH && git pull -git switch $current_branch --force && git fetch origin $TARGET_BRANCH -git rebase $TARGET_BRANCH -``` - -In case of a conflict during a rebase, "incoming" refers to your branch, and "current" refers to master. This is because the commits from your branch are being applied to master, so they are incoming. In case of a merge, it's the opposite! - -Check if everything is fine: - -```sh -git status -``` - -Push force -```sh -git push --force-with-lease -``` - - -### Release process - -- Merge all PRs. -- Open a terminal and make sure you are not in a venv with `deactivate`. -- Create a PR bumping the version with `uv run bumpver update --patch`. For a release candidate, use `uv run bumpver update --set-version 3.0.0_rc1`. -- Run `uv run python .github/pyproject_versions.py -c` to check version consistancy. -- Update the dependencies with `uv sync --upgrade` -- [Build Documentation](#build-documentation) with `uv run --only-group doc task docs`. -- Push the changes. -- Merge the PR. -- Wait for the Github Action `ReleaseDrafter` to finish running on the merge commit. -- [Edit the Draft release](https://github.com/mlco2/codecarbon/releases/) on Github and give it a tag, `v1.0.0` for the version 1.0.0. Github will automatically create a Git tag for it. Complete help [here](https://docs.github.com/en/repositories/releasing-projects-on-github/managing-releases-in-a-repository). -- A [Github Action](https://github.com/mlco2/codecarbon/actions) _Upload Python Package_ will be run automaticaly to upload the package. - -#### Test the release - -After the release on PyPi, please test it in a fresh environment: - -```sh -cd /tmp -rm -rf cc_rel_test -python -m venv cc_rel_test -source cc_rel_test/bin/activate -pip install codecarbon -# Check you have the last version -codecarbon --version -codecarbon monitor --offline --country-iso-code FRA -# Stop it with Ctrl+C if it works -# Then clean up -rm -rf cc_rel_test -``` - -And check if the doc looks good on [docs.codecarbon.io](https://docs.codecarbon.io/). - - -#### Test the build in Docker - -If you want to check the build is working, you could run: - -```bash -rm dist/* -uv build -docker run -it --rm -v $PWD:/data python:3.13 /bin/bash -pip install pytest pytest-mock requests-mock responses pandas -pip install --no-cache-dir /data/dist/codecarbon-*.whl -U --force-reinstall -cp /data/tests/test_package_integrity.py . -pytest test_package_integrity.py -``` - -### Contribute to a fork branch - -When a user open a PR from a fork, we are allowed to push to the fork branch. - -If you want to do so, do the following: - -```bash -git remote add https://github.com//codecarbon.git -git fetch -git checkout -b / -``` - - -## API and Dashboard - - -### CSV Dashboard - -To run locally the dashboard application, you can use it out on a sample data file such as the one in `examples/emissions.csv`, and run it with the following command from the code base: - -```bash -uv run --extra carbonboard task carbonboard --filepath="examples/emissions.csv" - -# or, if you don't want to use UV -pip install codecarbon[carbonboard] -python codecarbon/viz/carbonboard.py --filepath="examples/emissions.csv" -``` - -> **Note:** The `viz-legacy` extra is deprecated but still works for backwards compatibility. It will be removed in v4.0.0. Please use `carbonboard` instead. - -If you have the package installed, you can run the CLI command: - -```bash -carbonboard --filepath="examples/emissions.csv" --port=8050 -``` - - -### Web dashboard - -To test the new dashboard that uses the API, run: - -```sh -uv run task local -``` - -Then, click on the url displayed in the terminal. - -By default, the dashboard is connected to the production API, to connect it to your local API, you can set the environment variable `CODECARBON_API_URL` to `http://localhost:8008` : - -```sh -export CODECARBON_API_URL=http://localhost:8008 -uv run dashboard -``` - - - -### API - -The easiest way to run the API locally is with Docker, it will set-up the Postgres database for you. Launch this command in the project directory: - -```sh -uv run api.docker - -# or - -docker-compose up -d -``` - -Please see [Docker specific documentation](https://github.com/mlco2/codecarbon/blob/master/docker/README.md) for more informations. -When up, the API documentation is available locally at the following URL: http://localhost:8008/redoc and can be used for testing. - -If you want to run the API without Docker, you must first set the environment variables described in the .env.example file, and run the following command: - -```sh -uv run api.local -``` - -In order to make codecarbon automatically connect to the local API, create a file `.codecarbon.config` with contents: - -``` -[codecarbon] -api_endpoint = http://localhost:8008 -``` - -Before using it, you need an experiment_id, to get one, run: - -``` -codecarbon login -``` - -It will ask the API for an experiment_id on the default project and save it to `.codecarbon.config` for you. - -Then you could run an example: - -``` -python examples/api_call_debug.py -``` - -📝 Edit the line `occurence = 60 * 24 * 365 * 100` to specify the number of minutes you want to run it. - - - -### Test the API - -Test dependencies (pytest, pytest-asyncio, etc.) are in the `dev` optional group. Install them first: - -```sh -uv sync --project carbonserver --extra dev -``` - -Then run: - -```sh -uv run task test-api-unit -``` - -```sh -export CODECARBON_API_URL=http://localhost:8008 -uv run task test-api-integ -``` - - -### Restore database from a production Backup - -```sh -docker cp postgresql_*.dump postgres_codecarbon:/tmp -docker exec -it postgres_codecarbon bash -export BACKUP_USER=upwnpbdktjvnoks0foxq -export BACKUP_DB=bnrwiktgr4hzukt1xseg -psql -U $POSTGRES_USER -d $POSTGRES_DB -c "CREATE USER $BACKUP_USER WITH PASSWORD '$POSTGRES_PASSWORD';" -psql -U $POSTGRES_USER -d $POSTGRES_DB -c "ALTER USER $BACKUP_USER CREATEDB;" -createdb -U $BACKUP_USER $BACKUP_DB -psql -U $BACKUP_USER -d $POSTGRES_DB -c "CREATE DATABASE $BACKUP_DB;" -pg_restore -d $BACKUP_DB -U $BACKUP_USER --jobs=8 --clean --create /tmp/postgresql_*.dump -psql -U $BACKUP_USER -d $BACKUP_DB -c "GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA public TO \"$POSTGRES_USER\";" -psql -U $POSTGRES_USER -d $BACKUP_DB -c "ALTER DATABASE $POSTGRES_DB RENAME TO \"$POSTGRES_DB-backup\";" -psql -U $BACKUP_USER -d $POSTGRES_DB-backup -c "ALTER DATABASE $BACKUP_DB RENAME TO $POSTGRES_DB;" -``` - -#### Clean the database - -To remove orphans (elements without run) from the database, run: - -```sql -CALL public.spcc_purgeduplicatedata(); -``` - +Some tests fail unless you set `export CODECARBON_ALLOW_MULTIPLE_RUNS=True` before running +them manually. Core and external classes are unit tested, with one test file per class. Most +pull requests are expected to contain either new tests or test updates. If you are unsure +what to test or how to test it, say so in the pull request description and the maintainers +will help you. - -### Deployment - - - -#### API - -The API is available to everyone from https://api.codecarbon.io, but if you want to deploy it for yourself, here are the instructions. - -To deploy the API we use [Clever Cloud](https://www.clever-cloud.com/), an IT Automation platform. They manage all the hard ops work while we focus on the Code Carbon value. - -Here is the Clever Cloud configuration if you want to reproduce it: - -```conf -APP_FOLDER="carbonserver" -CC_PIP_REQUIREMENTS_FILE="requirements.txt" -CC_POST_BUILD_HOOK="cd $APP_HOME/carbonserver && python3 -m alembic -c carbonserver/database/alembic.ini upgrade head" -CC_PYTHON_BACKEND="uvicorn" -CC_PYTHON_MODULE="main:app" -CC_PYTHON_VERSION="3.13" -DATABASE_URL="postgresql://secret_do_not_publish_this" -PORT="8080" -``` - -_CC stand here for Clever Cloud, not Code Carbon_ 😉 - -To deploy, +## Check style before you push ```sh -git remote add deploy git+ssh://git@push-n2-par-clevercloud-customers.services.clever-cloud.com/app_.git -git push deploy master:master +uv run task format +uv run task lint ``` -Yeah, not so hard, is it? +The pre-commit hook runs these for you. -See [the doc](https://www.clever-cloud.com/doc/getting-started/quickstart/) for more informations. +## Open your pull request -Please note that Clever Cloud host Code Carbon for free because they like our project. +`master` is protected, so branch from it and open a pull request — draft pull requests are +welcome if you want early feedback. Keep the change focused and describe the problem it +solves; documenting the intent and the limits of a contribution, in the pull request or in a +dedicated issue, helps the review. Once the automated tests pass, a maintainer reviews and +merges it. +## Alternative ways of contributing - -#### Dashboard - -Same as for the API, for example to deploy the branch `fix-unit` to CleverCloud: - -```sh -git push clever-dashboard fix-unit:master -``` +You have a cool idea, but do not know if it fits with CodeCarbon? You can create an issue to +share: -Config on CleverCloud: +- the code, via the GitHub repo or [Binder](https://mybinder.org/), to share executable notebooks +- a webapp, using [Voilà](https://github.com/voila-dashboards/voila), [Dash](https://github.com/plotly/dash) or [Streamlit](https://github.com/streamlit/streamlit) +- ideas for improvement about the tool or its documentation -```sh -APP_FOLDER="dashboard" -CC_PIP_REQUIREMENTS_FILE="requirements-dashboard.txt" -CC_PYTHON_MODULE="carbon_board_API:server" -CC_PYTHON_VERSION="3.13" -CODECARBON_API_URL="https://api.codecarbon.io" -PORT="8000" -``` +## More detail +- [Development guide](development.md) — UV commands, debugging, stress testing, dependency + management, building the docs, running the API and dashboards locally +- [Maintainer guide](../maintaining.md) — release process and deployment +- [AI policy](ai_policy.md) +- [Code of conduct](code-of-conduct.md) ## Questions or Need Help? -Got stuck? Have an idea? Want to share your contribution? **[Join us on Discord](https://discord.gg/GS9js2XkJR)** – our community is here to help and support you! +Got stuck? Have an idea? Want to share your contribution? +**[Join us on Discord](https://discord.gg/GS9js2XkJR)** – our community is here to help and +support you! - ## License -By contributing your code, you agree to license your contribution under the terms of the [MIT License](https://github.com/mlco2/codecarbon/blob/master/LICENSE). +By contributing your code, you agree to license your contribution under the terms of the +[MIT License](https://github.com/mlco2/codecarbon/blob/master/LICENSE). All files are released with the MIT license. diff --git a/docs/how-to/enable-rapl.md b/docs/how-to/enable-rapl.md index ce297380b..ea0f0c9db 100644 --- a/docs/how-to/enable-rapl.md +++ b/docs/how-to/enable-rapl.md @@ -8,6 +8,8 @@ Without RAPL, CodeCarbon estimates CPU power based on hardware specifications an - ✅ **Direct hardware measurements** — Read CPU energy directly from RAPL counters - ✅ **Higher precision** — Microjoule-level accuracy instead of estimates + +On the CPUs we profiled, the load-and-TDP estimate deviated from RAPL by up to roughly a factor of two in either direction. See [Accuracy and validation](../explanation/accuracy.md) for the measured figures. - ✅ **Multi-domain support** — Measure package, core, uncore, DRAM, and GPU separately - ✅ **Real-time data** — No delay or aggregation artifacts diff --git a/docs/how-to/examples.md b/docs/how-to/examples.md index 06f084aca..7380c5933 100644 --- a/docs/how-to/examples.md +++ b/docs/how-to/examples.md @@ -1,97 +1,12 @@ # CodeCarbon Examples -The directory [examples/](https://github.com/mlco2/codecarbon/tree/master/examples) contains practical examples demonstrating how to use CodeCarbon to track carbon emissions from your computing tasks. The examples below are organized by use case rather than alphabetically. - -## Quick Start Examples - -| Example | Type | Description | -|---------|------|-------------| -| [print_hardware.py](https://github.com/mlco2/codecarbon/blob/master/examples/print_hardware.py) | Python Script | Detect and display available hardware (CPU, GPU, RAM) on your system | -| [command_line_tool.py](https://github.com/mlco2/codecarbon/blob/master/examples/command_line_tool.py) | Python Script | Track emissions of external command-line tools executed via subprocess | - -## Tracking Methods - -| Example | Type | Description | -|---------|------|-------------| -| [mnist_decorator.py](https://github.com/mlco2/codecarbon/blob/master/examples/mnist_decorator.py) | Python Script | Track emissions using the `@track_emissions` decorator on functions | -| [mnist_context_manager.py](https://github.com/mlco2/codecarbon/blob/master/examples/mnist_context_manager.py) | Python Script | Track emissions using `EmissionsTracker` as a context manager (with statement) | -| [mnist_callback.py](https://github.com/mlco2/codecarbon/blob/master/examples/mnist_callback.py) | Python Script | Track emissions using Keras/TensorFlow callbacks during model training | -| [api_call_demo.py](https://github.com/mlco2/codecarbon/blob/master/examples/api_call_demo.py) | Python Script | Track emissions and send data to the CodeCarbon API with `@track_emissions` | - -## Basic Model Training - -| Example | Type | Description | -|---------|------|-------------| -| [mnist.py](https://github.com/mlco2/codecarbon/blob/master/examples/mnist.py) | Python Script | Train a simple neural network on MNIST dataset with TensorFlow | -| [mnist-sklearn.py](https://github.com/mlco2/codecarbon/blob/master/examples/mnist-sklearn.py) | Python Script | Train a scikit-learn model on MNIST and track emissions | -| [pytorch-multigpu-example.py](https://github.com/mlco2/codecarbon/blob/master/examples/pytorch-multigpu-example.py) | Python Script | PyTorch CNN training on MNIST with multi-GPU support | - -## Hyperparameter Search - -| Example | Type | Description | -|---------|------|-------------| -| [mnist_grid_search.py](https://github.com/mlco2/codecarbon/blob/master/examples/mnist_grid_search.py) | Python Script | Grid search hyperparameter optimization with emission tracking | -| [mnist_random_search.py](https://github.com/mlco2/codecarbon/blob/master/examples/mnist_random_search.py) | Python Script | Random search hyperparameter optimization with emission tracking | - -## ML Model Inference - -| Example | Type | Description | -|---------|------|-------------| -| [bert_inference.py](https://github.com/mlco2/codecarbon/blob/master/examples/bert_inference.py) | Python Script | BERT language model inference with task-level tracking | -| [task_inference.py](https://github.com/mlco2/codecarbon/blob/master/examples/task_inference.py) | Python Script | Track emissions for different inference tasks (load dataset, build model, predict) | -| [task_loop_same_task.py](https://github.com/mlco2/codecarbon/blob/master/examples/task_loop_same_task.py) | Python Script | Track emissions running the same task multiple times | -| [transformers_smollm2.py](https://github.com/mlco2/codecarbon/blob/master/examples/transformers_smollm2.py) | Python Script | Small language model (SmolLM2) inference from Hugging Face | -| [ollama_local_api.py](https://github.com/mlco2/codecarbon/blob/master/examples/ollama_local_api.py) | Python Script | Track emissions of local LLM API calls using Ollama | - -## Hardware-Specific Examples - -| Example | Type | Description | -|---------|------|-------------| -| [intel_npu.py](https://github.com/mlco2/codecarbon/blob/master/examples/intel_npu.py) | Python Script | Intel Neural Processing Unit (NPU) support for model inference | -| [full_cpu.py](https://github.com/mlco2/codecarbon/blob/master/examples/full_cpu.py) | Python Script | Demonstrate full CPU utilization and emission tracking | - -## Parallel & Concurrent Processing - -| Example | Type | Description | -|---------|------|-------------| -| [multithread.py](https://github.com/mlco2/codecarbon/blob/master/examples/multithread.py) | Python Script | Track emissions from multithreaded workloads | -| [compare_cpu_load_and_RAPL.py](https://github.com/mlco2/codecarbon/blob/master/examples/compare_cpu_load_and_RAPL.py) | Python Script | Compare RAPL power measurement vs CPU load estimation in parallel workloads | - -## Logging & Output Integration - -| Example | Type | Description | -|---------|------|-------------| -| [boamps_output.py](https://github.com/mlco2/codecarbon/blob/master/examples/boamps_output.py) | Python Script | Write the output in [BoAmps](https://github.com/Boavizta/BoAmps) format. | -| [logging_to_file.py](https://github.com/mlco2/codecarbon/blob/master/examples/logging_to_file.py) | Python Script | Save emissions data to a local CSV file | -| [logging_to_file_exclusive_run.py](https://github.com/mlco2/codecarbon/blob/master/examples/logging_to_file_exclusive_run.py) | Python Script | Long-running process with exclusive file logging | -| [logging_to_google_cloud.py](https://github.com/mlco2/codecarbon/blob/master/examples/logging_to_google_cloud.py) | Python Script | Send emissions data to Google Cloud Logging | -| [logfire_metrics.py](https://github.com/mlco2/codecarbon/blob/master/examples/logfire_metrics.py) | Python Script | Integrate CodeCarbon with Logfire metrics platform | -| [prometheus_call.py](https://github.com/mlco2/codecarbon/blob/master/examples/prometheus_call.py) | Python Script | Export emissions metrics to Prometheus | -| [mnist-comet.py](https://github.com/mlco2/codecarbon/blob/master/examples/mnist-comet.py) | Python Script | Integrate emission tracking with Comet.ml experiment tracking | - -## Metrics & Analysis - -| Example | Type | Description | -|---------|------|-------------| -| [pue.py](https://github.com/mlco2/codecarbon/blob/master/examples/pue.py) | Python Script | Calculate Power Usage Effectiveness (PUE) with CodeCarbon | -| [wue.py](https://github.com/mlco2/codecarbon/blob/master/examples/wue.py) | Python Script | Calculate Water Usage Effectiveness (WUE) of your computing | - -## Interactive Notebooks - -| Example | Type | Description | -|---------|------|-------------| -| [notebook.ipynb](https://github.com/mlco2/codecarbon/blob/master/examples/notebook.ipynb) | Jupyter Notebook | Basic CodeCarbon usage in Jupyter environment | -| [compare_cpu_load_and_RAPL.ipynb](https://github.com/mlco2/codecarbon/blob/master/examples/compare_cpu_load_and_RAPL.ipynb) | Jupyter Notebook | Compare different power measurement methods (RAPL vs CPU load) | -| [local_llms.ipynb](https://github.com/mlco2/codecarbon/blob/master/examples/local_llms.ipynb) | Jupyter Notebook | Track emissions of local LLM inference | - -## Setup & Configuration - -| Item | Description | -|------|-------------| -| [requirements-examples.txt](https://github.com/mlco2/codecarbon/blob/master/examples/requirements-examples.txt) | Python dependencies for running the examples | -| [rapl/](https://github.com/mlco2/codecarbon/blob/master/examples/rapl/) | Setup instructions for RAPL power measurement support | -| [slurm_rocm/](https://github.com/mlco2/codecarbon/blob/master/examples/slurm_rocm/) | Configuration for SLURM job scheduler with ROCm GPU support | -| [notebooks/](https://github.com/mlco2/codecarbon/blob/master/examples/notebooks/) | Additional Jupyter notebooks | +The directory [examples/](https://github.com/mlco2/codecarbon/tree/master/examples) contains practical examples demonstrating how to use CodeCarbon to track carbon emissions from your computing tasks. + +The canonical, always-up-to-date index of every example lives in +[examples/README.md](https://github.com/mlco2/codecarbon/blob/master/examples/README.md), +grouped by use case: getting started, tracking parts of a run, models and +inference, sending results somewhere, configuration, hardware debugging and +clusters. It is not duplicated here so the two cannot drift apart. ## Running the Examples diff --git a/docs/how-to/installation.md b/docs/how-to/installation.md index a1113c222..7dec7215f 100644 --- a/docs/how-to/installation.md +++ b/docs/how-to/installation.md @@ -54,6 +54,10 @@ Please refer to [pyproject.toml](https://github.com/mlco2/codecarbon/blob/master/pyproject.toml) for the latest list of the packages used. +## After installing on Linux + +CodeCarbon is most accurate when it can read the Intel RAPL hardware energy counters, which are root-only by default on modern kernels. Without them it estimates CPU power from CPU load and the processor's TDP. See [Improve Measurement Accuracy with RAPL](enable-rapl.md) for a permanent, reboot-proof setup. + ## (Non-Python users) Standalone installer If you are not using Python but would like to run CodeCarbon (for instance to use the [CodeCarbon Command line](../tutorials/cli.md)), we diff --git a/docs/index.md b/docs/index.md index 5dc7720ca..5abc05cba 100644 --- a/docs/index.md +++ b/docs/index.md @@ -73,9 +73,13 @@ A single datacenter can consume large amounts of energy to run computing code. A | [CLI Tutorial](tutorials/cli.md) | Track emissions from the command line | | [Python API Tutorial](tutorials/python-api.md) | Track emissions in Python code | | [Comparing Model Efficiency](tutorials/comparing-model-efficiency.md) | Measure carbon efficiency across ML models | +| [Accurate CPU measurements (Linux/RAPL)](how-to/enable-rapl.md) | Read real energy counters instead of estimating | +| [Troubleshooting](how-to/troubleshooting.md) | Fix warnings and numbers that look wrong | | [API Reference](reference/api.md) | Full parameter documentation | | [Framework Examples](how-to/scikit-learn.md) | Example usage patterns | | [Methodology](explanation/methodology.md) | How emissions are calculated | +| [Accuracy and validation](explanation/accuracy.md) | How accurate the numbers are, and why | +| [Alternatives comparison](explanation/alternatives.md) | CodeCarbon vs other carbon tracking tools | | [EcoLogits](https://ecologits.ai/latest/?utm_source=codecarbon&utm_medium=docs) | Track emissions from GenAI API calls | | [Discord Community](https://discord.gg/GS9js2XkJR) | Chat with us and the community | @@ -84,5 +88,5 @@ A single datacenter can consume large amounts of energy to run computing code. A CodeCarbon is built by a community of open-source contributors and supported by organizations committed to sustainable computing. - **[Contributors](https://github.com/mlco2/codecarbon/graphs/contributors)** - See everyone who has contributed to the project -- **[Citation](https://zenodo.org/records/11171501)** - Cite CodeCarbon in your research +- **[Citation](https://doi.org/10.5281/zenodo.4658424)** - Cite CodeCarbon in your research - **[Partners](https://github.com/mlco2/codecarbon#partners)** - The organizations supporting this work diff --git a/docs/reference/api.md b/docs/reference/api.md index 15117b123..77333564c 100644 --- a/docs/reference/api.md +++ b/docs/reference/api.md @@ -1,4 +1,4 @@ -# Parameters +# API Reference Parameters can be set via `EmissionsTracker()`, `OfflineEmissionsTracker()`, the `@track_emissions` decorator, config files, or environment variables. See @@ -25,6 +25,22 @@ All parameters are documented below: show_root_heading: true show_signature: false +### Methods + +::: codecarbon.emissions_tracker.BaseEmissionsTracker + options: + members: + - start + - stop + - flush + - start_task + - stop_task + - get_detected_hardware + - service_shutdown + show_root_heading: false + show_signature: true + heading_level: 4 + ## OfflineEmissionsTracker (additional parameters) `OfflineEmissionsTracker` adds these parameters for offline mode: @@ -46,3 +62,18 @@ and `country_iso_code` for offline mode: options: show_root_heading: true show_signature: false + +## Task-level tracking + +Use these to measure individual tasks inside a single run. See +`examples/task_inference.py` and `examples/task_loop_same_task.py`. + +::: codecarbon.emissions_tracker.TaskEmissionsTracker + options: + show_root_heading: true + show_signature: true + +::: codecarbon.emissions_tracker.track_task_emissions + options: + show_root_heading: true + show_signature: true diff --git a/docs/reference/changelog.md b/docs/reference/changelog.md deleted file mode 100644 index 2dbceee3c..000000000 --- a/docs/reference/changelog.md +++ /dev/null @@ -1,103 +0,0 @@ -# Changelog - -User-visible changes in CodeCarbon: new features, behaviour changes, deprecations, -removals, and anything that requires action on your side. For the full list of merged -pull requests, see the -[GitHub releases](https://github.com/mlco2/codecarbon/releases). - -Deprecated features and their replacements are listed on the -[deprecations page](deprecations.md). - -## Unreleased - -### Added - -- CPU power estimation now recognises the Apple M2, M3 and M4 chip families, so - measurements on recent Apple Silicon no longer fall back to the default TDP - ([#1256](https://github.com/mlco2/codecarbon/pull/1256)). - -### Changed - -- Updated the RAPL access instructions and the Ansible deployment role. - -## 3.3.0 — 2026-08-04 - -### Added - -- Windows support through the Energy Meter Interface (EMI), including multi-die - machines. Windows measurements are no longer estimation-only where EMI is available - ([#1263](https://github.com/mlco2/codecarbon/pull/1263)). - -### Changed - -- The API client now raises on HTTP errors instead of silently returning `None`. - If you call `ApiClient` directly, expect exceptions where you previously got `None` - and had to check for it ([#1277](https://github.com/mlco2/codecarbon/pull/1277)). - -### Fixed - -- Documented that `tracking_mode` affects RAM and CPU measurement, not only the - process scope ([#1163](https://github.com/mlco2/codecarbon/pull/1163)). - -## 3.2.9 — 2026-07-19 - -### Changed - -- Substantially faster startup and lower measurement overhead: the tracker defers - initialisation, caches hardware detection between runs, skips redundant GPU detail - queries while monitoring, and reads only the header row when validating an existing - CSV - ([#1251](https://github.com/mlco2/codecarbon/pull/1251), - [#1252](https://github.com/mlco2/codecarbon/pull/1252), - [#1254](https://github.com/mlco2/codecarbon/pull/1254), - [#1264](https://github.com/mlco2/codecarbon/pull/1264), - [#1235](https://github.com/mlco2/codecarbon/pull/1235)). -- When using the hosted API, the run is only created on the first emission upload - rather than at tracker start, so aborted runs no longer show up as empty runs - ([#1253](https://github.com/mlco2/codecarbon/pull/1253)). - -### Fixed - -- Dashboard: run sums now exclude filtered-out runs, the date picker month arrows are - aligned, and hovering the emissions chart no longer errors - ([#1229](https://github.com/mlco2/codecarbon/pull/1229), - [#1233](https://github.com/mlco2/codecarbon/pull/1233), - [#1230](https://github.com/mlco2/codecarbon/pull/1230)). - -## 3.2.8 — 2026-06-07 - -### Added - -- Optional, opt-in telemetry ([#1171](https://github.com/mlco2/codecarbon/pull/1171)). -- BoAmps output format, with documentation and an example - ([#1154](https://github.com/mlco2/codecarbon/pull/1154)). -- Documentation of what the hardware measurement actually covers - ([#1204](https://github.com/mlco2/codecarbon/pull/1204)). - -### Changed - -- The web dashboard front end is now a plain React application - ([#1076](https://github.com/mlco2/codecarbon/pull/1076)). - -### Deprecated - -- The `save_to_file`, `save_to_api`, `save_to_logger`, `save_to_prometheus` and - `save_to_logfire` parameters. Use `output_methods=[OutputMethod.CSV, ...]` instead. - They now emit a `DeprecationWarning`. - -### Fixed - -- `codecarbon monitor` in offline mode now uses `OfflineEmissionsTracker` - ([#1202](https://github.com/mlco2/codecarbon/pull/1202)). -- macOS: CPU detection through `psutil` ([#1211](https://github.com/mlco2/codecarbon/pull/1211)) - and importing `amdsmi` when `libamd_smi` is absent - ([#1201](https://github.com/mlco2/codecarbon/pull/1201)). -- Carbon intensity lookup for Nordic regions with the online tracker - ([#1224](https://github.com/mlco2/codecarbon/pull/1224)). -- The CLI now warns when authentication validation falls back to the API - ([#1209](https://github.com/mlco2/codecarbon/pull/1209)). - ---- - -Changes released before 3.2.8 are listed on the -[GitHub releases page](https://github.com/mlco2/codecarbon/releases). diff --git a/docs/reference/deprecations.md b/docs/reference/deprecations.md deleted file mode 100644 index 274d2a138..000000000 --- a/docs/reference/deprecations.md +++ /dev/null @@ -1,71 +0,0 @@ -# Deprecations and Migrations - -Everything in CodeCarbon that still works but is on its way out, when it was -deprecated, when it is scheduled to be removed, and what to use instead. -User-visible changes per release are on the [changelog](changelog.md). - -| Deprecated | Since | Removal | Replacement | -|---|---|---|---| -| `codecarbon[viz-legacy]` extra | 3.3.0 | 4.0.0 | `codecarbon[carbonboard]` | -| `save_to_file`, `save_to_api`, `save_to_logger`, `save_to_prometheus`, `save_to_logfire` parameters | 3.2.8 | Not scheduled | `output_methods=[...]` | -| `co2_signal_api_token` parameter and config key | 3.1.1 | Not scheduled | `electricitymaps_api_token` | - -## `viz-legacy` extra - -The old Dash-based dashboard extra is replaced by `carbonboard`. - -```bash -# before -pip install codecarbon[viz-legacy] - -# after -pip install codecarbon[carbonboard] -``` - -See [Visualize Emissions](../how-to/visualize.md) for how to run it. - -## `save_to_*` parameters - -Passing any `save_to_*` flag raises a `DeprecationWarning`. Pass the outputs you want -as a list instead. - -```python -# before -EmissionsTracker(save_to_file=True, save_to_logger=True) - -# after -from codecarbon.output_methods.base_output import OutputMethod - -EmissionsTracker(output_methods=[OutputMethod.CSV, OutputMethod.LOGGER]) -``` - -See [Output Formats](output.md) for the full list of output methods. - -## `co2_signal_api_token` - -The CO2 Signal API became part of Electricity Maps. The old parameter and config key -still work and are read as a fallback, but they log a warning. - -```python -# before -EmissionsTracker(co2_signal_api_token="...") - -# after -EmissionsTracker(electricitymaps_api_token="...") -``` - -```ini -# .codecarbon.config — before -[codecarbon] -co2_signal_api_token = ... - -# .codecarbon.config — after -[codecarbon] -electricitymaps_api_token = ... -``` - -## For maintainers - -This table is the checklist for the next major release: everything with a removal -version scheduled for it must be removed, and every "Not scheduled" row should get a -decision before the release is cut. diff --git a/docs/reference/output.md b/docs/reference/output.md index 720e0a883..8bd97075a 100644 --- a/docs/reference/output.md +++ b/docs/reference/output.md @@ -22,7 +22,8 @@ It can also be set in the config file as a comma-separated string, e.g. The individual `save_to_file`, `save_to_api`, `save_to_logger`, `save_to_prometheus` and `save_to_logfire` parameters are deprecated and will be removed in a future version. Use `output_methods` instead. When `output_methods` - is provided, the `save_to_*` flags are ignored. + is provided, the `save_to_*` flags are ignored. See the + [deprecations list](https://github.com/mlco2/codecarbon/blob/master/CHANGELOG.md#deprecations-and-migrations). ## CSV diff --git a/docs/tutorials/first-tracking.md b/docs/tutorials/first-tracking.md index 82efd5a9d..6486a7e88 100644 --- a/docs/tutorials/first-tracking.md +++ b/docs/tutorials/first-tracking.md @@ -58,6 +58,7 @@ print(f"Energy consumed: {tracker.final_emissions_data.energy_consumed:.6f} kWh" ## What's next? - [Configure CodeCarbon](../how-to/configuration.md) with config files, environment variables, or script parameters +- On Linux, [get accurate CPU measurements](../how-to/enable-rapl.md) by enabling the RAPL hardware energy counters - Learn about [CLI tracking](cli.md) to monitor without code changes - Explore all [Python API options](python-api.md) (decorators, explicit objects, offline mode) - See the full [API Reference](../reference/api.md) for all configuration parameters diff --git a/examples/README.md b/examples/README.md index 3e6e022b1..070b1b45b 100644 --- a/examples/README.md +++ b/examples/README.md @@ -1,19 +1,113 @@ -# Getting Started +# CodeCarbon examples + +Runnable scripts and notebooks showing how to track emissions in real workloads. + +This README is the canonical index of this directory. The documentation site +mirrors it at [docs.codecarbon.io/latest/how-to/examples/](https://docs.codecarbon.io/latest/how-to/examples/). ## Setup -The following examples use Keras from TensorFlow 2.0. The dependencies can be installed as follows +Most examples need only CodeCarbon itself: + +```bash +pip install codecarbon +``` + +Examples that train or run models need extra libraries (TensorFlow, PyTorch, +scikit-learn). Installing them all is a large download, so prefer installing +only what the example you want actually imports: ```bash pip install -r examples/requirements-examples.txt ``` -## Examples -* [mnist.py](mnist.py): Usage using explicit `CO2Tracker` objects. -* [mnist_decorator.py](mnist_decorator.py): Using the `@track_co2` decorator. -* [mnist_callback.py](mnist_callback.py): Using Keras callbacks to save emissions after each epoch. -* [mnist-comet.py](mnist-comet.py): Using `CO2Tracker` with [`Comet`](https://www.comet.ml/site) for automatic experiment and emissions tracking. -* [api_call_demo.py](api_call_demo.py): Simplest demo to send computer emissions to CodeCarbon API. -* [api_call_debug.py](api_call_debug.py): Script to send computer emissions to CodeCarbon API. Made for debugging: debug log and send data every 20 seconds. -* [emi_channels.py](emi_channels.py): Print every channel exposed by the Windows Energy Meter Interface and the power each of them reports, to debug CPU power measurement on Windows. -* And many more in the [examples](../examples) folder. +The public API used throughout is `EmissionsTracker`, `OfflineEmissionsTracker`, +`track_emissions` and `OutputMethod`. + +## Start here + +| Example | What it shows | +|---|---| +| [`mnist_context_manager.py`](mnist_context_manager.py) | The recommended entry point: `with EmissionsTracker() as tracker:` | +| [`mnist.py`](mnist.py) | Explicit `EmissionsTracker` object with `start()` and `stop()` | +| [`mnist_decorator.py`](mnist_decorator.py) | The `@track_emissions` decorator on a function | +| [`notebook.ipynb`](notebook.ipynb) | Tracking inside a Jupyter notebook | +| [`notebooks/codecarbon_workshop.ipynb`](notebooks/codecarbon_workshop.ipynb) | Full hands-on workshop covering the whole feature set | +| [`print_hardware.py`](print_hardware.py) | Dump the CPU, GPU and RAM CodeCarbon detected on this machine | + +## Tracking parts of a run + +| Example | What it shows | +|---|---| +| [`mnist_callback.py`](mnist_callback.py) | A Keras callback that records emissions after each epoch | +| [`task_inference.py`](task_inference.py) | `start_task()` / `stop_task()` to attribute emissions to named phases | +| [`task_loop_same_task.py`](task_loop_same_task.py) | Repeatedly measuring the same named task | +| [`mnist_grid_search.py`](mnist_grid_search.py) | Emissions across a hyperparameter grid search | +| [`mnist_random_search.py`](mnist_random_search.py) | Emissions across a random search | + +## Models and inference + +| Example | What it shows | +|---|---| +| [`mnist-sklearn.py`](mnist-sklearn.py) | scikit-learn training | +| [`mnist_inference.py`](mnist_inference.py) | Measuring inference rather than training | +| [`bert_inference.py`](bert_inference.py) | BERT inference with task-level tracking | +| [`transformers_smollm2.py`](transformers_smollm2.py) | Hugging Face Transformers with SmolLM2 | +| [`local_llms.ipynb`](local_llms.ipynb) | Comparing local LLM runs | +| [`ollama_local_api.py`](ollama_local_api.py) | Tracking calls to a local Ollama server | +| [`pytorch-multigpu-example.py`](pytorch-multigpu-example.py) | Multi-GPU PyTorch training | +| [`intel_npu.py`](intel_npu.py) | Inference on an Intel NPU | + +## Sending results somewhere + +| Example | What it shows | +|---|---| +| [`logging_to_file.py`](logging_to_file.py) | Writing the CodeCarbon log to a file | +| [`logging_to_file_exclusive_run.py`](logging_to_file_exclusive_run.py) | File logging for a long-running exclusive run | +| [`logging_to_google_cloud.py`](logging_to_google_cloud.py) | Google Cloud Logging output | +| [`logfire_metrics.py`](logfire_metrics.py) | Logfire metrics output | +| [`prometheus_call.py`](prometheus_call.py) | Exporting metrics to Prometheus | +| [`boamps_output.py`](boamps_output.py) | Writing output in [BoAmps](https://github.com/Boavizta/BoAmps) format | +| [`mnist-comet.py`](mnist-comet.py) | Pairing tracking with [Comet](https://www.comet.com) experiment tracking | +| [`api_call_demo.py`](api_call_demo.py) | Minimal example sending data to the CodeCarbon API | +| [`api_call_debug.py`](api_call_debug.py) | Same, with debug logging and a 20-second interval, for troubleshooting | + +## Configuration and process patterns + +| Example | What it shows | +|---|---| +| [`pue.py`](pue.py) | Applying a datacenter Power Usage Effectiveness multiplier | +| [`wue.py`](wue.py) | Applying a Water Usage Effectiveness factor | +| [`multithread.py`](multithread.py) | Several `OfflineEmissionsTracker` instances across threads | +| [`command_line_tool.py`](command_line_tool.py) | Wrapping an external binary run via `subprocess` (machine-level, not process-level) | +| [`full_cpu.py`](full_cpu.py) | Saturating the CPU to produce a clear measurement signal | + +## Hardware debugging + +Use these when the numbers look wrong and you need to see what CodeCarbon is +reading from your hardware. + +| Example | What it shows | +|---|---| +| [`compare_cpu_load_and_RAPL.py`](compare_cpu_load_and_RAPL.py) | Compare the CPU-load estimate against RAPL ground truth | +| [`compare_cpu_load_and_RAPL.ipynb`](compare_cpu_load_and_RAPL.ipynb) | The same comparison as an annotated notebook with plots | +| [`emi_channels.py`](emi_channels.py) | Print every Windows Energy Meter Interface channel and the power it reports | +| [`rapl/`](rapl/) | RAPL domain inspection and DRAM-handling diagnostics | +| [`test_rapl_calculus.sh`](test_rapl_calculus.sh) | Shell script reading raw RAPL counters around a `full_cpu.py` run | + +## Clusters + +| Example | What it shows | +|---|---| +| [`slurm_rocm/`](slurm_rocm/) | SLURM batch scripts and AMD ROCm/`amdsmi` examples | + +See also the [SLURM how-to guide](https://docs.codecarbon.io/latest/how-to/slurm/). + +## Sample data + +[`emissions.csv`](emissions.csv) is a sample output file. Use it to try the +local dashboard without running a workload first: + +```bash +carbonboard --filepath="examples/emissions.csv" --port=8050 +``` diff --git a/mkdocs.yml b/mkdocs.yml index 03eee181a..e8aa19047 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -199,7 +199,5 @@ nav: - API Reference: reference/api.md - Output Formats: reference/output.md - CLI Reference: reference/cli.md - - Changelog: reference/changelog.md - - Deprecations & Migrations: reference/deprecations.md - Track GenAI API Calls (EcoLogits) ↗: https://ecologits.ai/latest/?utm_source=codecarbon&utm_medium=docs - Join Our Discord 💬 ↗: https://discord.gg/GS9js2XkJR diff --git a/pyproject.toml b/pyproject.toml index 26a360338..cae1630ca 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,10 +5,28 @@ build-backend = "setuptools.build_meta" [project] name = "codecarbon" dynamic = ["version"] +description = "Track and reduce the CO2 emissions of your computing" readme = "README.md" requires-python = ">=3.10" license = "MIT" license-files = ["LICENSE"] +keywords = [ + "carbon", + "co2", + "emissions", + "carbon-footprint", + "sustainability", + "green-ai", + "green-computing", + "energy", + "energy-consumption", + "power-consumption", + "rapl", + "gpu", + "machine-learning", + "mlops", + "monitoring", +] authors = [ { name = "Mila" }, { name = "DataForGood" }, @@ -17,12 +35,24 @@ authors = [ { name = "Haverford College" }, ] classifiers = [ + "Development Status :: 5 - Production/Stable", + "Environment :: Console", + "Intended Audience :: Developers", + "Intended Audience :: Science/Research", + "Intended Audience :: System Administrators", "Natural Language :: English", + "Operating System :: POSIX :: Linux", + "Operating System :: MacOS", + "Operating System :: Microsoft :: Windows", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.14", + "Topic :: Scientific/Engineering :: Artificial Intelligence", + "Topic :: System :: Monitoring", + "Topic :: System :: Benchmark", + "Topic :: Software Development :: Libraries :: Python Modules", ] dependencies = [ "arrow", @@ -158,6 +188,9 @@ version_pattern = "MAJOR.MINOR.PATCH[_TAGNUM]" "codecarbon/_version.py" = [ '^__version__ = "{version}"$', ] +"CITATION.cff" = [ + '^version: {version}$', +] [tool.pytest.ini_options] pythonpath = "." From b9f82e8f7b8595c60d66c83e8beece66113e82a2 Mon Sep 17 00:00:00 2001 From: David Berenstein Date: Wed, 12 Aug 2026 16:35:22 +0200 Subject: [PATCH 4/9] build: bump zensical to 0.0.53 Docs build and the internal link check pass on the new version. 0.0.53 still emits no Open Graph tags of its own, so the `overrides/main.html` template added in this branch stays necessary. Co-Authored-By: Claude Opus 5 (1M context) --- uv.lock | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/uv.lock b/uv.lock index f37050957..31bcb75e7 100644 --- a/uv.lock +++ b/uv.lock @@ -3535,7 +3535,7 @@ wheels = [ [[package]] name = "zensical" -version = "0.0.52" +version = "0.0.53" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "click" }, @@ -3547,20 +3547,20 @@ dependencies = [ { name = "pyyaml" }, { name = "tomli" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/22/53/f3657dc0ed7666b29cededfeb424b28b8cf1f6ca75f7066af76fca8c1bcf/zensical-0.0.52.tar.gz", hash = "sha256:b11b79dd1bb7da4c1a5293cbc5a2f4394d980bf2bf1c4c326062bc5ddcf2a2e8", size = 3991761, upload-time = "2026-07-30T10:22:51.45Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/54/32/e143d094f832d8a2de6b56f2e20c40437d06376be69117d8e725d00e22c4/zensical-0.0.52-cp310-abi3-macosx_10_12_x86_64.whl", hash = "sha256:7e5fa1df686af8ef223d16637fd31fe2ab248a8b40556037c50af1102b05f5ed", size = 12839791, upload-time = "2026-07-30T10:22:24.867Z" }, - { url = "https://files.pythonhosted.org/packages/13/e6/746c00830a4149826190f99e182cf5642745978d8702372e8370c7ec8a12/zensical-0.0.52-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:20117f935e23900411e5d03ef1d15b3e5ef3f8730d9096a49eb08754fef1f2d4", size = 12723378, upload-time = "2026-07-30T10:22:27.057Z" }, - { url = "https://files.pythonhosted.org/packages/9c/5b/904fa8d57dd27682dcd2a8c683661f0d6e12c111d150b34c7d568ac80018/zensical-0.0.52-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2d889b32121fa43061a902c49d976353a674ae566803ac0ea5d41f01aa5da131", size = 13173818, upload-time = "2026-07-30T10:22:29.168Z" }, - { url = "https://files.pythonhosted.org/packages/8c/28/f124a2a512ae0d5d9d158b1cf31798e3191eb9a21ac8ccfdc9cc38e09a7a/zensical-0.0.52-cp310-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:61b62d254d47e82fb687bc8a74a1f0220a900a0f3ca4bb6c93eab51ca7c8391a", size = 13111975, upload-time = "2026-07-30T10:22:31.418Z" }, - { url = "https://files.pythonhosted.org/packages/10/09/bdcb062263005e0430a532e72ea32a24955208ae92191f02d38abd58b793/zensical-0.0.52-cp310-abi3-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9d09e7fd0d80418639482212fbce19d09877cbef92a4122030e5d19b32dbea08", size = 13498189, upload-time = "2026-07-30T10:22:33.42Z" }, - { url = "https://files.pythonhosted.org/packages/5a/60/7c3d6cee180a65e06a22de8a143d9ae4791ebae5278fd1abd2977fdc69b0/zensical-0.0.52-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6df854d07f5d89a47f37f661058bc3f95efc64d45b0d5500ea46821244f69c16", size = 13145652, upload-time = "2026-07-30T10:22:35.871Z" }, - { url = "https://files.pythonhosted.org/packages/1d/76/e794e77745017652344463a3c4ba286073ca26a43b0c86dcb40ae0dac0b5/zensical-0.0.52-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:188e15376b3718e6e880751c4014e394b59f798329bc21e39c950cc58254a32c", size = 13349087, upload-time = "2026-07-30T10:22:38.005Z" }, - { url = "https://files.pythonhosted.org/packages/a2/ba/6a38f9c29392c1d5729b25d24c8526d3f86c8133bbd2d8481acf1d1bdc78/zensical-0.0.52-cp310-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:40cde85bf35901a4c14a56349502b6d3c754d4386de6b887d498cd7269757e00", size = 13385257, upload-time = "2026-07-30T10:22:40.785Z" }, - { url = "https://files.pythonhosted.org/packages/02/b6/c82317c747ec39e1557aeb2b762087bc22437f80a07a16df21df666ac250/zensical-0.0.52-cp310-abi3-musllinux_1_2_i686.whl", hash = "sha256:d26c29272ce5bad16564a19ecdfd43bbd9b41568a57b923e8b710359de633322", size = 13550906, upload-time = "2026-07-30T10:22:42.933Z" }, - { url = "https://files.pythonhosted.org/packages/c6/77/b7c83ddced2887b03113c322036f74a2d519ae5599cbb2662e8d7f5c7e2c/zensical-0.0.52-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:5a1e1c6c99ae50e98cac957bb48719aecec57aa47899462acd65b2fa9afcbcdd", size = 13485819, upload-time = "2026-07-30T10:22:45.001Z" }, - { url = "https://files.pythonhosted.org/packages/b8/88/fcaee358b7e9d380ccdeb6d3a434b24cd6df6b48ff077bdda0046d8fb6c0/zensical-0.0.52-cp310-abi3-win32.whl", hash = "sha256:4ef40c8d2e8fc84886a28704667e38b7f89663cff32a57db5e909a26cf5cf66a", size = 12410758, upload-time = "2026-07-30T10:22:47.28Z" }, - { url = "https://files.pythonhosted.org/packages/9d/1c/d410a93763cafb8827e4e318bad369b84068047c4d658b511c9307104a62/zensical-0.0.52-cp310-abi3-win_amd64.whl", hash = "sha256:dd904e316f1cdc4fee707febdd85d0ac13f742a8ba14da9f9a4dacb8603fe480", size = 12662400, upload-time = "2026-07-30T10:22:49.503Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/bc/8b/d916d8226738421a847f039f71278fd07789744c32e9b40abcfa8b849ad8/zensical-0.0.53.tar.gz", hash = "sha256:61672d3e6389822b5738e099816dbc07416ea84db67c2b1cb7e6ea977d2e04d7", size = 3988318, upload-time = "2026-08-04T14:08:54.721Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9d/53/5db8c8e5a257db9a5fff0b77c8e05783283d6aaf42c05e34577f6b59f5d0/zensical-0.0.53-cp310-abi3-macosx_10_12_x86_64.whl", hash = "sha256:292cf9c7c323a50c6e3515d334ca08d9dcc517ce6d9d8ad1cd94d22befab1f56", size = 12835291, upload-time = "2026-08-04T14:08:16.746Z" }, + { url = "https://files.pythonhosted.org/packages/33/73/49a64c2c44aec251336a1cedcccbec7ba3d3eba9dd75d52ed24c09217d86/zensical-0.0.53-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:0f4c1219c534d3cccc0b86093748dc009e0e9d80d4dad8d65e2150c846aa1123", size = 12719959, upload-time = "2026-08-04T14:08:20.279Z" }, + { url = "https://files.pythonhosted.org/packages/d8/3a/2c08429f7c725d1a40d158b84d6aca4b5c4320d09a0a313e875d7dd3bfe5/zensical-0.0.53-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ca63b952b4961461b4376d61603adc2bf9d81b4df4946b2f27e20b2726f881f", size = 13169416, upload-time = "2026-08-04T14:08:23.474Z" }, + { url = "https://files.pythonhosted.org/packages/41/bc/ed057082989645d5ad3245bdf0b14c30334a315f866552c794c2413cf92f/zensical-0.0.53-cp310-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:34f41b7f37a0430a1378ac13d9a72513fcc53db676c124378cf63cc6f6e22713", size = 13099720, upload-time = "2026-08-04T14:08:26.521Z" }, + { url = "https://files.pythonhosted.org/packages/a4/54/859cf2267ef853ff20eee2af37d898071f821bf30ec3df7d73061b391c78/zensical-0.0.53-cp310-abi3-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:30ed22e9fcedda71888d9fe84f4fdb1aadd3b66cdb0223716f1eecce9ae22b07", size = 13482295, upload-time = "2026-08-04T14:08:29.618Z" }, + { url = "https://files.pythonhosted.org/packages/35/94/f73744d9f4b6107e2740aad58214285b84d4cf0997cde36bced43089b3d0/zensical-0.0.53-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10b0cb72861b14bd985bc5ad0203c35b1da7a19c87c194df3189fab7a910db04", size = 13140985, upload-time = "2026-08-04T14:08:32.731Z" }, + { url = "https://files.pythonhosted.org/packages/e0/ef/7557d859e25e4a74214d718a1528f2a123ff9d84823b49b32df9bc41ef17/zensical-0.0.53-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:15e9813f0f59db6cf1316414301139d030f1690b68af645f1bf68d78bc3defe0", size = 13344554, upload-time = "2026-08-04T14:08:35.924Z" }, + { url = "https://files.pythonhosted.org/packages/0b/d9/3a1011bd4390e85a6f602afca6ff8b862454415a800e7b41471dadd9e6b1/zensical-0.0.53-cp310-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:c203493598d6cad890d7cb48f9d75693f648fe0d2347b2f147406a99fd7bb101", size = 13373180, upload-time = "2026-08-04T14:08:39.384Z" }, + { url = "https://files.pythonhosted.org/packages/5e/ee/f4faf3d66d1e854afa43fa5354c1e0c8414af3fc5c563233a3ca7f10e494/zensical-0.0.53-cp310-abi3-musllinux_1_2_i686.whl", hash = "sha256:ec34844b3bc1855f5c10b99efbeebd27abcd983a9144dbad965609e65915c050", size = 13531133, upload-time = "2026-08-04T14:08:42.679Z" }, + { url = "https://files.pythonhosted.org/packages/5e/98/4a0272bb79bdd326714e552f685d58f31a501ffd49bdc17ae187e92b2581/zensical-0.0.53-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:e016062c3299c84be811848d1e81ad0f3f711615f0bed87bb0a1b47f6968a5a4", size = 13480141, upload-time = "2026-08-04T14:08:45.97Z" }, + { url = "https://files.pythonhosted.org/packages/e1/ab/8cbceea1e7f4d6d2ac078a0c34ccd06f1419401248d22f6d6ded4ac9a443/zensical-0.0.53-cp310-abi3-win32.whl", hash = "sha256:abb0af33bb646f15224045baa6c4118b59a2c9c3f80d7cd48edd66ee961c1985", size = 12410234, upload-time = "2026-08-04T14:08:48.871Z" }, + { url = "https://files.pythonhosted.org/packages/f2/ac/65f0ced38274b6c1073a4b1c52ea41b8b43e7e972f5e3979c2f2aca5cc46/zensical-0.0.53-cp310-abi3-win_amd64.whl", hash = "sha256:8b609bc89717b6f276774651ea3a41df21b4813929d2a206ee161a294dc28cd1", size = 12646224, upload-time = "2026-08-04T14:08:51.945Z" }, ] [[package]] From e6383ef7695d277ff22e5ec239b83110951094ee Mon Sep 17 00:00:00 2001 From: David Berenstein Date: Wed, 12 Aug 2026 16:40:43 +0200 Subject: [PATCH 5/9] test: skip the README link check when the file is absent The wheel-validation job runs the test suite against an installed wheel without the rest of the repository, so `examples/README.md` does not exist there and the check failed with FileNotFoundError. Co-Authored-By: Claude Opus 5 (1M context) --- tests/test_readme_links.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/test_readme_links.py b/tests/test_readme_links.py index 0d2f5d691..3614c3b44 100644 --- a/tests/test_readme_links.py +++ b/tests/test_readme_links.py @@ -14,6 +14,9 @@ @pytest.mark.parametrize("md_file", CHECKED_FILES, ids=lambda p: str(p.name)) def test_relative_links_exist(md_file): + if not md_file.exists(): + # The wheel-validation job runs the tests without the rest of the repository. + pytest.skip(f"{md_file} is not present in this checkout") missing = [ target for target in LINK_RE.findall(md_file.read_text()) From a1b2755179a8894bddf257ab55b9a6e5c820d0e5 Mon Sep 17 00:00:00 2001 From: David Berenstein Date: Wed, 12 Aug 2026 17:56:58 +0200 Subject: [PATCH 6/9] test: cover the RAPL permission help and the task decorator Co-Authored-By: Claude Opus 5 (1M context) --- tests/test_emissions_tracker.py | 33 ++++++++++++++++++++++++++ tests/test_rapl_permissions.py | 41 ++++++++++++++++++++++++++++++++- 2 files changed, 73 insertions(+), 1 deletion(-) diff --git a/tests/test_emissions_tracker.py b/tests/test_emissions_tracker.py index 8ab12e5d8..4afb820ca 100644 --- a/tests/test_emissions_tracker.py +++ b/tests/test_emissions_tracker.py @@ -16,6 +16,7 @@ EmissionsTracker, OfflineEmissionsTracker, track_emissions, + track_task_emissions, ) from codecarbon.external.geography import CloudMetadata from codecarbon.output import BoAmpsOutput, CodeCarbonAPIOutput, OutputMethod @@ -609,6 +610,38 @@ def dummy_train_model(): dummy_train_model() self.verify_output_file(self.emissions_file_path, 2) + def test_track_task_emissions_decorator( + self, + mock_cli_setup, + mock_log_values, + mocked_get_gpu_details, + mocked_env_cloud_details, + mocked_get_gpu_utilization_list, + mocked_is_gpu_details_available, + mocked_is_nvidia_system, + ): + tracker = OfflineEmissionsTracker( + country_iso_code="USA", + measure_power_secs=1, + output_dir=self.temp_path, + experiment_id="test", + ) + tracker.start() + + @track_task_emissions(tracker=tracker, task_name="training") + def dummy_train_model(): + heavy_computation(run_time_secs=1) + return 42 + + # The decorator is transparent: same name, same return value. + self.assertEqual("dummy_train_model", dummy_train_model.__name__) + self.assertEqual(42, dummy_train_model()) + + # ...and the task has been measured on the tracker we passed in. + self.assertIn("training", tracker._tasks) + self.assertGreater(tracker._tasks["training"].emissions_data.duration, 0) + tracker.stop() + def test_offline_tracker_country_name( self, mock_cli_setup, diff --git a/tests/test_rapl_permissions.py b/tests/test_rapl_permissions.py index 4c522435d..f44579e9a 100644 --- a/tests/test_rapl_permissions.py +++ b/tests/test_rapl_permissions.py @@ -2,10 +2,49 @@ import os import stat import sys +from unittest import mock import pytest -from codecarbon.core.cpu import IntelRAPL, is_rapl_available +from codecarbon.core.cpu import ( + RAPL_PERMISSION_HELP, + IntelRAPL, + _create_warn_function, + is_rapl_available, +) + + +def test_permission_warning_is_emitted_once(caplog): + """The scan hits every domain: warn once, then stay quiet on the debug channel.""" + warn = _create_warn_function() + + with caplog.at_level(logging.DEBUG, logger="codecarbon"): + warn("/sys/class/powercap/intel-rapl:0/energy_uj") + warn("/sys/class/powercap/intel-rapl:1/energy_uj") + + levels = [r.levelno for r in caplog.records] + assert levels == [logging.WARNING, logging.DEBUG] + # Both messages tell the user how to fix the permissions. + for record in caplog.records: + assert RAPL_PERMISSION_HELP in record.getMessage() + + +def test_unreadable_domain_is_skipped_with_actionable_warning(caplog): + rapl = IntelRAPL.__new__(IntelRAPL) # no filesystem scan, we test the check alone + + with ( + mock.patch("builtins.open", side_effect=PermissionError("nope")), + caplog.at_level(logging.WARNING, logger="codecarbon"), + ): + readable, is_main = rapl._validate_domain_readable( + "/sys/class/powercap/intel-rapl:0/energy_uj", + "/sys/class/powercap/intel-rapl:0", + "package-0", + ) + + # An unreadable domain is skipped, and never reported as the main one. + assert (readable, is_main) == (False, False) + assert RAPL_PERMISSION_HELP in caplog.text @pytest.mark.skipif(not sys.platform.lower().startswith("lin"), reason="requires Linux") From cd1f26d6ca3ac7f1ab2bd68714197412dad79bcc Mon Sep 17 00:00:00 2001 From: David Berenstein Date: Thu, 13 Aug 2026 07:37:47 +0200 Subject: [PATCH 7/9] docs: drop CHANGELOG.md, point releases at GitHub The changelog duplicated the GitHub releases page, which both pyproject files already declare as the canonical Changelog URL. Keeping it meant every PR needed a bullet in a file that only existed on one branch. The deprecations table was the one thing releases cannot carry, so it moves to docs/reference/deprecations.md and the two docs pages that linked into the changelog anchor now point there. Also pin black and ruff to the versions CI's pre-commit hooks use, and set black's target-version explicitly. The inferred target outran the interpreter, so black was skipping its AST safety check and local formatting runs rewrote ~120 files that CI considered clean. Co-Authored-By: Claude Opus 5 (1M context) --- CHANGELOG.md | 175 --------------------------------- docs/how-to/configuration.md | 2 +- docs/reference/deprecations.md | 75 ++++++++++++++ docs/reference/output.md | 2 +- mkdocs.yml | 1 + pyproject.toml | 10 +- uv.lock | 4 +- 7 files changed, 88 insertions(+), 181 deletions(-) delete mode 100644 CHANGELOG.md create mode 100644 docs/reference/deprecations.md diff --git a/CHANGELOG.md b/CHANGELOG.md deleted file mode 100644 index 15af798f2..000000000 --- a/CHANGELOG.md +++ /dev/null @@ -1,175 +0,0 @@ -# Changelog - -User-visible changes in CodeCarbon: new features, behaviour changes, deprecations, -removals, and anything that requires action on your side. For the full list of merged -pull requests, see the -[GitHub releases](https://github.com/mlco2/codecarbon/releases). - -Deprecated features and their replacements are listed under -[Deprecations and migrations](#deprecations-and-migrations) at the end of this file. - -## Unreleased - -### Added - -- CPU power estimation now recognises the Apple M2, M3 and M4 chip families, so - measurements on recent Apple Silicon no longer fall back to the default TDP - ([#1256](https://github.com/mlco2/codecarbon/pull/1256)). - -### Changed - -- Updated the RAPL access instructions and the Ansible deployment role. - -## 3.3.0 — 2026-08-04 - -### Added - -- Windows support through the Energy Meter Interface (EMI), including multi-die - machines. Windows measurements are no longer estimation-only where EMI is available - ([#1263](https://github.com/mlco2/codecarbon/pull/1263)). - -### Changed - -- The API client now raises on HTTP errors instead of silently returning `None`. - If you call `ApiClient` directly, expect exceptions where you previously got `None` - and had to check for it ([#1277](https://github.com/mlco2/codecarbon/pull/1277)). - -### Fixed - -- Documented that `tracking_mode` affects RAM and CPU measurement, not only the - process scope ([#1163](https://github.com/mlco2/codecarbon/pull/1163)). - -## 3.2.9 — 2026-07-19 - -### Changed - -- Substantially faster startup and lower measurement overhead: the tracker defers - initialisation, caches hardware detection between runs, skips redundant GPU detail - queries while monitoring, and reads only the header row when validating an existing - CSV - ([#1251](https://github.com/mlco2/codecarbon/pull/1251), - [#1252](https://github.com/mlco2/codecarbon/pull/1252), - [#1254](https://github.com/mlco2/codecarbon/pull/1254), - [#1264](https://github.com/mlco2/codecarbon/pull/1264), - [#1235](https://github.com/mlco2/codecarbon/pull/1235)). -- When using the hosted API, the run is only created on the first emission upload - rather than at tracker start, so aborted runs no longer show up as empty runs - ([#1253](https://github.com/mlco2/codecarbon/pull/1253)). - -### Fixed - -- Dashboard: run sums now exclude filtered-out runs, the date picker month arrows are - aligned, and hovering the emissions chart no longer errors - ([#1229](https://github.com/mlco2/codecarbon/pull/1229), - [#1233](https://github.com/mlco2/codecarbon/pull/1233), - [#1230](https://github.com/mlco2/codecarbon/pull/1230)). - -## 3.2.8 — 2026-06-07 - -### Added - -- Optional, opt-in telemetry ([#1171](https://github.com/mlco2/codecarbon/pull/1171)). -- BoAmps output format, with documentation and an example - ([#1154](https://github.com/mlco2/codecarbon/pull/1154)). -- Documentation of what the hardware measurement actually covers - ([#1204](https://github.com/mlco2/codecarbon/pull/1204)). - -### Changed - -- The web dashboard front end is now a plain React application - ([#1076](https://github.com/mlco2/codecarbon/pull/1076)). - -### Deprecated - -- The `save_to_file`, `save_to_api`, `save_to_logger`, `save_to_prometheus` and - `save_to_logfire` parameters. Use `output_methods=[OutputMethod.CSV, ...]` instead. - They now emit a `DeprecationWarning`. - -### Fixed - -- `codecarbon monitor` in offline mode now uses `OfflineEmissionsTracker` - ([#1202](https://github.com/mlco2/codecarbon/pull/1202)). -- macOS: CPU detection through `psutil` ([#1211](https://github.com/mlco2/codecarbon/pull/1211)) - and importing `amdsmi` when `libamd_smi` is absent - ([#1201](https://github.com/mlco2/codecarbon/pull/1201)). -- Carbon intensity lookup for Nordic regions with the online tracker - ([#1224](https://github.com/mlco2/codecarbon/pull/1224)). -- The CLI now warns when authentication validation falls back to the API - ([#1209](https://github.com/mlco2/codecarbon/pull/1209)). - -Changes released before 3.2.8 are listed on the -[GitHub releases page](https://github.com/mlco2/codecarbon/releases). - ---- - -## Deprecations and migrations - -Everything in CodeCarbon that still works but is on its way out, when it was -deprecated, when it is scheduled to be removed, and what to use instead. -User-visible changes per release are listed above. - -| Deprecated | Since | Removal | Replacement | -|---|---|---|---| -| `codecarbon[viz-legacy]` extra | 3.3.0 | 4.0.0 | `codecarbon[carbonboard]` | -| `save_to_file`, `save_to_api`, `save_to_logger`, `save_to_prometheus`, `save_to_logfire` parameters | 3.2.8 | Not scheduled | `output_methods=[...]` | -| `co2_signal_api_token` parameter and config key | 3.1.1 | Not scheduled | `electricitymaps_api_token` | - -### `viz-legacy` extra - -The old Dash-based dashboard extra is replaced by `carbonboard`. - -```bash -# before -pip install codecarbon[viz-legacy] - -# after -pip install codecarbon[carbonboard] -``` - -See [Visualize Emissions](https://docs.codecarbon.io/latest/how-to/visualize/) for how to run it. - -### `save_to_*` parameters - -Passing any `save_to_*` flag raises a `DeprecationWarning`. Pass the outputs you want -as a list instead. - -```python -# before -EmissionsTracker(save_to_file=True, save_to_logger=True) - -# after -from codecarbon.output_methods.base_output import OutputMethod - -EmissionsTracker(output_methods=[OutputMethod.CSV, OutputMethod.LOGGER]) -``` - -See [Output Formats](https://docs.codecarbon.io/latest/reference/output/) for the full list of output methods. - -### `co2_signal_api_token` - -The CO2 Signal API became part of Electricity Maps. The old parameter and config key -still work and are read as a fallback, but they log a warning. - -```python -# before -EmissionsTracker(co2_signal_api_token="...") - -# after -EmissionsTracker(electricitymaps_api_token="...") -``` - -```ini -# .codecarbon.config — before -[codecarbon] -co2_signal_api_token = ... - -# .codecarbon.config — after -[codecarbon] -electricitymaps_api_token = ... -``` - -### For maintainers - -This table is the checklist for the next major release: everything with a removal -version scheduled for it must be removed, and every "Not scheduled" row should get a -decision before the release is cut. diff --git a/docs/how-to/configuration.md b/docs/how-to/configuration.md index d49828fb5..500bd4bda 100644 --- a/docs/how-to/configuration.md +++ b/docs/how-to/configuration.md @@ -121,7 +121,7 @@ EmissionsTracker(electricitymaps_api_token="your-token-here") The old parameter name `co2_signal_api_token` still works for backward compatibility but is deprecated and will be removed in a future version. Use `electricitymaps_api_token` instead. See the - [deprecations list](https://github.com/mlco2/codecarbon/blob/master/CHANGELOG.md#deprecations-and-migrations). + [deprecations list](https://docs.codecarbon.io/latest/reference/deprecations/). ## Tracking Mode diff --git a/docs/reference/deprecations.md b/docs/reference/deprecations.md new file mode 100644 index 000000000..e76bbe032 --- /dev/null +++ b/docs/reference/deprecations.md @@ -0,0 +1,75 @@ +# Deprecations and migrations + +Everything in CodeCarbon that still works but is on its way out: when it was +deprecated, when it is scheduled to be removed, and what to use instead. + +User-visible changes per release are on the +[GitHub releases page](https://github.com/mlco2/codecarbon/releases). + +| Deprecated | Since | Removal | Replacement | +|---|---|---|---| +| `codecarbon[viz-legacy]` extra | 3.3.0 | 4.0.0 | `codecarbon[carbonboard]` | +| `save_to_file`, `save_to_api`, `save_to_logger`, `save_to_prometheus`, `save_to_logfire` parameters | 3.2.8 | Not scheduled | `output_methods=[...]` | +| `co2_signal_api_token` parameter and config key | 3.1.1 | Not scheduled | `electricitymaps_api_token` | + +## `viz-legacy` extra + +The old Dash-based dashboard extra is replaced by `carbonboard`. + +```bash +# before +pip install codecarbon[viz-legacy] + +# after +pip install codecarbon[carbonboard] +``` + +See [Visualize Emissions](../how-to/visualize.md) for how to run it. + +## `save_to_*` parameters + +Passing any `save_to_*` flag raises a `DeprecationWarning`. Pass the outputs you want +as a list instead. + +```python +# mktestdocs: skip +# before +EmissionsTracker(save_to_file=True, save_to_logger=True) + +# after +from codecarbon.output_methods.base_output import OutputMethod + +EmissionsTracker(output_methods=[OutputMethod.CSV, OutputMethod.LOGGER]) +``` + +See [Output Formats](output.md) for the full list of output methods. + +## `co2_signal_api_token` + +The CO2 Signal API became part of Electricity Maps. The old parameter and config key +still work and are read as a fallback, but they log a warning. + +```python +# mktestdocs: skip +# before +EmissionsTracker(co2_signal_api_token="...") + +# after +EmissionsTracker(electricitymaps_api_token="...") +``` + +```ini +# .codecarbon.config — before +[codecarbon] +co2_signal_api_token = ... + +# .codecarbon.config — after +[codecarbon] +electricitymaps_api_token = ... +``` + +## For maintainers + +This table is the checklist for the next major release: everything with a removal +version scheduled for it must be removed, and every "Not scheduled" row should get a +decision before the release is cut. diff --git a/docs/reference/output.md b/docs/reference/output.md index 8bd97075a..64122240c 100644 --- a/docs/reference/output.md +++ b/docs/reference/output.md @@ -23,7 +23,7 @@ It can also be set in the config file as a comma-separated string, e.g. `save_to_prometheus` and `save_to_logfire` parameters are deprecated and will be removed in a future version. Use `output_methods` instead. When `output_methods` is provided, the `save_to_*` flags are ignored. See the - [deprecations list](https://github.com/mlco2/codecarbon/blob/master/CHANGELOG.md#deprecations-and-migrations). + [deprecations list](https://docs.codecarbon.io/latest/reference/deprecations/). ## CSV diff --git a/mkdocs.yml b/mkdocs.yml index e8aa19047..8b7c9a069 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -198,6 +198,7 @@ nav: - Reference: - API Reference: reference/api.md - Output Formats: reference/output.md + - Deprecations and Migrations: reference/deprecations.md - CLI Reference: reference/cli.md - Track GenAI API Calls (EcoLogits) ↗: https://ecologits.ai/latest/?utm_source=codecarbon&utm_medium=docs - Join Our Discord 💬 ↗: https://discord.gg/GS9js2XkJR diff --git a/pyproject.toml b/pyproject.toml index cae1630ca..ecd19e36a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -114,8 +114,8 @@ dev = [ "taskipy", "bumpver", "pre-commit", - "ruff", - "black", + "ruff==0.16.1", + "black==26.5.1", # keep in sync with the black rev in .pre-commit-config.yaml "mypy", "pytest", "requests", @@ -192,6 +192,12 @@ version_pattern = "MAJOR.MINOR.PATCH[_TAGNUM]" '^version: {version}$', ] +[tool.black] +# Pin the target rather than letting black infer it from requires-python: the +# inferred upper bound outran the interpreter, and black then skips its AST +# safety check. Keep this at the oldest supported Python. +target-version = ["py310"] + [tool.pytest.ini_options] pythonpath = "." diff --git a/uv.lock b/uv.lock index 31bcb75e7..d3e533e6c 100644 --- a/uv.lock +++ b/uv.lock @@ -514,7 +514,7 @@ provides-extras = ["carbonboard", "viz-legacy"] [package.metadata.requires-dev] dev = [ - { name = "black" }, + { name = "black", specifier = "==26.5.1" }, { name = "bumpver" }, { name = "jsonschema" }, { name = "logfire", specifier = ">=1.0.1" }, @@ -526,7 +526,7 @@ dev = [ { name = "requests" }, { name = "requests-mock" }, { name = "responses" }, - { name = "ruff" }, + { name = "ruff", specifier = "==0.16.1" }, { name = "scikit-learn" }, { name = "taskipy" }, ] From 9a76404af4a5d5eec0bd832c988a4bdf7a4adf13 Mon Sep 17 00:00:00 2001 From: David Berenstein Date: Thu, 13 Aug 2026 07:41:24 +0200 Subject: [PATCH 8/9] docs(api): record why the token lookup value is SHA-256 CodeQL reports py/weak-sensitive-data-hashing on generate_lookup_value. The value is a database index, not a credential: authentication is bcrypt against hashed_token, and the derivation cannot be changed because it is computed from plaintext tokens that are never stored. Write that down so the alert is not re-litigated, and correct get_api_key_hash's return annotation, which claimed str while returning bcrypt's bytes. Co-Authored-By: Claude Opus 5 (1M context) --- .../carbonserver/api/infra/api_key_utils.py | 25 ++++++++++++++++--- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/carbonserver/carbonserver/api/infra/api_key_utils.py b/carbonserver/carbonserver/api/infra/api_key_utils.py index 4ecfe2e08..d71a7de13 100644 --- a/carbonserver/carbonserver/api/infra/api_key_utils.py +++ b/carbonserver/carbonserver/api/infra/api_key_utils.py @@ -13,8 +13,13 @@ def generate_api_key() -> str: return prefixed_api_key -def get_api_key_hash(api_key: str) -> str: - """Get the hash of the api key""" +def get_api_key_hash(api_key: str) -> bytes: + """Get the hash of the api key. + + Returns bcrypt's own ``bytes`` output. It is stored in a ``String`` column and + comes back from the database as ``str``, which is what ``verify_api_key`` + expects — hence the asymmetric annotations. + """ return bcrypt.hashpw( api_key.encode(), bcrypt.gensalt(), @@ -32,7 +37,19 @@ def verify_api_key(plain_api_key: str, hashed_api_key: str) -> bool: def generate_lookup_value(api_key: str) -> str: - # Generate a SHA-256 hash of the API key + """Derive the non-secret database index for an API key. + + This is a lookup shortcut, not a credential. It narrows a token lookup to a + handful of candidate rows; authentication is always ``verify_api_key`` + (bcrypt) against the stored ``hashed_token``. Collisions are expected and the + caller iterates over every candidate, so 8 hex characters is deliberate. + + SHA-256 is correct here and must not be swapped for an HMAC or a KDF: static + analysis flags this line as weak credential hashing (``py/weak-sensitive-data- + hashing``), but the value authenticates nothing. Changing the derivation would + also be unrecoverable — it is computed from plaintext tokens that are never + stored, so existing rows could not be backfilled and every issued API key + would stop resolving. + """ sha256_hash = hashlib.sha256(api_key.encode()).hexdigest() - # Use the first 8 characters of the hash as a lookup value return sha256_hash[:8] From 759b5165a642e3ffa774443c3b84a1008d2ba446 Mon Sep 17 00:00:00 2001 From: David Berenstein Date: Thu, 13 Aug 2026 08:34:21 +0200 Subject: [PATCH 9/9] build: drop the black and ruff pins, keep the black target #1369 removes the taskipy black/ruff tasks entirely, which is the root-cause fix for the format drift: pinning a second toolchain to match pre-commit keeps two copies of the same version truth in sync by hand. Take the deletion there instead and leave these unpinned so the two branches do not conflict. [tool.black] target-version stays: the pre-commit black hook reads pyproject too, so that fix is orthogonal to #1369 and still wanted. Co-Authored-By: Claude Opus 5 (1M context) --- pyproject.toml | 4 ++-- uv.lock | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index ecd19e36a..2f4976758 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -114,8 +114,8 @@ dev = [ "taskipy", "bumpver", "pre-commit", - "ruff==0.16.1", - "black==26.5.1", # keep in sync with the black rev in .pre-commit-config.yaml + "ruff", + "black", "mypy", "pytest", "requests", diff --git a/uv.lock b/uv.lock index d3e533e6c..31bcb75e7 100644 --- a/uv.lock +++ b/uv.lock @@ -514,7 +514,7 @@ provides-extras = ["carbonboard", "viz-legacy"] [package.metadata.requires-dev] dev = [ - { name = "black", specifier = "==26.5.1" }, + { name = "black" }, { name = "bumpver" }, { name = "jsonschema" }, { name = "logfire", specifier = ">=1.0.1" }, @@ -526,7 +526,7 @@ dev = [ { name = "requests" }, { name = "requests-mock" }, { name = "responses" }, - { name = "ruff", specifier = "==0.16.1" }, + { name = "ruff" }, { name = "scikit-learn" }, { name = "taskipy" }, ]