Teads CPU pipeline
The Teads CPU power curve CPU utilization (as a percentage) against a scaling factor that can be applied to the CPUs thermal design power to estimate the power drawn by the CPU in Watts.
The research underpinning the curve was summarized in a pair of blog posts:
TEADS Engineering: Buildiong an AWS EC2 Carbon Emissions Dataset
Teads Engineering: Estimating AWS EC2 Instances Power Consumption
The curve has become very widely used as a general purpose utilization-to-wattage converter for CPUs, despite the fact that it does not geenralize well.
The wattage can be transformed into energy by doing the following:
- Measure your CPU utilization
- Determine the thermal design power of your processor
- Determine the scaling factor for your CPU utilization by interpolating the Teads curve
- Determine the power drawn by your CPU by multiplying your scaling factor by the CPU's thermal design power
- Perform a unit conversion to convert power in Watts to energy in kwH
- Scale the energy estimated for the entire chip to the portion of the chip that is actually in use.
These steps can be executed in IF using just three plugins:
Interpolate
Multiply
Divide
We'll go through each step in the energy estimate and examine how to implement it in a manifest file using IF's standard library of builtins
.
Impact Framework implementation
First, create a manifest file and add this following boilerplate code:
name: carbon-intensity plugin demo
description:
tags:
initialize:
plugins:
tree:
children:
child:
pipeline:
observe:
regroup:
compute:
defaults:
inputs:
If this structure looks unfamiliar to you, you can go back to our manifests page.
Step 1: measure CPU utilization
The first step was to measure your CPU utilization. In real use cases you would typically do this using an importer plugin that grabs data from a monitor API or similar. However, for this example we will just manually create some dummy data. Add some timestamps, durations and cpu/utilization data to your inputs
array, as follows:
name: teads demo
description:
tags:
initialize:
plugins:
tree:
children:
child:
pipeline:
observe:
regroup:
compute:
defaults:
inputs:
- timestamp: 2023-08-06T00:00
duration: 360
cpu/utilization: 1
carbon: 30
- timestamp: 2023-09-06T00:00
duration: 360
carbon: 30
cpu/utilization: 10
- timestamp: 2023-10-06T00:00
duration: 360
carbon: 30
cpu/utilization: 50
- timestamp: 2023-10-06T00:00
duration: 360
carbon: 30
cpu/utilization: 100
Step 2: Determine the thermal design power of your processor
Typically determinign the TDP of your processor would be done using a CSV lookup. For now, we will just hard code some TDP data into your manifest so we can focus on the CPU utilization to energy calculations. Add thermal-design-power
to defaults
- this is a shortcut to providing it in every timestep in your inputs
array.
default:
thermal-design-power: 100
Step 3: Interpolate the Teads curve
The Teads curve has CPU utilization on the x
axis and a scaling factor on the y
axis. There are only four points on the published curve. Your task is to get the scaling factor for your specific CPU utilization values by interpolating between the known points. Luckily, we have a builtin
for that purpose!
Add the Interpolation
plugin to your list of plugins in the initialize
block.
initialize:
plugins:
interpolate:
method: Interpolation
path: builtin
The details about the interpolation you want to do and the values to return are configured in the config
which is also added in the initialize block
. Specifically, you have to provide the known points of the curve you want to interpolate, the input-parameter
(which is the x
value whose correspondiong y
value you want to find out, i.e. your CPU utilization value) and the output-parameter
(the name you want to give to your retrieved y
value).
You want to interpolate the Teads curve, so you can provide the x
and y
values obtained from the articles linked in the introduction section above:
x: [0, 10, 50, 100]
y: [0.12, 0.32, 0.75, 1.02]
Your input-parameter
is your cpu/utilization
and we'll name give the output-parameter
the name cpu-factor
.
Your compelted initialize
block for interpolate
should look as follows:
interpolate:
method: Interpolation
path: 'builtin'
config:
method: linear
x: [0, 10, 50, 100]
y: [0.12, 0.32, 0.75, 1.02]
input-parameter: 'cpu/utilization'
output-parameter: 'cpu-factor'
Step 4: Convert CPU factor to power
The interpoaltion only gives us the scaling factor; we need to apply that scaling factor to the processor's TDP to get the power drawn by the CPU at your specific CPU utilization.
To do this, we can use the Multiply
plugin in the IF standard library. We'll give the instance of Multiply
the name cpu-factor-to-wattage
and in the config
we'll define cpu-factor
and thermal-design-power
as the two elements in our inputs
array that we want to multiply together. Then we'll name the result cpu-wattage
:
cpu-factor-to-wattage:
method: Multiply
path: builtin
config:
input-parameters: ['cpu-factor', 'thermal-design-power']
output-parameter: 'cpu-wattage'
Add this to your initialize
block.
Step 5: Convert wattage to energy
Next we have to perform some unit conversions. Wattage is a measure of power (energy over time). To convert to energy, we can first multiply by the number of seconds our observation covers (duration
) to yield energy in joules. Then, convert to kWh by applying a scaling factor that takes seconds to hours and watts to kilowatts.
You can do this in two steps: the first uses another instance of Multiply
an the second uses Divide
:
To do the initial multiplication of the CPU wattage and the observation duration, add the following config to your initialize
block:
wattage-times-duration:
method: Multiply
path: builtin
config:
input-parameters: ['cpu-wattage', 'duration']
output-parameter: 'cpu-wattage-times-duration'
next, use the Divide
plugin to do the unit conversion:
wattage-to-energy-kwh:
method: Divide
path: 'builtin'
config:
numerator: cpu-wattage-times-duration
denominator: 3600000
output: cpu-energy-raw
Step 6: Scale the energy by the allocated CPUs
The cpu-energy-raw
value you just configured is for the entire chip. But your application probably doesn't use the entire chip. Chances are you have some number of VCPUs allocated to you that is less than the total available. So you can scale your energy estimate by the ratio of VCPUs allocated to VCPUS available.
Let's assume you know the number of VCPUs allocated and available in advance and that they are the same in every timestep. In this case, you can just add the values to defaults
so they become available in every timestep, just as you did with thermal-design-power
.
defaults:
thermal-design-power: 100
vcpus-total: 8
vcpus-allocated: 2
You need one instance of Divide
to calculate the vcpu-ratio
and another to apply that vcpu-ratio
to your cpu-energy-raw
value and yield your final result: cpu-energy-kwh
. Add the following to your initialize
block to achieve those steps:
calculate-vcpu-ratio:
method: Divide
path: 'builtin'
config:
numerator: vcpus-total
denominator: vcpus-allocated
output: vcpu-ratio
correct-cpu-energy-for-vcpu-ratio:
method: Divide
path: 'builtin'
config:
numerator: cpu-energy-raw
denominator: vcpu-ratio
output: cpu-energy-kwh
Step 7: Define your pipeline
Now you have configured all your plugins, covering all the stages of the calculation, you can simple define them in order in the pipeline
section of your manifest, as follows:
tree:
children:
child:
pipeline:
observe:
regroup:
compute:
- interpolate
- cpu-factor-to-wattage
- wattage-times-duration
- wattage-to-energy-kwh
- calculate-vcpu-ratio
- correct-cpu-energy-for-vcpu-ratio
You also need to add some input data that your pipeline can operate over.
You can see the full manifest in the IF repository.
That's it! Your manifest is ready to run!
Running the manifest
Having saved your manifest as teads-curve.yaml
you can run it using IF:
if-run -m teads-curve.yml -o teads-output.yml
This will yield the following output file:
name: teads curve demo
description: null
tags: null
initialize:
plugins:
interpolate:
path: builtin
method: Interpolation
config:
method: linear
x:
- 0
- 10
- 50
- 100
'y':
- 0.12
- 0.32
- 0.75
- 1.02
input-parameter: cpu/utilization
output-parameter: cpu-factor
cpu-factor-to-wattage:
path: builtin
method: Multiply
config:
input-parameters:
- cpu-factor
- thermal-design-power
output-parameter: cpu-wattage
wattage-times-duration:
path: builtin
method: Multiply
config:
input-parameters:
- cpu-wattage
- duration
output-parameter: cpu-wattage-times-duration
wattage-to-energy-kwh:
path: builtin
method: Divide
config:
numerator: cpu-wattage-times-duration
denominator: 3600000
output: cpu-energy-raw
calculate-vcpu-ratio:
path: builtin
method: Divide
config:
numerator: vcpus-total
denominator: vcpus-allocated
output: vcpu-ratio
correct-cpu-energy-for-vcpu-ratio:
path: builtin
method: Divide
config:
numerator: cpu-energy-raw
denominator: vcpu-ratio
output: cpu-energy-kwh
execution:
command: >-
/home/user/.npm/_npx/1bf7c3c15bf47d04/node_modules/.bin/ts-node
/home/user/if/src/index.ts -m manifests/examples/teads-curve.yml
environment:
if-version: 0.6.0
os: macOS
os-version: 14.6.1
node-version: 18.20.4
date-time: 2024-10-03T15:05:11.948Z (UTC)
dependencies:
- '@babel/core@7.22.10'
- '@babel/preset-typescript@7.23.3'
- '@commitlint/cli@18.6.0'
- '@commitlint/config-conventional@18.6.0'
- '@grnsft/if-core@0.0.25'
- '@jest/globals@29.7.0'
- '@types/jest@29.5.8'
- '@types/js-yaml@4.0.9'
- '@types/luxon@3.4.2'
- '@types/node@20.9.0'
- axios-mock-adapter@1.22.0
- axios@1.7.2
- cross-env@7.0.3
- csv-parse@5.5.6
- csv-stringify@6.4.6
- fixpack@4.0.0
- gts@5.2.0
- husky@8.0.3
- jest@29.7.0
- js-yaml@4.1.0
- lint-staged@15.2.2
- luxon@3.4.4
- release-it@16.3.0
- rimraf@5.0.5
- ts-command-line-args@2.5.1
- ts-jest@29.1.1
- typescript-cubic-spline@1.0.1
- typescript@5.2.2
- winston@3.11.0
- zod@3.23.8
status: success
tree:
children:
child:
pipeline:
observe:
regroup:
compute:
- interpolate
- cpu-factor-to-wattage
- wattage-times-duration
- wattage-to-energy-kwh
- calculate-vcpu-ratio
- correct-cpu-energy-for-vcpu-ratio
defaults:
thermal-design-power: 100
vcpus-total: 8
vcpus-allocated: 2
inputs:
- timestamp: 2023-08-06T00:00
duration: 360
cpu/utilization: 1
carbon: 30
- timestamp: 2023-09-06T00:00
duration: 360
carbon: 30
cpu/utilization: 10
- timestamp: 2023-10-06T00:00
duration: 360
carbon: 30
cpu/utilization: 50
- timestamp: 2023-10-06T00:00
duration: 360
carbon: 30
cpu/utilization: 100
outputs:
- timestamp: 2023-08-06T00:00
duration: 360
cpu/utilization: 1
carbon: 30
thermal-design-power: 100
vcpus-total: 8
vcpus-allocated: 2
cpu-factor: 0.13999999999999999
cpu-wattage: 13.999999999999998
cpu-wattage-times-duration: 5039.999999999999
cpu-energy-raw: 0.0013999999999999998
vcpu-ratio: 4
cpu-energy-kwh: 0.00034999999999999994
- timestamp: 2023-09-06T00:00
duration: 360
carbon: 30
cpu/utilization: 10
thermal-design-power: 100
vcpus-total: 8
vcpus-allocated: 2
cpu-factor: 0.32
cpu-wattage: 32
cpu-wattage-times-duration: 11520
cpu-energy-raw: 0.0032
vcpu-ratio: 4
cpu-energy-kwh: 0.0008
- timestamp: 2023-10-06T00:00
duration: 360
carbon: 30
cpu/utilization: 50
thermal-design-power: 100
vcpus-total: 8
vcpus-allocated: 2
cpu-factor: 0.75
cpu-wattage: 75
cpu-wattage-times-duration: 27000
cpu-energy-raw: 0.0075
vcpu-ratio: 4
cpu-energy-kwh: 0.001875
- timestamp: 2023-10-06T00:00
duration: 360
carbon: 30
cpu/utilization: 100
thermal-design-power: 100
vcpus-total: 8
vcpus-allocated: 2
cpu-factor: 1.02
cpu-wattage: 102
cpu-wattage-times-duration: 36720
cpu-energy-raw: 0.0102
vcpu-ratio: 4
cpu-energy-kwh: 0.00255