Skip to content

Commit 8eac358

Browse files
authored
Merge pull request #804 from mlco2/feat/ram_stick_power
[Version 3] New heuristic for RAM
2 parents 1bc0982 + b3e827d commit 8eac358

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

50 files changed

+1220
-440
lines changed

codecarbon/_version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = "3.0.0_rc3"
1+
__version__ = "3.0.0_rc7"

codecarbon/core/cpu.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,14 +66,17 @@ def is_rapl_available() -> bool:
6666
def is_psutil_available():
6767
try:
6868
nice = psutil.cpu_times().nice
69-
if nice > 0.1:
69+
if nice > 0.0001:
7070
return True
7171
else:
72+
logger.debug(
73+
f"is_psutil_available() : psutil.cpu_times().nice is too small : {nice} !"
74+
)
7275
return False
7376
except Exception as e:
7477
logger.debug(
7578
"Not using the psutil interface, an exception occurred while instantiating "
76-
+ f"psutil.cpu_percent : {e}",
79+
+ f"psutil.cpu_times : {e}",
7780
)
7881
return False
7982

codecarbon/core/resource_tracker.py

Lines changed: 32 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,9 @@
44
from codecarbon.core import cpu, gpu, powermetrics
55
from codecarbon.core.config import parse_gpu_ids
66
from codecarbon.core.util import detect_cpu_model, is_linux_os, is_mac_os, is_windows_os
7-
from codecarbon.external.hardware import CPU, GPU, MODE_CPU_LOAD, RAM, AppleSiliconChip
7+
from codecarbon.external.hardware import CPU, GPU, MODE_CPU_LOAD, AppleSiliconChip
88
from codecarbon.external.logger import logger
9+
from codecarbon.external.ram import RAM
910

1011

1112
class ResourceTracker:
@@ -16,23 +17,38 @@ def __init__(self, tracker):
1617

1718
def set_RAM_tracking(self):
1819
logger.info("[setup] RAM Tracking...")
19-
self.ram_tracker = "3 Watts for 8 GB ratio constant"
20-
ram = RAM(tracking_mode=self.tracker._tracking_mode)
20+
if self.tracker._force_ram_power is not None:
21+
self.ram_tracker = (
22+
f"User specified constant: {self.tracker._force_ram_power} Watts"
23+
)
24+
logger.info(
25+
f"Using user-provided RAM power: {self.tracker._force_ram_power} Watts"
26+
)
27+
else:
28+
self.ram_tracker = "RAM power estimation model"
29+
ram = RAM(
30+
tracking_mode=self.tracker._tracking_mode,
31+
force_ram_power=self.tracker._force_ram_power,
32+
)
2133
self.tracker._conf["ram_total_size"] = ram.machine_memory_GB
2234
self.tracker._hardware: List[Union[RAM, CPU, GPU, AppleSiliconChip]] = [ram]
2335

