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/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.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/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.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/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/.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/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/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/carbonserver/carbonserver/api/infra/api_key_utils.py b/carbonserver/carbonserver/api/infra/api_key_utils.py index 4ecfe2e08..d2a6556b3 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,9 @@ 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 + """Non-secret lookup index, not a credential: authentication is always + ``verify_api_key`` (bcrypt). Collisions are expected and iterated over. + """ + # codeql[py/weak-sensitive-data-hashing] sha256_hash = hashlib.sha256(api_key.encode()).hexdigest() - # Use the first 8 characters of the hash as a lookup value return sha256_hash[:8] 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..adacfa5ad 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() @@ -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 """ diff --git a/docs/explanation/accuracy.md b/docs/explanation/accuracy.md new file mode 100644 index 000000000..051fa6a38 --- /dev/null +++ b/docs/explanation/accuracy.md @@ -0,0 +1,182 @@ +# 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#which-backend-gets-chosen). + +| 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`. Two different curves, [selected by `tracking_mode`](methodology.md#the-two-cpu_load-models) | See the profiling results below: in **machine mode**, on the machines profiled, the estimate deviated from RAPL by roughly −60% to +90% depending on CPU and load point. Process mode is uncharacterised | Fallback when no CPU counter is available | +| Default watts per thread | Estimates from thread count alone | Not characterised; this is the least accurate path | Last resort, when the CPU model is absent from `cpu_power.csv` | + +### The CPU load × TDP fallback, measured + +`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. + +!!! warning "These figures are machine mode only" + + The profiling script constructs its tracker with `force_mode_cpu_load=True` + and no `tracking_mode` argument (`compare_cpu_load_and_RAPL.py:289-292`), so + every number below was gathered under the default + `tracking_mode="machine"`. + + That matters because `cpu_load` mode uses a **different power model** in + process mode — linear with no idle floor, rather than cubic with a 10%-of-TDP + floor. See + [Methodology → The two cpu_load models](methodology.md#the-two-cpu_load-models). + **The deviations below do not transfer to `tracking_mode="process"`.** No + equivalent profiling has been done for process mode. + +Deviation of the estimate from the RAPL reading, over load points above 5% +(negative means the estimate is lower than RAPL): + +| 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 assumed load-to-power curve does not match the real one.** In machine +mode CodeCarbon applies a cubic curve with a 10%-of-TDP floor +(`hardware.py:287-288`); in process mode it interpolates linearly from zero +(`hardware.py:346`). Neither shape is fitted to hardware. Real curves are convex +on some parts +(the E3-1240 v2 stays under 10 W up to 40% load, so the linear estimate +overshoots it by well over 100%) and saturate early on others (the Threadripper +reaches its power ceiling around 65% load, so the estimate *undershoots* at mid +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 + +- `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: + +- 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..daabb7037 --- /dev/null +++ b/docs/explanation/alternatives.md @@ -0,0 +1,111 @@ +# 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. + +The tools compared here are [ML CO2 Impact](https://mlco2.github.io/impact/), +[carbontracker](https://github.com/lfwa/carbontracker), +[eco2AI](https://github.com/sb-ai-lab/Eco2AI), +[experiment-impact-tracker](https://github.com/Breakend/experiment-impact-tracker), +[Zeus](https://github.com/ml-energy/zeus), +[Scaphandre](https://github.com/hubblo-org/scaphandre) and the cloud providers' +own consoles. + +## 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. + +Note that experiment-impact-tracker's 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 still maintained. + +### "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. diff --git a/docs/explanation/faq.md b/docs/explanation/faq.md index 624ebd90e..4e859b5b4 100644 --- a/docs/explanation/faq.md +++ b/docs/explanation/faq.md @@ -6,7 +6,23 @@ 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. + +## 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? @@ -51,11 +67,14 @@ In a single Python process, the first tracker pays a one-time cost to detect har ## What hardware does CodeCarbon support? -CodeCarbon supports various CPU architectures, GPUs, and cloud providers. For details on measurement priority and supported hardware, see the [Methodology](methodology.md#cpu-metrics-priority) page. +CodeCarbon supports various CPU architectures, GPUs, and cloud providers. For details on measurement priority and supported hardware, see the [Methodology](methodology.md#which-backend-gets-chosen) page. ## How do I report a bug? -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..3256955bc 100644 --- a/docs/explanation/methodology.md +++ b/docs/explanation/methodology.md @@ -1,209 +1,246 @@ # Methodology -Carbon dioxide (CO₂) emissions, expressed as kilograms of -CO₂-equivalents (CO₂eq), are the product of two main factors: +## The shape of the calculation -``` text -C = Carbon Intensity of the electricity consumed for computation: quantified as g of CO₂ emitted per kilowatt-hour of electricity. +Emissions, expressed in kilograms of CO₂-equivalent (CO₂eq), are the product of +two factors: -E = Energy Consumed by the computational infrastructure: quantified as kilowatt-hours. +```text +E = Energy consumed by the computational infrastructure, in kilowatt-hours +C = Carbon intensity of that electricity, in g CO₂ emitted per kilowatt-hour ``` -Carbon dioxide emissions (CO₂eq) can then be calculated as `C * E` - -## Carbon Intensity - -Carbon Intensity of the consumed electricity is calculated as a weighted -average of the emissions from the different energy sources that are used -to generate electricity, including fossil fuels and renewables. In this -toolkit, the fossil fuels coal, petroleum, and natural gas are -associated with specific carbon intensities: a known amount of carbon -dioxide is emitted for each kilowatt-hour of electricity generated. -Renewable or low-carbon fuels include solar power, hydroelectricity, -biomass, geothermal, and more. The nearby energy grid contains a mixture -of fossil fuels and low-carbon energy sources, called the Energy Mix. -Based on the mix of energy sources in the local grid, the Carbon -Intensity of the electricity consumed can be computed. - -![Grid Energy Mix](../images/grid_energy_mix.png){.align-center width="350px" height="300px"} - -When available, CodeCarbon uses global carbon intensity of electricity -per cloud provider ( -[here](https://github.com/mlco2/codecarbon/blob/master/codecarbon/data/cloud/impact.csv)) -or per country ( -[here](https://github.com/mlco2/codecarbon/blob/master/codecarbon/data/private_infra/global_energy_mix.json) -). - -If we don't have the global carbon intensity or electricity of a -country, but we have its electricity mix, we used to compute the carbon -intensity of electricity using this table: - -| Energy Source | Carbon Intensity (kg/MWh) | -|---------------|---------------------------| -| Coal | 995 | -| Petroleum | 816 | -| Natural Gas | 743 | -| Geothermal | 38 | -| Hydroelectricity | 26 | -| Nuclear | 29 | -| Solar | 48 | -| Wind | 26 | - -*Carbon Intensity Across Energy Sources* - -Sources: +```text +Emissions (kgCO₂eq) = E × C +``` -- [for fossil energies](https://github.com/responsibleproblemsolving/energy-usage#conversion-to-co2) -- [for renewables energies](http://www.world-nuclear.org/uploadedFiles/org/WNA/Publications/Working_Group_Reports/comparison_of_lifecycle.pdf) +CodeCarbon computes `E` by sampling CPU, GPU and RAM power at a fixed interval +and integrating over time, and resolves `C` from the machine's location or +cloud region. Both halves have fallbacks, and which fallback ran is what +determines how much you should trust the result. -Then, for example, if the Energy Mix of the Grid Electricity is 25% -Coal, 35% Petroleum, 26% Natural Gas and 14% Nuclear: +Expanded, that is four terms: -``` text -Net Carbon Intensity = 0.25 * 995 + 0.35 * 816 + 0.26 * 743 + 0.14 * 29 = 731.59 kgCO₂/kWh +```text +emissions = Σ_intervals (power × Δt × PUE) × carbon_intensity ``` -But it doesn't happen anymore because Our World in Data now provides -the global carbon intensity of electricity per country ( -[source](https://ourworldindata.org/grapher/carbon-intensity-electricity#explore-the-data) -). Some countries are missing data for last year, so we use the previous -year data available. +- `power` — watts drawn by the CPU, GPU and RAM at the moment of sampling. +- `Δt` — the sampling interval, `measure_power_secs`, 15 seconds by default. + Power × Δt is energy; summing over every interval gives the energy of the run. +- `PUE` — power usage effectiveness, an optional multiplier for datacentre + overhead such as cooling. It defaults to 1.0, meaning no overhead assumed. +- `carbon_intensity` — grams of CO₂-equivalent emitted per kilowatt-hour on the + grid the machine draws from. -If ever we have neither the global carbon intensity of a country nor -its electricity mix, we apply a world average of 475 gCO2.eq/KWh ( -[source](https://www.iea.org/reports/global-energy-co2-status-report-2019/emissions) -). +`E` above is therefore the **PUE-inflated** energy: a `pue` you set is already +folded into every energy figure CodeCarbon reports, per component. This matters +when you do arithmetic with the CSV columns — see +[Power Usage Effectiveness](#power-usage-effectiveness-pue). -As you can see, we try to be as accurate as possible in estimating -carbon intensity of electricity. Still there is room for improvement and -all contributions are welcome. +| Half of the formula | Best case | Worst case | +|---|---|---| +| `E` — energy | Hardware energy counters (RAPL, EMI, NVML) | A CPU model's catalogue TDP scaled by CPU load | +| `C` — carbon intensity | Live grid intensity from Electricity Maps | The 475 gCO₂eq/kWh world average | -## Power Usage +Nothing in the output states which case applied to `C`. For `E`, the log line +emitted at startup names the CPU backend. -Power supply to the underlying hardware is tracked at frequent time -intervals. This is a configurable parameter `measure_power_secs`, with -default value 15 seconds, that can be passed when instantiating the -emissions tracker. +## Energy: what is measured, what is modelled -CodeCarbon focuses on the main compute components it can measure or -estimate directly: CPU, GPU, and RAM. It does not separately model disk -I/O, network transfers, displays, cooling, or other peripherals because -those sources are usually much smaller, and often negligible, for local -code-level experiments. They are also not exposed through the same -low-overhead measurement interfaces as CPU, GPU, and RAM. However, they -can matter for workloads dominated by data movement, storage, or -distributed systems. +Power is sampled at `measure_power_secs`, default 15 seconds, configurable when +instantiating the tracker. Energy is the integral of power over time: +`Energy = Power × Time`. -Currently, the package supports the following hardware infrastructure. +| Component | Best available source | Modelled fallback | +|---|---|---| +| CPU | Intel RAPL (Linux), Energy Meter Interface (Windows 11), `powermetrics` (macOS), Intel Power Gadget (legacy) | TDP × CPU load, or a flat constant | +| GPU | NVML via `nvidia-ml-py` (Nvidia) or AMDSMI (AMD) — a direct device reading | none; without a supported GPU library, GPU is not counted | +| RAM | none — there is no RAM power counter | always modelled from an estimated DIMM count | -### Tracking Modes +CodeCarbon does not separately model disk I/O, network transfer, displays, +cooling or other peripherals. Those are usually small for local, code-level +experiments, and they are not exposed through the same low-overhead interfaces. +They can matter for workloads dominated by data movement, storage or +distributed communication. -CodeCarbon operates in two distinct modes to determine how power consumption is attributed to your work. Choosing the right mode is essential for data accuracy. +### Tracking modes -The `tracking_mode` parameter (values: `"machine"` or `"process"`, default `"machine"`) controls the **scope** of power attribution: +The `tracking_mode` parameter (`"machine"` or `"process"`, default +`"machine"`) controls the **scope** of attribution, independently of which +measurement backend was selected. -**Machine Mode** (`tracking_mode="machine"`): Measures the total energy consumed by the whole hardware stack (all CPUs, GPUs, and RAM). This is the most straightforward measurement and is ideal for dedicated machines where the tracked workload dominates resource usage. +**Machine mode** measures the whole hardware stack. It is the straightforward +reading, and the right one on a dedicated machine. -**Process Mode** (`tracking_mode="process"`): Estimates the energy attributable to your Python process (and its child processes) by sampling their CPU time relative to total CPU capacity. This is a software-based approximation — it does **not** read hardware counters directly — and is preferable on shared environments where other workloads are running in parallel. +**Process mode** estimates the share attributable to your Python process and +its children, by sampling their CPU time against total CPU capacity. This is a +software approximation — it does **not** read per-process hardware counters — +and is preferable on shared machines. -> ⚠️ **GPU limitation**: Process Mode only affects CPU and RAM attribution. GPU power is always measured at the device level, so if you share a GPU with other users or processes, CodeCarbon will still account for the **entire GPU's** power consumption, not just your share. +!!! warning "GPU is always machine-wide" -Note: The underlying measurement method (Intel RAPL, Windows Energy Meter Interface, Intel Power Gadget, TDP-based CPU-load estimation…) is chosen automatically based on hardware availability and software permissions. It applies independently of the tracking mode. + Process mode affects CPU and RAM attribution only. GPU power is measured at + the device level, so on a shared GPU CodeCarbon still attributes the + **entire GPU's** consumption to your run. -### GPU +!!! warning "On the estimation path, `tracking_mode` also changes the power model" -Tracks Nvidia GPUs energy consumption using `nvidia-ml-py` library -(installed with the package). + When CodeCarbon falls back to `cpu_load` mode, `tracking_mode` selects not + just the attribution scope but a **different power curve** — cubic with a + 10% floor for machine mode, linear with no floor for process mode. See + [The two cpu_load models](#the-two-cpu_load-models). On the hardware-counter + paths (RAPL, EMI, NVML) this does not apply. -### RAM +### Power Usage Effectiveness (PUE) -CodeCarbon v2 uses a 3 Watts for 8 GB ratio -[source](https://www.crucial.com/support/articles-faq-memory/how-much-power-does-memory-use) -. +If you set `pue`, it is applied inside the measurement loop to **each +component's energy** before accumulation +([`emissions_tracker.py:1194`](https://github.com/mlco2/codecarbon/blob/master/codecarbon/emissions_tracker.py#L1194)): -But this is not a good measure because it doesn't take into account the -number of RAM slots used in the machine, that really drive the power -consumption, not the amount of RAM. For example, in servers you could -have thousands of GB of RAM but the power consumption would not be -proportional to the amount of memory used, but to the number of memory -modules used. - -Old machine could use 2 Mb memory stick, where modern servers will use -128 Mb memory stick. +```python +energy *= self._pue +``` -So, in CodeCarbon v3 we switch to using 5 Watts for each RAM slot. The -energy consumption is calculated as follows: +This means `cpu_energy`, `gpu_energy` and `ram_energy` in the CSV are **already +inflated by the PUE**, not just `energy_consumed` and `emissions`. Do not +multiply by PUE a second time when reusing the per-component columns. Water +consumption is derived from the post-PUE energy in the same loop. + +## CPU + +### Which backend gets chosen + +This is the part of CodeCarbon most often misread. The selection logic is in +[`resource_tracker.py:249-279`](https://github.com/mlco2/codecarbon/blob/master/codecarbon/core/resource_tracker.py#L249), +and it is evaluated strictly in this order — the first row that applies wins. + +Rows 1–9 are decided in that selector. If none of them matches, it delegates to +the `_setup_fallback_tracking` helper (`resource_tracker.py:159-219`), which is +where rows 10 and 11 are decided — hence the different line citations on the +last two rows. + +| # | Condition | Result | Source | +|---|---|---|---| +| 1 | `force_cpu_power` is set | **Every platform backend is skipped** — RAPL is not consulted even if it is available — and your value becomes the TDP handed to row 10 or row 11 below. It is not the reported power. | `resource_tracker.py:255-260`, guard at `:272`, `:169-174` | +| 2 | `force_mode_cpu_load` is set, `psutil` present, and a TDP is known | `cpu_load` mode | `resource_tracker.py:263-270` | +| 3 | Linux **and** RAPL files readable | `intel_rapl` — hardware energy counters | `resource_tracker.py:223-225` | +| 4 | macOS **and** Apple Silicon **and** `psutil` present | `cpu_load` mode | `resource_tracker.py:228-230` | +| 5 | macOS **and** Apple Silicon **and** no `psutil` **and** `powermetrics` usable | `powermetrics` | `resource_tracker.py:231-233` | +| 6 | macOS **and** Intel **and** Intel Power Gadget installed | `intel_power_gadget` | `resource_tracker.py:234-236` | +| 7 | macOS **and** Intel **and** `powermetrics` usable | `powermetrics` | `resource_tracker.py:237-239` | +| 8 | Windows **and** EMI available | `windows_emi` — hardware energy counters | `resource_tracker.py:241-243` | +| 9 | Windows **and** Intel Power Gadget installed | `intel_power_gadget` | `resource_tracker.py:244-246` | +| 10 | none of the above, `psutil` present | `cpu_load` mode | `resource_tracker.py:179-190`, `:201-212` | +| 11 | none of the above, no `psutil` | `constant` mode | `resource_tracker.py:191-198`, `:213-218` | + +Two consequences worth stating plainly, because they surprise people: + +- **`force_cpu_power` disables hardware measurement, and it is not the reported + power.** Setting it on a machine with working RAPL replaces a real counter + with an estimate. Because `psutil` is a hard dependency, the estimate is + normally `cpu_load` mode with your value as the TDP, so in machine mode + `force_cpu_power=65` reports `65 × (0.1 + 0.9 × load³)` watts, between 6.5 W + and 65 W, not a flat 65 W. Only on an installation without `psutil` does it + become a flat figure, and then it is half your value, not your value + (row 11 and `constant` mode below). Unlike a registry TDP, it is used as + given: it is not multiplied by the CPU package count. +- **On Apple Silicon, `powermetrics` is effectively unreachable.** + `psutil` is a hard dependency of CodeCarbon, so row 4 fires before row 5 in + every normal installation. The `sudo`-granting instructions that used to + appear here describe a path the code no longer takes. + +### The fallback constants + +When no hardware counter is available, CodeCarbon needs a power figure. It gets +one from the following ladder, in +[`core/cpu.py:1002-1032`](https://github.com/mlco2/codecarbon/blob/master/codecarbon/core/cpu.py#L1002): + +| Situation | TDP used | Constant | Source | +|---|---|---|---| +| CPU model detected and present in the TDP registry (2000+ Intel and AMD parts) | the registry value | — | `cpu.py:1005-1012` | +| CPU model detected but **absent** from the registry, `psutil` present | `threads × 4 W` | `DEFAULT_POWER_PER_CORE = 4` | [`cpu.py:29`](https://github.com/mlco2/codecarbon/blob/master/codecarbon/core/cpu.py#L29), used at `:1024` | +| CPU model absent from the registry and no `psutil`, or the model could not be detected at all | `85 W` | `POWER_CONSTANT = 85` | [`hardware.py:23`](https://github.com/mlco2/codecarbon/blob/master/codecarbon/external/hardware.py#L23), applied at `:457` | + +Whatever TDP results is multiplied by the physical CPU package count +(`resource_tracker.py:267`, `:278`) to give the machine-level ceiling. + +That ceiling is then turned into an instantaneous power reading in one of two +ways, and **the two ways are not the same**: + +- **`cpu_load` mode** — see the next section. **It uses two different power + models depending on `tracking_mode`.** + +- **`constant` mode** — reached only when `psutil` is unavailable — applies a + flat half of TDP + ([`hardware.py:363`](https://github.com/mlco2/codecarbon/blob/master/codecarbon/external/hardware.py#L363)): + + ```text + power = TDP × CONSUMPTION_PERCENTAGE_CONSTANT # CONSUMPTION_PERCENTAGE_CONSTANT = 0.5 + ``` + + `CONSUMPTION_PERCENTAGE_CONSTANT = 0.5` is defined at + [`hardware.py:26`](https://github.com/mlco2/codecarbon/blob/master/codecarbon/external/hardware.py#L26). + +### The two cpu_load models + +`cpu_load` mode does not have one power model. It has two, selected by +`tracking_mode` inside +[`_get_power_from_cpu_load`](https://github.com/mlco2/codecarbon/blob/master/codecarbon/external/hardware.py#L274) +(`hardware.py:274-352`). They differ in shape, not only in scope, and the +difference is large at low load. + +| | `tracking_mode="machine"` | `tracking_mode="process"` | +|---|---|---| +| Load source | `psutil.cpu_percent()`, system-wide (`hardware.py:280-282`) | per-process CPU-time deltas over wall clock, summed across the process and its children (`hardware.py:294-330`) | +| Normalisation | none — already a 0–100% figure | divided by core count (`hardware.py:345`) | +| Curve | **cubic** (`hardware.py:287-288`) | **linear** (`hardware.py:346`) | +| Idle floor | **10% of TDP** | **none** — 0% load gives 0 W | + +```python +# machine mode — hardware.py:287-288 +load_factor = 0.1 + 0.9 * ((cpu_load / 100.0) ** 3) +power = tdp * load_factor +``` -``` text -RAM Power Consumption = 5 Watts * Number of RAM slots used +```python +# process mode — hardware.py:345-346 +cpu_load_normalized = cpu_load / self._cpu_count +power = self._tdp * cpu_load_normalized / 100 ``` -But getting the number of RAM slots used is not possible as you need -root access to get the number of RAM slots used. So we use an heuristic -based on the RAM size. - -For example keep a minimum of 2 modules. Except for ARM CPU like -Raspberry Pi where we will consider a 3W constant. Then consider the max -RAM per module is 128GB and that RAM module only exist in power of 2 (2, -4, 8, 16, 32, 64, 128). So we can estimate the power consumption of the -RAM by the number of modules used. - -- For ARM CPUs (like Raspberry Pi), a constant 3W will be used as the - minimum power -- Base power per DIMM is 5W for x86 systems and 1.5W for ARM systems -- For standard systems (up to 4 DIMMs): linear scaling at full power - per DIMM -- For medium systems (5-8 DIMMs): decreasing efficiency (90% power per - additional DIMM) -- For large systems (9-16 DIMMs): further reduced efficiency (80% - power per additional DIMM) -- For very large systems (17+ DIMMs): highest efficiency (70% power - per additional DIMM) -- Ensures at least 10W for x86 systems (assuming 2 DIMMs at minimum) -- Ensures at least 3W for ARM systems - -Example Power Estimates: - -- **Small laptop (8GB RAM)**: ~10W (2 DIMMs at 5W each) -- **Desktop (32GB RAM)**: ~20W (4 DIMMs at 5W each) -- **Desktop (64GB RAM)**: ~20W (4 DIMMs at 5W each), the same as 32GB -- **Small server (128GB RAM)**: ~40W (8 DIMMs with efficiency - scaling) -- **Large server (1TB RAM)**: ~40W (using 8x128GB DIMMs with high - efficiency scaling) - -This approach significantly improves the accuracy for large servers by -recognizing that RAM power consumption doesn't scale linearly with -capacity, but rather with the number of physical modules. Since we -don't have direct access to the actual DIMM configuration, this -heuristic provides a more reasonable estimate than the previous linear -model. - -If you know the exact RAM power consumption of your system, then provide -it using the `force_ram_power` parameter, which will -override the automatic estimation. - -For example, in a Ubuntu machine, you can get the number of RAM slots -used with the following command: - -``` bash -sudo lshw -C memory -short | grep DIMM +!!! warning "The same workload measured both ways will not differ only by scope" -/0/37/0 memory 4GiB DIMM DDR4 Synchrone Unbuffered (Unregistered) 2400 MHz (0,4 ns) -/0/37/1 memory 4GiB DIMM DDR4 Synchrone Unbuffered (Unregistered) 2400 MHz (0,4 ns) -/0/37/2 memory 4GiB DIMM DDR4 Synchrone Unbuffered (Unregistered) 2400 MHz (0,4 ns) -/0/37/3 memory 4GiB DIMM DDR4 Synchrone Unbuffered (Unregistered) 2400 MHz (0,4 ns) -``` + Switching `tracking_mode` silently swaps the power model. At low + utilisation the two diverge sharply: machine mode never reports below 10% + of TDP, while process mode reports proportionally to load and reaches zero. + At 50% load, machine mode gives `0.1 + 0.9 × 0.125 = 21%` of TDP, while a + process saturating half the cores gives 50% of TDP — more than double, + from the same underlying utilisation. + + If you compare a machine-mode run against a process-mode run and the + numbers disagree by more than the attribution scope explains, this is why. + +**None of these shape choices is sourced in the code.** The cubic exponent, the +0.1 floor in machine mode, and the absence of any floor in process mode are all +asserted, with no comment, citation or fitting procedure anywhere in the +module. They are plausible defaults, not measurements. -Here we count 4 RAM slots used, so the power consumption will be 4 x 5 = -20 Watts, just add `force_ram_power=20` to the init of -CodeCarbon. +[Accuracy and validation](accuracy.md#the-cpu-load-tdp-fallback-measured) +reports how far this fallback lands from measured RAPL energy — **in machine +mode only**; those figures do not transfer to process mode. -### CPU +### Per-platform notes -- **On Windows** +**Linux.** Energy is read from Intel RAPL files under +`/sys/class/powercap/intel-rapl/subsystem` +([Weaver](https://web.eece.maine.edu/~vweaver/projects/rapl/)). Every CPU listed +there is tracked. The files must exist *and* be readable by the running user; +on many distributions they are root-only by default. Despite the "Intel RAPL" +name, AMD processors are supported since Linux kernel 5.8. See +[RAPL Metrics](rapl.md) for the details. -Tracks Intel and AMD processor energy consumption using the [Energy +**Windows.** Tracks Intel and AMD processor energy consumption using the [Energy Meter Interface (EMI)](https://learn.microsoft.com/en-us/windows-hardware/drivers/powermeter/energy-meter-interface), through which Windows 11 exposes the CPU RAPL energy counters (the same @@ -214,7 +251,7 @@ are needed. *Note*: EMI reports CPU power only on Windows 11 running on bare metal (on Windows 10, only on devices with dedicated metering hardware, such as the Surface Book). On virtual machines or older Windows versions, -CodeCarbon falls back to the CPU-load estimation mode described below. +CodeCarbon falls back to the CPU-load estimation mode described above. *Note*: as on Linux, only package channels are measured. Windows exposes one EMI device per metered component, so a CPU shows up as one device per @@ -228,58 +265,23 @@ the [`rapl_include_dram`](../how-to/configuration.md#including-dram-in-the-cpu-measurement) option is enabled. -Legacy support for `Intel Power Gadget` is kept for machines where it is -still installed, but the tool [has been discontinued by -Intel](https://github.com/mlco2/codecarbon/issues/457). - -- **On Mac (Intel)** - -Tracks Intel processors energy consumption using the +**macOS, 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). - -- **Apple Silicon Chips (M1, M2, M3, ...)** +[source](https://www.intel.com/content/www/us/en/developer/articles/tool/power-gadget.html). +Intel has since discontinued the tool; this is a known limitation, +tracked in [issue #457](https://github.com/mlco2/codecarbon/issues/457). -Apple Silicon Chips contain both the CPU and the GPU. +**macOS, Apple Silicon.** The Apple Silicon chip contains both CPU and GPU, and +`powermetrics` can read both, but it requires `sudo` and — as row 4 of the table +above shows — `cpu_load` mode is selected first whenever `psutil` is installed, +which is the normal case. There is no known way to read Apple Silicon energy +without administrative rights; if you know of one, please +[open an issue](https://github.com/mlco2/codecarbon/issues). -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/)). +**Legacy Intel Power Gadget** is retained on all platforms for machines where +it is still installed, but it should not be relied on for new setups. -To give sudo rights without having to enter a password each time, you -can modify the sudoers file with the following command: - -``` bash -sudo visudo -``` - -Then add the following line at the end of the file: - -``` bash -username ALL = (root) NOPASSWD: /usr/bin/powermetrics -``` - -If you do not want to give sudo rights to your user, then CodeCarbon -will fall back to constant mode to measure CPU energy consumption. - -- **On Linux** - -Tracks Intel and AMD processor energy consumption from Intel RAPL files -at `/sys/class/powercap/intel-rapl/subsystem` ( -[reference](https://web.eece.maine.edu/~vweaver/projects/rapl/) ). All -CPUs listed in this directory will be tracked. - -*Note*: The Power Consumption will be tracked only if the RAPL files -exist at the above-mentioned path and if the user has the necessary -permissions to read them. - -## CPU hardware +### CPU hardware background The CPU die is the processing unit itself. It's a piece of semiconductor that has been sculpted/etched/deposited by various @@ -287,7 +289,8 @@ manufacturing processes into a net of logic blocks that do stuff that makes computing possible. The processor package is what you get when you buy a single processor. It contains one or more dies, plastic/ceramic housing for dies and gold-plated contacts that match those on your -motherboard. +motherboard. RAPL and EMI report at the package level, which is why +CodeCarbon deduplicates subdomain channels. In Linux kernel, energy_uj is a current energy counter in micro joules. It is used to measure CPU core's energy consumption. @@ -299,137 +302,176 @@ will read two files : /sys/class/powercap/intel-rapl/intel-rapl:1/energy_uj and /sys/class/powercap/intel-rapl/intel-rapl:0/energy_uj -## RAPL Metrics - -RAPL (Running Average Power Limit) is a feature of modern processors -that provides energy consumption measurements through hardware counters. - -See for more -information. - -Despite the name "Intel RAPL", it supports AMD processors since Linux -kernel 5.8. - -Read more about how we use it in [RAPL Metrics](rapl.md). - -## CPU metrics priority - -CodeCarbon will first try to read the energy consumption of the CPU from -a low level interface like RAPL (on Linux), the Energy Meter Interface -(on Windows 11) or `powermetrics` (on macOS). If none of the tracking -tools are available, CodeCarbon will be switched to a fallback mode: - -- It will first detect which CPU hardware is currently in use, and - then map it to a data source listing 2000+ Intel and AMD CPUs and - their corresponding thermal design powers (TDPs). -- If the CPU is not found in the data source, a global constant will - be applied. -- If `psutil` is available, CodeCarbon will try to estimate the energy - consumption from the TDP and the CPU load. -- CodeCarbon assumes that 50% of the TDP will be the average power - consumption to make this approximation. - -Here is a drawing of the fallback mode: - -![CPU Fallback](../images/cpu_fallback.png){.align-center} - -The code doing this is available in -[codecarbon/core/resource_tracker.py](https://github.com/mlco2/codecarbon/blob/master/codecarbon/core/resource_tracker.py#L24). - -The net Energy Used is the net power supply consumed during the compute -time, measured as `kWh`. - -We compute energy consumption as the product of the power consumed and -the time the power was consumed for. The formula is: -`Energy = Power * Time` - -## References - -[Energy Usage Reports: Environmental awareness as part of algorithmic -accountability](https://arxiv.org/pdf/1911.08354.pdf) - -### How CodeCarbon Works - -CodeCarbon uses a scheduler that, by default, calls for a measure every -15 seconds, so it has no significant overhead. - -The measure itself is fast and CodeCarbon is designed to be as light as -possible with a small memory footprint. +For how a rolling energy counter becomes a power figure, see +[Power Estimation](power-estimation.md). For the primary sources on RAPL, see +[Khan et al., *RAPL in Action*](https://dl.acm.org/doi/10.1145/3177754) and +[Weaver, *Reading RAPL energy measurements from Linux*](https://web.eece.maine.edu/~vweaver/projects/rapl/); +this +[blog post](https://blog.chih.me/read-cpu-power-with-RAPL.html) is a useful +informal walkthrough. + +## GPU + +Nvidia GPU energy is read through `nvidia-ml-py` (installed with the package), +which queries NVML — a direct device reading, not a model. There is no fallback: +if NVML is unavailable, GPU energy is not counted at all rather than estimated. +On Apple Silicon under `powermetrics`, the integrated GPU is reported as a +separate `AppleSiliconChip` device. + +## RAM + +There is no hardware counter for RAM power on the platforms CodeCarbon +supports, and reading the actual DIMM configuration needs root. So CodeCarbon +runs a two-stage estimate in +[`external/ram.py:82-193`](https://github.com/mlco2/codecarbon/blob/master/codecarbon/external/ram.py#L82). + +**Stage 1 — guess the DIMM count** from total RAM, using a hardcoded step +function (`ram.py:82-139`): + +| Total RAM | Assumed DIMMs | +|---|---| +| ≤ 2 GB | 1 | +| ≤ 16 GB | 2 | +| 17–64 GB | 4 | +| 65–128 GB | 8 | +| > 128 GB | `ceil(total_GB / largest_fitting_DIMM_size)`, capped at 32 | + +**Stage 2 — assign power per DIMM** with a marginal-efficiency taper +(`ram.py:141-193`): + +| Parameter | Value | Source | +|---|---|---| +| Base power per DIMM, x86 | `RAM_SLOT_POWER_X86 = 5` W | [`ram.py:14`](https://github.com/mlco2/codecarbon/blob/master/codecarbon/external/ram.py#L14) | +| Base power per DIMM, ARM | 1.5 W | `ram.py:158` | +| DIMMs 1–4 | 100% of base each | `ram.py:168-170` | +| DIMMs 5–8 | 90% of base each | `ram.py:171-175` | +| DIMMs 9–16 | 80% of base each | `ram.py:176-182` | +| DIMMs 17+ | 70% of base each | `ram.py:183-190` | +| Minimum, x86 | 10 W (2 DIMMs × 5 W) | `ram.py:165`, applied `:193` | +| Minimum, ARM | 3 W | `ram.py:160`, applied `:193` | + +The only citation anywhere in the module is a pre-v3 +[Crucial FAQ](https://www.crucial.com/support/articles-faq-memory/how-much-power-does-memory-use) +recording the *old* 3 W-per-8 GB rule (`ram.py:20-22`), which the current model +replaced. **Neither the 5 W figure nor any of the 0.9/0.8/0.7 multipliers has a +source in the code.** They are asserted values that appear reasonable, not +measurements. + +The change from v2's 3 W-per-8 GB to per-DIMM power is nonetheless directionally +right: RAM power tracks the number of physical modules, not the number of +gigabytes. A server with 1 TB across 8 DIMMs does not draw 128× a laptop's RAM +power. Worked examples from the model above: + +- 8 GB laptop → 2 DIMMs → **10 W** +- 32 GB desktop → 4 DIMMs → **20 W** +- 64 GB desktop → 4 DIMMs → **20 W** (identical to 32 GB) +- 128 GB server → 8 DIMMs → **38 W** +- 1 TB server → 8 DIMMs → **38 W** (identical to 128 GB) + +**If you can measure or look up your real configuration, do so and override the +estimate.** On Linux: + +```bash +sudo lshw -C memory -short | grep DIMM -The scheduler is started when the first `start` method is called and -stopped when `stop` method is called. +/0/37/0 memory 4GiB DIMM DDR4 Synchrone Unbuffered (Unregistered) 2400 MHz +/0/37/1 memory 4GiB DIMM DDR4 Synchrone Unbuffered (Unregistered) 2400 MHz +/0/37/2 memory 4GiB DIMM DDR4 Synchrone Unbuffered (Unregistered) 2400 MHz +/0/37/3 memory 4GiB DIMM DDR4 Synchrone Unbuffered (Unregistered) 2400 MHz +``` -Another scheduler (`scheduler_monitor_power`) -is used to monitor only the power consumption of the hardware every -second. It is needed for hardware that do not have energy counters but -only instant power, like in CPU load mode. +Four slots, so pass `force_ram_power=20` (4 × 5 W) — or better, a wattage you +have measured — to the tracker. `force_ram_power` bypasses the whole heuristic. -## Estimation of Equivalent Usage Emissions +## Carbon intensity -The CodeCarbon dashboard provides equivalent emissions and energy usage -comparisons to help users better understand the carbon impact of their -activities. These comparisons are based on the following assumptions: +Carbon intensity is the weighted average emissions of the energy sources +feeding the grid the machine is drawing from. Fossil fuels — coal, petroleum, +natural gas — carry high intensities; solar, hydro, wind, nuclear, biomass and +geothermal carry low ones. The local mix determines the figure. -### Car Usage +![Grid Energy Mix](../images/grid_energy_mix.png){.align-center width="350px" height="300px"} -- **Emission factor**: *0.12 kgCO₂ per kilometer driven*. -- This value is derived from the average emissions of a European - passenger car under normal driving conditions. +### Resolution order -Source : [European Environment -Agency](https://co2cars.apps.eea.europa.eu/?source=%7B%22track_total_hits%22%3Atrue%2C%22query%22%3A%7B%22bool%22%3A%7B%22must%22%3A%5B%7B%22constant_score%22%3A%7B%22filter%22%3A%7B%22bool%22%3A%7B%22must%22%3A%5B%7B%22bool%22%3A%7B%22should%22%3A%5B%7B%22term%22%3A%7B%22year%22%3A2023%7D%7D%5D%7D%7D%2C%7B%22bool%22%3A%7B%22should%22%3A%5B%7B%22term%22%3A%7B%22scStatus%22%3A%22Provisional%22%7D%7D%5D%7D%7D%5D%7D%7D%7D%7D%5D%7D%7D%2C%22display_type%22%3A%22tabular%22%7D) +CodeCarbon resolves `C` through the ladder below, implemented in +[`core/emissions.py`](https://github.com/mlco2/codecarbon/blob/master/codecarbon/core/emissions.py). +The first level that answers wins. -### TV Usage +| # | Level | Condition | Data | Source | +|---|---|---|---|---| +| 1 | Forced value | `force_carbon_intensity_g_co2e_kwh` is set | your number | `emissions.py:62-66`, `:156-160` | +| 2 | Cloud region | running on a recognised cloud provider **and** the provider/region pair is in `impact.csv` | [`data/cloud/impact.csv`](https://github.com/mlco2/codecarbon/blob/master/codecarbon/data/cloud/impact.csv) | `emissions.py:68-75` | +| 3 | Electricity Maps | an API token is configured | live grid intensity by lat/lon or country code | `emissions.py:162-177`, `core/electricitymaps_api.py` | +| 4 | Regional | country is US, Canada, Sweden, Norway or Finland **and** a region/bidding zone is known | US state and Canadian province files; Nordic bidding-zone factors | `emissions.py:182-195`, `:234-290` | +| 5 | Country | the country ISO code is in the energy-mix file | [`global_energy_mix.json`](https://github.com/mlco2/codecarbon/blob/master/codecarbon/data/private_infra/global_energy_mix.json), from [Our World in Data](https://ourworldindata.org/grapher/carbon-intensity-electricity) | `emissions.py:292-326` | +| 6 | World average | nothing above resolved | **475 gCO₂eq/kWh**, from the [IEA](https://www.iea.org/reports/global-energy-co2-status-report-2019/emissions) | `carbon_intensity_per_source.json`, applied at `emissions.py:88-96` and `:307-315` | -- **Energy consumption**: *138 Wh per day based on average use*. -- This assumes: - - An average daily usage of 6.5 hours. - - A modern television with a power consumption of approximately - *21.2 W per hour*. +Some countries lack data for the most recent year, in which case the most +recent available year is used. -Source : [The French Agency for Ecological -Transition](https://agirpourlatransition.ademe.fr/particuliers/maison/economies-denergie-deau/electricite-combien-consomment-appareils-maison) +!!! warning "The fallbacks are silent" -### US Citizen Weekly Emissions + Every step down this ladder degrades quietly. If your cloud provider and + region are not in `impact.csv` — and **AWS and Azure publish no + per-region carbon intensity at all; only GCP does** — CodeCarbon logs a + warning and drops to the country value, or to the 475 g world average if no + country is known (`emissions.py:76-97`). Likewise an unknown country ISO + code drops straight to the world average (`emissions.py:303-315`). -- **Annual emissions**: *13.3 tons of CO₂ equivalent per year* for an - average US citizen. -- **Weekly emissions**: This value is divided by the 52 weeks in a - year to estimate weekly emissions: + **Nothing in the CSV, the API payload or the emissions figure records which + level answered.** A run resolved from a live Electricity Maps reading and a + run resolved from the world average produce output of identical shape. If + the provenance matters to you, check the startup logs at `WARNING` level or + set `force_carbon_intensity_g_co2e_kwh` explicitly so there is no ambiguity. -$$\text{Weekly Emissions} = \frac{\text{Annual Emissions (tons)}}{52}$$ +### The legacy per-fuel table -$$\text{Weekly Emissions} = \frac{13.3}{52} \approx 0.256 \, \text{tons of CO₂ equivalent per week.}$$ +Before per-country intensities were available from Our World in Data, +CodeCarbon derived intensity from a country's electricity *mix* using this +table. It is retained for the regional Canadian data, which is published as a +mix rather than as an intensity. -Source : [IEA CO2 total emissions per capita by region, -2000-2023](https://www.iea.org/data-and-statistics/charts/co2-total-emissions-per-capita-by-region-2000-2023) +| Energy Source | Carbon Intensity (kg/MWh) | +|---------------|---------------------------| +| Coal | 995 | +| Petroleum | 816 | +| Natural Gas | 743 | +| Geothermal | 38 | +| Hydroelectricity | 26 | +| Nuclear | 29 | +| Solar | 48 | +| Wind | 26 | -### Calculation Formula +*Carbon Intensity Across Energy Sources* -The equivalent emissions are calculated using this formula: +Sources: -$$\text{Equivalent Emissions} = \frac{\text{Total Emissions (kgCO₂)}}{\text{Emission Factor (kgCO₂/unit)}}$$ +- [for fossil energies](https://github.com/responsibleproblemsolving/energy-usage#conversion-to-co2) +- [for renewables energies](http://www.world-nuclear.org/uploadedFiles/org/WNA/Publications/Working_Group_Reports/comparison_of_lifecycle.pdf) -For example: +Then, for example, if the Energy Mix of the Grid Electricity is 25% +Coal, 35% Petroleum, 26% Natural Gas and 14% Nuclear: -- **Car Usage**: *1 kWh* of energy consumption is approximately - equivalent to: - - *8.33 kilometers driven by a car* (*1 ÷ 0.12*). - - *11.9 hours of TV usage* (*1 ÷ 0.084*), if emissions are - considered. -- **US Citizen Emissions**: - - *1 kWh* of energy consumption can be compared to a fraction of - the average weekly emissions of a US citizen: +``` text +Net Carbon Intensity = 0.25 * 995 + 0.35 * 816 + 0.26 * 743 + 0.14 * 29 = 731.59 kgCO₂/MWh +``` -$$\text{US Citizen Equivalent} = \frac{\text{Total Emissions (tons)}}{0.256}$$ +## Measurement cadence -These estimates are approximate and subject to regional variations in: +CodeCarbon runs a scheduler that takes a measurement every `measure_power_secs` +(default 15 s), started by `start()` and stopped by `stop()`. The measurement +itself is fast and the memory footprint is small, so overhead is not +significant at the default interval. -- Grid emissions intensity. -- Vehicle efficiencies. +A second scheduler, `scheduler_monitor_power`, samples power once per second. +It exists for hardware that exposes instantaneous power but no cumulative +energy counter — `cpu_load` mode in particular — so that the per-interval +energy is an average of many samples rather than a single instant. -### Source Code +## Further reading -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. +- [Accuracy and validation](accuracy.md) — measured deviations and known gaps +- [RAPL Metrics](rapl.md) — the Linux energy counters in detail +- [Power Estimation](power-estimation.md) — counters to power +- [Output reference](../reference/output.md) — what each field means diff --git a/docs/explanation/model-comparisons.md b/docs/explanation/model-comparisons.md index 084c869c9..9fea04279 100644 --- a/docs/explanation/model-comparisons.md +++ b/docs/explanation/model-comparisons.md @@ -1,4 +1,13 @@ -# Model Comparisons {#model_examples} +# Model Comparisons (Third-Party Data) {#model_examples} + +!!! warning "These numbers were not produced by CodeCarbon" + + Every figure and chart on this page comes from published third-party + research that used **Microsoft Azure cloud tooling** to estimate emissions + (see [References](#references)). They are reproduced here only as an + illustration of the orders of magnitude involved in model training. + CodeCarbon measures differently and will report different values — see + [Methodology](methodology.md) for what CodeCarbon actually does. The following table shows the different electricity consumption of popular NLP and Computer Vision models: diff --git a/docs/explanation/power-estimation.md b/docs/explanation/power-estimation.md index b56880429..5486f9967 100644 --- a/docs/explanation/power-estimation.md +++ b/docs/explanation/power-estimation.md @@ -1,5 +1,12 @@ # How Power Estimation Works in CodeCarbon +!!! info + + This page explains how CodeCarbon derives **power** (Watts) from **energy** + counters. If you are looking for how CodeCarbon estimates power for hardware + that has no energy counter (TDP registry, constants, CPU load), see + [Methodology](methodology.md). + CodeCarbon tracks energy consumption by periodically querying the underlying hardware interfaces (e.g., RAPL for Intel CPUs, NVML for NVIDIA GPUs, AMDSMI for AMD GPUs) or by falling back on constant power models for non-supported hardware (such as generic CPU or RAM matching). While energy is the metric primarily responsible for CO₂ emissions estimations, tracking **power** (measured in Watts or kiloWatts) is equally important to provide meaningful dashboards and to help users understand their instantaneous consumption. diff --git a/docs/explanation/rapl.md b/docs/explanation/rapl.md index eb6d37e21..71e1cd495 100644 --- a/docs/explanation/rapl.md +++ b/docs/explanation/rapl.md @@ -91,7 +91,7 @@ and consistent measurements: generations - Can be supplemented with `dram` domains for complete hardware measurement (package + DRAM) -2. **Optional psys mode**: Set `prefer_psys=True` to use `psys` +2. **Optional psys mode**: Set `rapl_prefer_psys=True` to use `psys` (platform/system) domain instead: - Provides total platform power (CPU + chipset + PCIe + some other components) @@ -106,7 +106,7 @@ and consistent measurements: - Falls back to MSR if MMIO is unreadable 4. **Subdomain filtering**: Excludes `core` and `uncore` subdomains when `package` is available to avoid double-counting -5. **DRAM exclusion**: By default (`include_dram=False`), don't add +5. **DRAM exclusion**: By default (`rapl_include_dram=False`), don't add DRAM domain to package. As DRAM is supposed to be in RAM power, not CPU in a future version of CodeCarbon. @@ -148,12 +148,67 @@ Measurements," (K. N. Khan, M. Hirki, T. Niemi, J. K. Nurminen, and Z. Ou, ACM Trans. Model. Perform. Eval. Comput. Syst., vol. 3, no. 2, pp. 1--26, Apr. 2018, doi: 10.1145/3177754.) -## RAPL Measurements: Real-World Examples +## Key Takeaways for RAPL Measurements + +1. **CodeCarbon defaults to package domains**: This provides the most + reliable and consistent measurements that match CPU TDP + specifications. Package domains update correctly under load across + all Intel generations. +2. **psys can be unreliable**: While `psys` provides total platform + power, it: + - Can report higher values than expected (includes chipset, PCIe, + etc.) + - May not include all CPU components on older Intel systems : on + some computers, `psys` is lower than `package`. + - So it is disabled by default, you can enable it with + `rapl_prefer_psys=True` if desired +3. **Avoid summing overlapping domains**: Never sum psys + package + + core + uncore. They are hierarchical and overlapping. This causes + 2-3x over-counting! +4. **Domain hierarchy**: + - psys ⊃ package ⊃ {core, uncore} + - Correct: Use package alone (CodeCarbon default) OR psys alone + (with rapl_prefer_psys=True) + - Wrong: Sum multiple levels +5. **Interface deduplication**: The same domain may appear in both + `intel-rapl` (MSR) and `intel-rapl-mmio` interfaces. CodeCarbon + automatically deduplicates, preferring MMIO. +6. **DRAM measurement**: CodeCarbon does not include DRAM domains by + default (`rapl_include_dram=False`) for CPU hardware measurement. Set + `rapl_include_dram=True` to measure CPU package + DRAM domains. +7. **Platform-specific behavior**: + - Intel modern: package or psys (with rapl_prefer_psys=True) + - Intel older: package-0 for CPU only + - AMD: Sum all package-X-die-Y for multi-die CPUs, which is + [under review](https://github.com/mlco2/codecarbon/issues/1379) +8. **Limitations**: RAPL does NOT measure: + - Discrete GPUs (use nvidia-smi/rocm-smi) + - SSDs, peripherals, fans + - Actual DRAM chips — the `dram` domain reports memory-controller + power, and CodeCarbon excludes it by default (`rapl_include_dram=False`) + - Complete system power (use wall meter for accuracy) + +## Appendix: raw measurements from three machines + +!!! note + + Everything below is raw annotated output collected from three real machines. + It is kept as evidence for the domain-selection rules above; you do not need + to read it to understand how CodeCarbon picks RAPL domains. Choosing the right metric to track CPU power consumption depends on CPU hardware and available domains. Below are measurements from different systems showing the importance of avoiding double-counting. +!!! warning "The two Intel walkthroughs were collected with `rapl_prefer_psys=True`" + + Their `psys` readings and the "use psys only" annotations inside the code + blocks describe that non-default configuration. With the default + `rapl_prefer_psys=False`, CodeCarbon uses the **package** domain(s) instead: + psys is detected and explicitly skipped (`codecarbon/core/cpu.py:694-731`). + The package figure is given alongside psys in each block, so you can read + the default behaviour off the same numbers. + We investigate RAPL on various architectures : - 2017 Gaming computer with AMD Ryzen Threadripper 1950X @@ -166,6 +221,15 @@ Power plug measure when idle (10% CPU): 125 W package-0-die-0: 68 W | package-0-die-1: 68 W | CodeCarbon: 137 W ``` +!!! warning "Multi-die summing is under review" + + On Linux, CodeCarbon sums every domain whose name contains `package`, so the + two per-die domains above are added together. Whether those domains report + independent power or mirror the same counter is unresolved, and it decides + whether 137 W is right or double the real figure. Tracked in + [issue #1379](https://github.com/mlco2/codecarbon/issues/1379). The numbers + here are left exactly as observed — they are the evidence behind that issue. + ### Laptop: Intel(R) Core(TM) Ultra 7 265H (TDP 28W) **Idle Measurements**: @@ -183,8 +247,9 @@ RAPL domains (individual readings): ✅ CORRECT: Use psys only = 6.66W (matches battery discharge) ``` -**CodeCarbon behavior**: Uses **psys only** (6.66W) to avoid -double-counting. +**CodeCarbon behavior**: by default uses **package-0 only** (3.85W) — never a +sum of overlapping domains. With `rapl_prefer_psys=True` it uses **psys only** +(6.66W), which is what matches the battery discharge rate here. **Under Load (stress-ng)**: @@ -200,8 +265,9 @@ RAPL domains: ✅ CORRECT: Use psys only = 24.69W (close to battery discharge) ``` -**CodeCarbon measurement**: 22W using psys (accurate, within expected -range) +**CodeCarbon measurement**: 22W using psys, with `rapl_prefer_psys=True` +(accurate, within expected range). The default configuration would have +reported the package-0 figure (21.35W) instead. **Note**: The package-0 measurement (21.35W) excludes some platform components like chipset and PCIe that are included in psys (24.69W). @@ -245,11 +311,10 @@ Analysis: - Core power (14.00W) matches the CPU TDP spec (15W) ``` -**CodeCarbon behavior**: Uses **psys only** (29.97W) for accurate total -platform measurement. - -**Legacy behavior (before v2.x)**: Would have measured only package-0 -(15.73W), missing ~14W of platform power! +**CodeCarbon behavior**: by default uses **package-0 only** (15.73W). With +`rapl_prefer_psys=True` it uses **psys only** (29.97W), the total platform +figure — roughly 14W more, because psys also covers chipset, PCIe and other +platform components that are not CPU power. ### Desktop: AMD Ryzen Threadripper 1950X (16-Core, 32 threads, Multi-die) @@ -314,41 +379,3 @@ Analysis: - `core` domain reports very low values (unclear if included in package) - Package measurements are generally reliable for total CPU power - -## Key Takeaways for RAPL Measurements - -1. **CodeCarbon defaults to package domains**: This provides the most - reliable and consistent measurements that match CPU TDP - specifications. Package domains update correctly under load across - all Intel generations. -2. **psys can be unreliable**: While `psys` provides total platform - power, it: - - Can report higher values than expected (includes chipset, PCIe, - etc.) - - May not include all CPU components on older Intel systems : on - some computers, `psys` is lower than `package`. - - So it is disabled by default, you can enable it with - `prefer_psys=True` if desired -3. **Avoid summing overlapping domains**: Never sum psys + package + - core + uncore. They are hierarchical and overlapping. This causes - 2-3x over-counting! -4. **Domain hierarchy**: - - psys ⊃ package ⊃ {core, uncore} - - Correct: Use package alone (CodeCarbon default) OR psys alone - (with prefer_psys=True) - - Wrong: Sum multiple levels -5. **Interface deduplication**: The same domain may appear in both - `intel-rapl` (MSR) and `intel-rapl-mmio` interfaces. CodeCarbon - automatically deduplicates, preferring MMIO. -6. **DRAM measurement**: CodeCarbon does not include DRAM domains by - default (`include_dram=False`) for CPU hardware measurement. Set - `include_dram=True` to measure CPU package + DRAM domains. -7. **Platform-specific behavior**: - - Intel modern: package or psys (with prefer_psys=True) - - Intel older: package-0 for CPU only - - AMD: Sum all package-X-die-Y for multi-die CPUs -8. **Limitations**: RAPL does NOT measure: - - Discrete GPUs (use nvidia-smi/rocm-smi) - - SSDs, peripherals, fans - - Actual DRAM chips, we still have to investigate on this point - - Complete system power (use wall meter for accuracy) 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 4486f158e..8c9c5fecb 100644 --- a/docs/how-to/agent-instructions.md +++ b/docs/how-to/agent-instructions.md @@ -157,7 +157,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..500bd4bda 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://docs.codecarbon.io/latest/reference/deprecations/). ## Tracking Mode diff --git a/docs/how-to/contributing.md b/docs/how-to/contributing.md index 243bb80d2..5f158b0cf 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,57 @@ 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(); -``` - +Most pull requests are expected to contain either new tests or test updates. See +[development](development.md#tests) for details. - -### 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 ``` -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/development.md b/docs/how-to/development.md new file mode 100644 index 000000000..ba7f76ebd --- /dev/null +++ b/docs/how-to/development.md @@ -0,0 +1,303 @@ +# 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. 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 +cd webapp && pnpm dev +``` + +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 +cd webapp && pnpm dev +``` + +### 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 task 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 task dashboard +``` + +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/enable-rapl.md b/docs/how-to/enable-rapl.md index ce297380b..cb1413512 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 @@ -180,7 +182,7 @@ Check the output for `CPU Tracking Method: RAPL` to confirm RAPL is active. If running CodeCarbon in Docker, mount the RAPL sysfs: ```bash -docker run --device /sys/class/powercap:/sys/class/powercap:ro +docker run -v /sys/class/powercap:/sys/class/powercap:ro ``` Or in `docker-compose.yml`: 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/how-to/troubleshooting.md b/docs/how-to/troubleshooting.md new file mode 100644 index 000000000..8af622d7b --- /dev/null +++ b/docs/how-to/troubleshooting.md @@ -0,0 +1,318 @@ +# Troubleshooting + +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." + +Concurrent trackers are allowed by default (`allow_multiple_runs=True`). Only +when you set `allow_multiple_runs=False` does CodeCarbon take a machine-wide +lock so that two trackers do not double-count the same hardware. With the lock +enabled, 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: + +- Set `allow_multiple_runs=True` (or the environment variable + `CODECARBON_ALLOW_MULTIPLE_RUNS=True`) to go back to the default behaviour and + skip the lock entirely. This is what CodeCarbon's own test suite does. +- 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 + ``` + +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 in the Python + API and 10 seconds for `codecarbon monitor` (which also uses an + `api_call_interval` of 30 measures); 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 } + +Also seen as `RAPL - Permission denied listing ...`, `... scanning ... for RAPL +domains` or `... reading name file ...`. Since a kernel security fix the energy +counters under `/sys/class/powercap/` are root-readable only, so CodeCarbon +falls back to estimating from CPU load and TDP. The quick fix does not survive a +reboot: + +```bash +sudo chmod -R a+r /sys/class/powercap/* +``` + +For the persistent udev rule and the security tradeoff, see [Improve Measurement +Accuracy with RAPL](enable-rapl.md). + +### "RAPL - No package domains found, falling back to psys" + +Only the platform domain is exposed, so readings cover the whole SoC rather than +the CPU package and will not line up with the CPU TDP. Expected on several +laptop platforms and some newer Intel generations; it is not an error. If no +domain can be selected you see `RAPL - No package or psys domains found, using +all available domains` instead. See [RAPL +Metrics](../explanation/rapl.md) for the domain hierarchy. + +### "RAPL - psys domain detected but not used (rapl_prefer_psys=False)" + +Both domains are available and CodeCarbon chose `package`, which is more +consistent with CPU TDP specifications. Pass `rapl_prefer_psys=True` to the +tracker for total platform power instead; expect the figure to rise. + +### No RAPL at all: containers, VMs, WSL + +`/sys/class/powercap/` is not exposed inside most containers, most virtual +machines, or under WSL, so CodeCarbon silently estimates. `codecarbon detect` +confirms which mode you are in. For Docker on bare metal, mount the path +read-only (`-v /sys/class/powercap:/sys/class/powercap:ro`); see [Docker and +Containerized +Environments](enable-rapl.md#docker-and-containerized-environments). Under a +hypervisor there is nothing to do from inside the guest — and on shared hardware +RAPL would report energy for the whole physical CPU rather than your share, so +the estimate answers a different question rather than being a degraded +measurement. + +## macOS + +On Apple Silicon, CodeCarbon does not use `powermetrics`. `psutil` is a hard +dependency of the package, and the backend selector picks CPU-load estimation +as soon as `psutil` is importable, before `powermetrics` is ever tried +([`resource_tracker.py:228-233`](https://github.com/mlco2/codecarbon/blob/master/codecarbon/core/resource_tracker.py#L228)). +Granting passwordless `sudo` for `powermetrics` will not change which backend is +used. See +[Which backend gets chosen](../explanation/methodology.md#which-backend-gets-chosen). + +On Intel Macs, CodeCarbon uses Intel Power Gadget when it is installed, and +`powermetrics` when it is not. `powermetrics` requires root: CodeCarbon runs it +through `sudo` and first checks whether that `sudo` call would prompt for a +password. If a prompt is detected, CodeCarbon logs at debug level *"Not using +PowerMetrics, sudo password prompt detected"* and falls back to estimation, +since a library cannot answer an interactive prompt. To get measured values +there, grant passwordless `sudo` for `powermetrics` alone by adding a line like +this with `sudo visudo`: + +```bash +username ALL = (root) NOPASSWD: /usr/bin/powermetrics +``` + +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 000000000..12253dc99 Binary files /dev/null and b/docs/images/og-card.png differ 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/maintaining.md b/docs/maintaining.md new file mode 100644 index 000000000..a2db8f1b4 --- /dev/null +++ b/docs/maintaining.md @@ -0,0 +1,133 @@ +# 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" +``` + +The `CC_` prefix is Clever Cloud's, not CodeCarbon's. + +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 +``` + +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/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/deprecations.md b/docs/reference/deprecations.md new file mode 100644 index 000000000..62f445b7c --- /dev/null +++ b/docs/reference/deprecations.md @@ -0,0 +1,14 @@ +# 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.2.1 | 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` | + diff --git a/docs/reference/output.md b/docs/reference/output.md index 720e0a883..ebf9fc3ff 100644 --- a/docs/reference/output.md +++ b/docs/reference/output.md @@ -22,48 +22,110 @@ 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://docs.codecarbon.io/latest/reference/deprecations/). ## CSV The package has an in-built logger that logs data into a CSV file named `emissions.csv` in the `output_dir`, provided as an input parameter (defaults to the current directory), for each experiment tracked across projects. -| Field | Description | -|-------|-------------| -| timestamp | Time of the experiment in `%Y-%m-%dT%H:%M:%S` format | -| project_name | Name of the project, defaults to `codecarbon` | -| run_id | ID of the run | -| duration | Duration of the compute, in seconds | -| emissions | Emissions as CO₂-equivalents (CO₂eq), in kg | -| emissions_rate | Emissions divided per duration, in Kg/s | -| cpu_power | Mean CPU power (W) | -| gpu_power | Mean GPU power (W) | -| ram_power | Mean RAM power (W) | -| cpu_energy | Energy used per CPU (kWh) | -| gpu_energy | Energy used per GPU (kWh) | -| ram_energy | Energy used per RAM (kWh) | -| energy_consumed | Sum of cpu_energy, gpu_energy and ram_energy (kWh) | -| country_name | Name of the country where the infrastructure is hosted | -| country_iso_code | 3-letter alphabet ISO Code of the respective country | -| region | Province/State/City where the compute infrastructure is hosted | -| on_cloud | `Y` if on cloud, `N` for private infrastructure | -| cloud_provider | One of aws/azure/gcp | -| cloud_region | Geographical region (e.g., us-east-2 for aws, brazilsouth for azure, asia-east1 for gcp) | -| os | Operating system (e.g., Windows-10-10.0.19044-SP0) | -| python_version | Python version (e.g., 3.8.10) | -| codecarbon_version | Version of codecarbon used | -| cpu_count | Number of CPUs | -| cpu_model | Example: Intel(R) Core(TM) i7-1065G7 CPU @ 1.30GHz | -| gpu_count | Number of GPUs | -| gpu_model | Example: 1 x NVIDIA GeForce GTX 1080 Ti | -| longitude | Longitude, with reduced precision to a range of 11.1 km / 123 km² (privacy protection) | -| latitude | Latitude, with reduced precision to a range of 11.1 km / 123 km² (privacy protection) | -| ram_total_size | Total RAM available (GB) | -| tracking_mode | `machine` or `process` (default: `machine`) | -| cpu_utilization_percent | Average CPU utilization during tracking period (%) | -| gpu_utilization_percent | Average GPU utilization during tracking period (%) | -| ram_utilization_percent | Average RAM utilization during tracking period (%) | -| ram_used_gb | Average RAM used during tracking period (GB) | +The columns are written in the field order of the `EmissionsData` dataclass +(`codecarbon/output_methods/emissions_data.py`). + +The **Provenance** column says where each number comes from: a hardware counter, a model, +or configuration. + +| Field | Description | Provenance | +|-------|-------------|------------| +| timestamp | Time of the experiment in `%Y-%m-%dT%H:%M:%S` format | | +| project_name | Name of the project, defaults to `codecarbon` | Config | +| run_id | ID of the run | Generated (UUID) | +| experiment_id | ID of the experiment the run belongs to, used by the API | Config | +| duration | Duration of the compute, in seconds | Measured (wall clock) | +| emissions | Emissions as CO₂-equivalents (CO₂eq), in kg | Computed: `energy × carbon intensity`, see the carbon intensity note | +| emissions_rate | Emissions divided per duration, in Kg/s | Computed: `emissions / duration` | +| cpu_power | Mean CPU power (W) | Varies by backend, see the CPU backends note. Mean of the per-interval samples, not PUE-scaled | +| gpu_power | Mean GPU power (W) | Derived from the GPU energy-counter delta over each interval (`core/gpu_device.py:52`), then averaged. Not PUE-scaled | +| ram_power | Mean RAM power (W) | Always modelled, never measured, see the RAM note | +| cpu_energy | Energy used per CPU (kWh) | Same backend as `cpu_power`, PUE-inflated | +| gpu_energy | Energy used per GPU (kWh) | Accumulated board-energy counter: NVML `nvmlDeviceGetTotalEnergyConsumption` (`core/gpu_nvidia.py:49`) or AMD `amdsmi_get_energy_count` (`core/gpu_amd.py:112`). PUE-inflated | +| ram_energy | Energy used per RAM (kWh) | Modelled RAM power × interval, PUE-inflated | +| energy_consumed | Sum of cpu_energy, gpu_energy and ram_energy (kWh) | Sum of the three columns above; every term already PUE-inflated | +| water_consumed | Water footprint of the run, in litres | Computed: `wue × energy_consumed` (`emissions_tracker.py:1195`). `0` unless you set `wue` | +| country_name | Name of the country where the infrastructure is hosted | IP geolocation (geojs, ipinfo.io fallback), or config in offline mode | +| country_iso_code | 3-letter alphabet ISO Code of the respective country | As `country_name` | +| region | Province/State/City where the compute infrastructure is hosted | As `country_name`; on cloud, from the cloud region lookup | +| cloud_provider | One of aws/azure/gcp | Cloud instance metadata probe | +| cloud_region | Geographical region (e.g., us-east-2 for aws, brazilsouth for azure, asia-east1 for gcp) | Cloud instance metadata probe | +| os | Operating system (e.g., Windows-10-10.0.19044-SP0) | | +| python_version | Python version (e.g., 3.8.10) | | +| codecarbon_version | Version of codecarbon used | | +| cpu_count | Number of CPUs | `psutil.cpu_count()`: logical threads, not physical cores. Under SLURM, the CPUs allocated to the job (`core/util.py:149`) | +| cpu_model | Example: Intel(R) Core(TM) i7-1065G7 CPU @ 1.30GHz | CPU model string detected at startup | +| gpu_count | Number of GPUs | NVML / AMDSMI device enumeration | +| gpu_model | Example: 1 x NVIDIA GeForce GTX 1080 Ti | NVML / AMDSMI device name | +| longitude | Longitude of the machine | IP geolocation, full precision, see the coordinates note | +| latitude | Latitude of the machine | IP geolocation, full precision, see the coordinates note | +| ram_total_size | Total RAM available (GB) | `psutil.virtual_memory().total` | +| tracking_mode | `machine` or `process` (default: `machine`) | Config | +| cpu_utilization_percent | Average CPU utilization during tracking period (%) | Mean of `psutil.cpu_percent()` samples, taken every second | +| gpu_utilization_percent | Average GPU utilization during tracking period (%) | Mean of NVML / AMDSMI utilization samples, taken every second | +| ram_utilization_percent | Average RAM utilization during tracking period (%) | Mean of `psutil.virtual_memory().percent` samples | +| ram_used_gb | Average RAM used during tracking period (GB) | Mean of `psutil.virtual_memory().used` samples | +| on_cloud | `Y` if on cloud, `N` for private infrastructure | Cloud instance metadata probe (`core/cloud.py`) | +| pue | Power Usage Effectiveness applied to this run (default `1.0`) | Config, see the PUE note | +| wue | Water Usage Effectiveness in L/kWh (default `0`) | Config | + +### Notes on provenance + +#### CPU backends + +`cpu_power` and `cpu_energy` depend entirely on the backend selected at startup. The +backend is chosen once, logged at startup, and not recorded in the CSV. Roughly in +decreasing order of trustworthiness: + +| Backend | Nature | Where | +|---|---|---| +| Intel RAPL (Linux) | Measured, hardware energy counter | `core/rapl.py` | +| Windows EMI | Measured, hardware energy counter | `core/windows_emi.py` | +| `powermetrics` (macOS, needs sudo) | Measured, OS-reported power. Effectively unreachable on Apple Silicon: `psutil` is a hard dependency, so the `cpu_load` path is selected first (`core/resource_tracker.py:228-233`) | `core/powermetrics.py` | +| `cpu_load` mode | Modelled: cubic in load with a 10 % TDP floor in `machine` mode, linear `TDP × load/cpu_count` in `process` mode | `external/hardware.py:287-288`, `:345-346` | +| `constant` mode | Modelled: `TDP × 0.5`, a flat 50 % of TDP | `external/hardware.py:362-364` | + +The last two are estimates, and on a lightly loaded or unusual machine they can be far +from the truth. The selection order, including which options override which, is on the +[methodology page](../explanation/methodology.md); measured deviation figures are in +[accuracy](../explanation/accuracy.md). + +#### RAM + +`ram_power` and `ram_energy` are never measured. Commodity hardware exposes no RAM energy +counter. CodeCarbon estimates a DIMM count from the total RAM size, then applies 5 W per +DIMM on x86 (1.5 W on ARM), with decreasing marginal power above four DIMMs and a floor of +two DIMMs' worth (`external/ram.py:82-193`). Treat these columns as an order-of-magnitude +heuristic. If you can measure your own RAM power, override it with `force_ram_power`. + +#### Carbon intensity + +`emissions` is only as good as the carbon intensity behind it, which comes from a fallback +chain documented on the [methodology page](../explanation/methodology.md). No column +records which level answered: check the run's log output if you need to know. + +#### PUE + +PUE multiplies the per-component energy columns, not just the total. At +`emissions_tracker.py:1194` the PUE is applied to each hardware measurement before it is +accumulated. With `pue=1.5`, the `cpu_energy` column is therefore 1.5x the energy the CPU +actually drew: it is datacenter energy attributed to the CPU, not raw CPU energy. The +power columns are not scaled by PUE, so `cpu_energy` will not equal `cpu_power × duration` +when `pue != 1`. `water_consumed` is computed from the already-inflated energy. + +#### Coordinates + +`latitude` and `longitude` are written at full precision in the CSV. Rounding to one +decimal (~11 km) is applied only when data is sent to the CodeCarbon API +(`core/api_client.py:245-246`). If the CSV leaves your machine, treat the coordinates as +precise. !!! note Developers can enhance the Output interface by implementing a custom class that extends `BaseOutput` at `codecarbon/output.py`. For example, to log into a database. 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..8a36cb16c 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 4-second API call 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 19c451331..8b7c9a069 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 @@ -180,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/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/pyproject.toml b/pyproject.toml index 795be29b1..b92c8fb53 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", @@ -157,6 +187,15 @@ version_pattern = "MAJOR.MINOR.PATCH[_TAGNUM]" "codecarbon/_version.py" = [ '^__version__ = "{version}"$', ] +"CITATION.cff" = [ + '^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/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") diff --git a/tests/test_readme_links.py b/tests/test_readme_links.py new file mode 100644 index 000000000..3614c3b44 --- /dev/null +++ b/tests/test_readme_links.py @@ -0,0 +1,26 @@ +"""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): + 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()) + 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}" diff --git a/uv.lock b/uv.lock index 7830e8098..797ff1f68 100644 --- a/uv.lock +++ b/uv.lock @@ -3508,7 +3508,7 @@ wheels = [ [[package]] name = "zensical" -version = "0.0.52" +version = "0.0.53" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "click" }, @@ -3520,20 +3520,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]]