EcoCompute is an open reproducibility project measuring how LLM quantization affects inference energy per token across GPUs and model sizes. This page is about the part you can run yourself: the container that produced those measurements.
energy.json
Apache-2.0
Not a certified benchmark
energy.json either lands on the
curve published here or visibly does not. Both outcomes are useful; the second one is more useful.
中文读者:逐步的容器使用手册见 EcoCompute 容器数据测量 · 中文使用手册。
NVML on-device power sampling at 10 Hz over the decode phase, 256 tokens per run with warmup, repeated for the configured iterations. Energy, average power and throughput come out of that trace — no datasheet wattage, no wall-meter guesswork.
No GPU, or NVML power telemetry unavailable? The run still completes, but the report is derived from the
published dataset and labelled basis ≠ measured with the fallback named in
measurement_source. A fallback is never dressed up as a measurement.
An MLCube-compatible
descriptor with one task, energy_estimate, exercised with the official mlcube CLI on
the Docker platform; a CPU descriptor verifies the build/mount/report contract without a GPU. Dependencies are
pinned with a full transitive lock, because bitsandbytes and torch change NF4/INT8 kernels between releases.
Every report validates against
schema/energy.schema.json
and carries its own scope: certified_benchmark_result: false, plus a scenario_note
saying the SingleStream/Offline label is nominal and not enforced by LoadGen.
“Running the project” means the container row: release schema-1.3-r3, whose reports validate as ecocompute-energy/1.3 against Protocol v1.1. The full version-by-version correspondence lives in the protocol’s §8.
| Layer | Current version | DOI / release | Role |
|---|---|---|---|
| Protocol | v1.1 | 10.5281/zenodo.22958675 | Normative requirements |
| Report schema | ecocompute-energy/1.3 | GitHub release schema-1.3-r3 | Machine validation |
| Container | schema-1.3-r3 | GitHub release · image digest in SHA256SUMS | Executable measurement |
| Main dataset | v1.1.0 | 10.5281/zenodo.19647290 | Published measurements (360+ configurations) |
| Supplement | RTX 4090 deep dive v3 | 10.5281/zenodo.22037483 | Deep re-test (Ada): repeatability, paired perplexity |
Prerequisite for a real measurement: an NVIDIA GPU with NVML power telemetry. Docker is optional — see the two modes below. Without a GPU you can still verify the whole pipeline — see step 3.
No GPU at all? The
Colab notebook
(source)
runs the same NVML measurement on a free T4 and emits the same schema-validated energy.json. It is a
re-implementation of entrypoint.py's sampler rather than the container itself, so it is not pinned to
the image's dependency set, and it measures both arms in randomized order with a cooldown to idle before each — a
protocol the container does not enforce. On a 16 GB T4 both arms fit up to about 3B parameters.
curl -fsSLO https://github.com/hongping-zh/ecocompute-mlcube/releases/download/schema-1.3-r3/quickstart.sh curl -fsSLO https://github.com/hongping-zh/ecocompute-mlcube/releases/download/schema-1.3-r3/SHA256SUMS sha256sum -c SHA256SUMS bash quickstart.sh
It measures NF4 against its own FP16 baseline on TinyLlama-1.1B, writes a schema-validated
energy.json into ./ecocompute-out/, and prints the overlay link. The script prints every
command it runs — and bash quickstart.sh --dry-run prints the whole plan (mode, every directory it
would touch, the exact commands) and exits without doing anything.
Why four lines instead of curl | bash: piping a moving branch into a shell means
executing whatever main holds at that moment, with no version, no checksum and no signature. Here you
download from an immutable release,
verify the SHA-256 against the published SHA256SUMS, and only then execute. The same release also
carries a CycloneDX SBOM (from the pinned requirements.lock.txt) and the image
digest below. Two more properties travel with the script: the container never runs as root — the
image's default user is unprivileged and the script always passes --user $(id -u):$(id -g) — and
ECOCOMPUTE_READONLY=1 optionally adds --read-only plus a tmpfs /tmp, leaving
only the two bind mounts writable. What the script changes on your host is written out in full in its header:
the output dir, the model cache, and (native mode only) a checkout + venv on the data disk — nothing else.
Pin the image by digest (instead of the mutable :latest) for a fully reproducible run:
ECOCOMPUTE_IMAGE=ghcr.io/hongping-zh/ecocompute-mlcube@sha256:5c812e66c34fcdab436bdcf4c1135dc91a6a86b1f94d3efb6118947a7492e275 \ ECOCOMPUTE_REF=schema-1.3-r3 bash quickstart.sh
The release page lists the full SHA-256 values of the schema, the validator, the quickstart and the SBOM, plus the release commit and the CI build that produced the image.
Time: the measurement is a few minutes; the first run is dominated by downloads and everything is cached
afterwards. The one end-to-end run we have timed took 57 minutes on a China-hosted rented RTX 4090 in
native mode — 5.5 of those were the ~2 GB model, nearly all the rest was ~3 GB of torch/CUDA wheels from
download.pytorch.org. The setup script now prefers the host's configured (domestic) package index for
torch, which should remove most of that, but we have not re-timed it, and we have not timed the docker path at all.
It picks one of two modes and tells you which:
--gpus all; nothing is installed on the host.entrypoint.py, same report.Or drive the image directly (the default user is unprivileged; passing --user makes the report
land in a directory you own):
mkdir -p out
docker run --rm --gpus all --user "$(id -u):$(id -g)" \
-v "$PWD/out:/workspace/outputs" \
ghcr.io/hongping-zh/ecocompute-mlcube:latest energy_estimate \
--model TinyLlama/TinyLlama-1.1B-Chat-v1.0 --params_b 1.1 \
--precision NF4 --gpu_arch auto --output_dir /workspace/outputs --share
git clone https://github.com/hongping-zh/ecocompute-mlcube.git
cd ecocompute-mlcube
# with the official MLCommons MLCube CLI (task name: energy_estimate)
pip install mlcube mlcube-docker
mlcube run --mlcube=. --task=energy_estimate --platform=docker
# ...or drive the entrypoint directly (development / CI)
python3 entrypoint.py energy_estimate \
--parameters_file workspace/parameters/energy_params.yaml \
--output_dir workspace/outputs
cat workspace/outputs/energy.json
A GPU measured straight off idle and the same GPU measured after ten minutes of load are two different
machines: the hot one clocks lower and draws more, so it reports more energy per token. Since protocol
ecocompute-protocol/1.1 the report says which one you measured.
--thermal_mode cold # default: a fixed warmup, then measure — matches every published EcoCompute number --thermal_mode steady # warm up until the die temperature stops rising, then measure
steady runs warmup passes one second apart until three consecutive temperature readings fall
within 2 °C, then starts sampling; warmup energy is never counted in the result. If the budget runs out
first the report says steady_state_reached: false and keeps the temperatures it did observe, rather
than claiming a steady state it never reached. The two modes are comparable within a mode and not across
one — and the 42 seed measurements predate the field entirely, so their thermal state is unknown, not cold.
That is why the default stayed cold.
python3 entrypoint.py energy_estimate \
--model TinyLlama/TinyLlama-1.1B-Chat-v1.0 \
--precision NF4 --gpu_arch auto --params_b 1.1 \
--output_dir workspace/outputs --prefetch --share
--prefetch — asks the site estimator for its prediction before measuring and prints it,
so you see prediction vs. measurement side by side. Offline or slow? It is skipped silently; the measurement is
never blocked or altered.--share — prints (and writes share_url.txt) a link of the form
quantenergy.tech/?tab=run&overlay=…. Your point is encoded in the URL itself: opening it
restores your marker on the crossover curve entirely in the browser. Nothing is uploaded and no server stores
anything.One JSON report per configuration, validated against
schema/energy.schema.json.
The two fields this site plots are results.vs_fp16_energy_pct and results.basis:
| Field | Meaning |
|---|---|
system_under_test.gpu / .gpu_arch | Card and architecture class (turing, ampere, ada, blackwell) — selects which fitted curve your point is compared against |
workload.* | model_name, params_b, precision (FP16 / NF4 / INT8, weight-only), batch_size, context_length |
measurement.* | method (NVML on-device power sampling), sample_rate_hz (requested) and achieved_sample_rate_hz (what the loop managed), tokens_per_run, iterations, thermal_mode |
thermal.* | The thermal state the number was taken in: warmup_iterations, gpu_temp_at_start_c, gpu_temp_steady_c, gpu_temp_end_c, gpu_temp_max_c, steady_state_reached. basis: "unavailable" on cards whose driver reports no temperature — never a guessed number |
measurement_source | Where the numbers actually came from — a direct NVML run, or a named dataset fallback |
results.total_energy_joules, energy_per_token_mj, avg_power_watts, throughput_tokens_per_s | Integrated over the generation window, per configuration |
results.vs_fp16_energy_pct | Signed ΔE% against the same model's own FP16 run on the same GPU. Negative = quantization saves energy — this is the value plotted on the crossover curve |
results.basis | measured / interpolated / extrapolated — the tag shown next to every number on this site |
certified_benchmark_result, scenario_note | The report states its own scope: false, and the SingleStream/Offline label is nominal, not LoadGen-enforced |
With --dry_run, or when no NVIDIA GPU / NVML is present, the container emits values derived from
the published EcoCompute dataset, flagged in measurement_source and never labelled
measured.
Some consumer, vGPU and Turing cards return NVML_ERROR_NOT_SUPPORTED. The on-device path is
probed first; on failure the run falls back to the dataset path with the reason recorded — it does not crash and
it does not claim a measurement.
A single failed power read never aborts a run; the count lands in results.dropped_samples so you
can judge the trace quality yourself.
A CPU-only descriptor (mlcube.cpu.yaml + Dockerfile.cpu) builds the image, mounts
the workspace and produces a schema-valid report, so the MLCube contract can be verified on any laptop:
mlcube run --mlcube=mlcube.cpu.yaml --task=energy_estimate --platform=docker.
Determinism matters here for a specific reason: bitsandbytes NF4/INT8 kernels and torch
change their numeric behaviour between releases. Top-level dependencies are pinned with exact ==
versions and the image installs from a full transitive lock captured from the verified CUDA image.
The container was exercised with MLCommons' mlcube CLI (v0.0.9, docker platform):
mlcube describe accepts the descriptor: task energy_estimate, inputs=[parameters_file], outputs=[output_dir].mlcube run on the CPU descriptor builds the image, mounts the workspace and writes workspace/outputs/energy.json.bert-base-uncased, batch_size=32 → Offline scenario).measured.The newest column here — RTX 4090 (Ada), July 2026, five models from 0.5B to 7B × FP16 / NF4 / INT8, all
fifteen configurations real hardware measurements — was produced by this container, driven by the sweep scripts in
autodl/
(setup → environment verification → full model × precision sweep → aggregation). That run extended Ada coverage to
INT8 and to 7B for the first time, and it is archived as its own citable artifact:
10.5281/zenodo.22037483 (CC BY 4.0).
It is a supplementary deep dive alongside the main
v1.1.0 dataset, not a replacement:
n = 1 per configuration.
| Do | Don't |
|---|---|
| Compare precisions on the same GPU | Compare across GPUs without normalising thermal state and clocks |
Treat basis: "measured" as ground truth for that configuration | Cite interpolated / extrapolated values for compliance or procurement claims |
| Read the numbers as GPU-package energy | Present them as whole-system, datacenter or carbon figures without a PUE and grid model |
Report iterations and the observed spread downstream | Publish an n = 1 number as if it were a tight distribution |
scenario field is a nominal label derived from batch size; the container
runs its own warmup/iterations loop and applies no MLPerf LoadGen timing constraints.certified_benchmark_result: false.So the roadmap is not mistaken for the product — these are wanted, but they do not exist today:
linux/amd64, single-GPU
(accelerator_count: 1); it does not measure multi-card or tensor-parallel serving.We are looking for collaborators to reproduce EcoCompute measurements across additional GPUs, cloud instances, model families and quantization backends. If you operate NVIDIA, AMD or other accelerator infrastructure and want to contribute comparable energy-per-token data, please get in touch.
Most valuable, in order:
energy.json
attached is the single most useful thing you can send.If you have no GPU to spare, the Colab notebook produces a submittable report on a free T4 in well under an hour.
How: use the submission form — it parses your energy.json in your
browser, previews your point against the fitted curve, then copies the submission to your clipboard and opens an issue you paste into and send yourself. You can also open
an issue or pull request directly on
the container repository
with your energy.json (and the --share link if you have one), or reach the maintainer via
hongping-zh.github.io.
Accepted submissions appear in the public replications gallery, moderated for schema and
format only — never for results.
@software{ecocompute_mlcube,
title = {EcoCompute energy MLCube: reproducible LLM inference energy measurement},
author = {Zhang, Hongping},
year = {2026},
url = {https://github.com/hongping-zh/ecocompute-mlcube},
license = {Apache-2.0}
}