2436
def set_CPU_tracking(self):
2537
logger.info("[setup] CPU Tracking...")
2638
cpu_number = self.tracker._conf.get("cpu_physical_count")
2739
tdp = cpu.TDP()
28-
29-
max_power = tdp.tdp * cpu_number if tdp.tdp is not None else None
30-
if self.tracker._conf.get("force_mode_cpu_load", False) and tdp.tdp is not None:
31-
if tdp.tdp is None:
32-
logger.warning(
33-
"Force CPU load mode requested but TDP could not be calculated. Falling back to another mode."
34-
)
35-
elif cpu.is_psutil_available():
40+
if self.tracker._force_cpu_power is not None:
41+
logger.info(
42+
f"Using user-provided CPU power: {self.tracker._force_cpu_power} Watts"
43+
)
44+
self.cpu_tracker = "User Input TDP constant"
45+
max_power = self.tracker._force_cpu_power
46+
else:
47+
max_power = tdp.tdp * cpu_number if tdp.tdp is not None else None
48+
if self.tracker._conf.get("force_mode_cpu_load", False) and (
49+
tdp.tdp is not None or self.tracker._force_cpu_power is not None
50+
):
51+
if cpu.is_psutil_available():
3652
# Register a CPU with MODE_CPU_LOAD
3753
model = tdp.model
3854
hardware_cpu = CPU.from_utils(
@@ -50,7 +66,7 @@ def set_CPU_tracking(self):
5066
logger.warning(
5167
"Force CPU load mode requested but psutil is not available."
5268
)
53-
if cpu.is_powergadget_available() and self.tracker._default_cpu_power is None:
69+
if cpu.is_powergadget_available() and self.tracker._force_cpu_power is None:
5470
logger.info("Tracking Intel CPU via Power Gadget")
5571
self.cpu_tracker = "Power Gadget"
5672
hardware_cpu = CPU.from_utils(
@@ -73,7 +89,7 @@ def set_CPU_tracking(self):
7389
# change code to check if powermetrics needs to be installed or just sudo setup
7490
elif (
7591
powermetrics.is_powermetrics_available()
76-
and self.tracker._default_cpu_power is None
92+
and self.tracker._force_cpu_power is None
7793
):
7894
logger.info("Tracking Apple CPU and GPU via PowerMetrics")
7995
self.gpu_tracker = "PowerMetrics"
@@ -113,9 +129,9 @@ def set_CPU_tracking(self):
113129
)
114130
self.cpu_tracker = "TDP constant"
115131
model = tdp.model
116-
if (max_power is None) and self.tracker._default_cpu_power:
132+
if (max_power is None) and self.tracker._force_cpu_power:
117133
# We haven't been able to calculate CPU power but user has input a default one. We use it
118-
user_input_power = self.tracker._default_cpu_power
134+
user_input_power = self.tracker._force_cpu_power
119135
logger.debug(f"Using user input TDP: {user_input_power} W")
120136
self.cpu_tracker = "User Input TDP constant"
121137
max_power = user_input_power
@@ -205,7 +221,7 @@ def set_CPU_GPU_ram_tracking(self):
205221
self.set_CPU_tracking()
206222
self.set_GPU_tracking()
207223

208-
logger.debug(
224+
logger.info(
209225
f"""The below tracking methods have been set up:
210226
RAM Tracking Method: {self.ram_tracker}
211227
CPU Tracking Method: {self.cpu_tracker}

codecarbon/core/units.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
from dataclasses import dataclass, field
66

7+
# from pydantic.dataclasses import dataclass, field
8+
79

810
@dataclass
911
class Time:
@@ -61,7 +63,10 @@ class Energy:
6163

6264
@classmethod
6365
def from_power_and_time(cls, *, power: "Power", time: "Time") -> "Energy":
64-
return cls(kWh=power.kW * time.hours)
66+
assert isinstance(power.kW, float)
67+
assert isinstance(time.hours, float)
68+
energy = power.kW * time.hours
69+
return cls(kWh=energy)
6570

6671
@classmethod
6772
def from_ujoules(cls, energy: float) -> "Energy":
@@ -82,7 +87,10 @@ def __add__(self, other: "Energy") -> "Energy":
8287
return Energy(self.kWh + other.kWh)
8388

8489
def __mul__(self, factor: float) -> "Energy":
85-
return Energy(self.kWh * factor)
90+
assert isinstance(factor, float)
91+
assert isinstance(self.kWh, float)
92+
result = Energy(self.kWh * factor)
93+
return result
8694

8795
def __float__(self) -> float:
8896
return float(self.kWh)
@@ -127,7 +135,9 @@ def from_energies_and_delay(cls, e1: "Energy", e2: "Energy", delay: "Time"):
127135
Power: Resulting Power estimation
128136
"""
129137
delta_energy = abs(e2.kWh - e1.kWh)
138+
assert isinstance(delta_energy, float)
130139
kW = delta_energy / delay.hours if delay.hours != 0.0 else 0.0
140+
assert isinstance(delta_energy, float)
131141
return cls(kW=kW)
132142

133143
@classmethod

codecarbon/emissions_tracker.py

Lines changed: 31 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,9 @@
2020
from codecarbon.core.units import Energy, Power, Time
2121
from codecarbon.core.util import count_cpus, count_physical_cpus, suppress
2222
from codecarbon.external.geography import CloudMetadata, GeoMetadata
23-
from codecarbon.external.hardware import CPU, GPU, RAM, AppleSiliconChip
23+
from codecarbon.external.hardware import CPU, GPU, AppleSiliconChip
2424
from codecarbon.external.logger import logger, set_logger_format, set_logger_level
25+
from codecarbon.external.ram import RAM
2526
from codecarbon.external.scheduler import PeriodicScheduler
2627
from codecarbon.external.task import Task
2728
from codecarbon.input import DataSource
@@ -171,7 +172,8 @@ def __init__(
171172
log_level: Optional[Union[int, str]] = _sentinel,
172173
on_csv_write: Optional[str] = _sentinel,
173174
logger_preamble: Optional[str] = _sentinel,
174-
default_cpu_power: Optional[int] = _sentinel,
175+
force_cpu_power: Optional[int] = _sentinel,
176+
force_ram_power: Optional[int] = _sentinel,
175177
pue: Optional[int] = _sentinel,
176178
force_mode_cpu_load: Optional[bool] = _sentinel,
177179
allow_multiple_runs: Optional[bool] = _sentinel,
@@ -227,7 +229,8 @@ def __init__(
227229
Accepts one of "append" or "update". Default is "append".
228230
:param logger_preamble: String to systematically include in the logger.
229231
messages. Defaults to "".
230-
:param default_cpu_power: cpu power to be used as default if the cpu is not known.
232+
:param force_cpu_power: cpu power to be used instead of automatic detection.
233+
:param force_ram_power: ram power to be used instead of automatic detection.
231234
:param pue: PUE (Power Usage Effectiveness) of the datacenter.
232235
:param force_mode_cpu_load: Force the addition of a CPU in MODE_CPU_LOAD
233236
:param allow_multiple_runs: Allow multiple instances of codecarbon running in parallel. Defaults to False.
@@ -277,7 +280,8 @@ def __init__(
277280
self._set_from_conf(tracking_mode, "tracking_mode", "machine")
278281
self._set_from_conf(on_csv_write, "on_csv_write", "append")
279282
self._set_from_conf(logger_preamble, "logger_preamble", "")
280-
self._set_from_conf(default_cpu_power, "default_cpu_power")
283+
self._set_from_conf(force_cpu_power, "force_cpu_power")
284+
self._set_from_conf(force_ram_power, "force_ram_power")
281285
self._set_from_conf(pue, "pue", 1.0, float)
282286
self._set_from_conf(force_mode_cpu_load, "force_mode_cpu_load", False)
283287
self._set_from_conf(
@@ -521,6 +525,15 @@ def flush(self) -> Optional[float]:
521525
but keep running the experiment.
522526
:return: CO2 emissions in kgs
523527
"""
528+
# if another instance of codecarbon is already running, Nothing to do here
529+
if (
530+
hasattr(self, "_another_instance_already_running")
531+
and self._another_instance_already_running
532+
):
533+
logger.warning(
534+
"Another instance of codecarbon is already running. Exiting."
535+
)
536+
return
524537
if self._start_time is None:
525538
logger.error("You first need to start the tracker.")
526539
return None
@@ -996,15 +1009,16 @@ def track_emissions(
9961009
log_level: Optional[Union[int, str]] = _sentinel,
9971010
on_csv_write: Optional[str] = _sentinel,
9981011
logger_preamble: Optional[str] = _sentinel,
999-
default_cpu_power: Optional[int] = _sentinel,
1000-
pue: Optional[int] = _sentinel,
1001-
allow_multiple_runs: Optional[bool] = _sentinel,
10021012
offline: Optional[bool] = _sentinel,
10031013
country_iso_code: Optional[str] = _sentinel,
10041014
region: Optional[str] = _sentinel,
10051015
cloud_provider: Optional[str] = _sentinel,
10061016
cloud_region: Optional[str] = _sentinel,
10071017
country_2letter_iso_code: Optional[str] = _sentinel,
1018+
force_cpu_power: Optional[int] = _sentinel,
1019+
force_ram_power: Optional[int] = _sentinel,
1020+
pue: Optional[int] = _sentinel,
1021+
allow_multiple_runs: Optional[bool] = _sentinel,
10081022
):
10091023
"""
10101024
Decorator that supports both `EmissionsTracker` and `OfflineEmissionsTracker`
@@ -1057,8 +1071,6 @@ def track_emissions(
10571071
Accepts one of "append" or "update". Default is "append".
10581072
:param logger_preamble: String to systematically include in the logger.
10591073
messages. Defaults to "".
1060-
:param default_cpu_power: cpu power to be used as default if the cpu is not known.
1061-
:param pue: PUE (Power Usage Effectiveness) of the datacenter.
10621074
:param allow_multiple_runs: Prevent multiple instances of codecarbon running. Defaults to False.
10631075
:param offline: Indicates if the tracker should be run in offline mode.
10641076
:param country_iso_code: 3 letter ISO Code of the country where the experiment is
@@ -1078,6 +1090,10 @@ def track_emissions(
10781090
See http://api.electricitymap.org/v3/zones for
10791091
a list of codes and their corresponding
10801092
locations.
1093+
:param force_cpu_power: cpu power to be used instead of automatic detection.
1094+
:param force_ram_power: ram power to be used instead of automatic detection.
1095+
:param pue: PUE (Power Usage Effectiveness) of the datacenter.
1096+
:param allow_multiple_runs: Prevent multiple instances of codecarbon running. Defaults to False.
10811097
10821098
:return: The decorated function
10831099
"""
@@ -1109,14 +1125,16 @@ def wrapped_fn(*args, **kwargs):
11091125
log_level=log_level,
11101126
on_csv_write=on_csv_write,
11111127
logger_preamble=logger_preamble,
1112-
default_cpu_power=default_cpu_power,
11131128
pue=pue,
1114-
allow_multiple_runs=allow_multiple_runs,
11151129
country_iso_code=country_iso_code,
11161130
region=region,
11171131
cloud_provider=cloud_provider,
11181132
cloud_region=cloud_region,
11191133
country_2letter_iso_code=country_2letter_iso_code,
1134+
force_cpu_power=force_cpu_power,
1135+
force_ram_power=force_ram_power,
1136+
pue=pue,
1137+
allow_multiple_runs=allow_multiple_runs,
11201138
)
11211139
else:
11221140
tracker = EmissionsTracker(
@@ -1144,7 +1162,8 @@ def wrapped_fn(*args, **kwargs):
11441162
log_level=log_level,
11451163
on_csv_write=on_csv_write,
11461164
logger_preamble=logger_preamble,
1147-
default_cpu_power=default_cpu_power,
1165+
force_cpu_power=force_cpu_power,
1166+
force_ram_power=force_ram_power,
11481167
pue=pue,
11491168
allow_multiple_runs=allow_multiple_runs,
11501169
)

0 commit comments

Comments
 (0)