After successfully installing Arch Linux on my Thinkpad X1 Carbon Generation 6 I began experimenting with laptop component undervolting to reduce heat and improve performance. I was able to undervolt my CPU core, cache, and System Agent successfully.

I encountered some strange behavior while undervolting my Thinkpad X1 Carbon integrated Intel GPU. While undervolting my CPU I expected a system crash, lock-up, or freeze upon reaching a sufficiently large voltage offset.

However, when I set a -999 mV GPU voltage adjustment, my laptop was still stable at idle and/or while running gputest /benchmark and on AC power. Much later I discovered that the machine will reliably crash when attempting to set a -1050 mV offset. This indicates that the GPU voltage offset is actually being applied. The main issue with this is that it seems unlikely that the GPU can actually apply an undervolt of over 900 mV and still function.

Indeed, Francesco Palmarini reported on Github that:

It is kind of impossible that your GPU can work at -1V offset voltage. On XTU I get an instant reboot on -500mV

However, despite the apparent physical impossibility of applying such a large voltage offset, there seems to be a real effect on performance. Applying a -500 mV voltage offset resulted in approximately 2 watts lower peak power consumption as measured by throttled monitoring.

I benchmarked GPU performance at different voltage offsets: test.py

# config.toml

[voltage]
start = 0
stop = -1000
step = -100
# test.py

import toml
from subprocess import run
from multiprocessing import Process
from tempfile import NamedTemporaryFile
from jinja2 import Template
from tqdm import tqdm

def undervolt_gpu(voltage):
    log = f"undervolt___{voltage}.log"
    print(f"undervolt_gpu: saving to {log}")
    with open("lenovo_fix.conf") as fd:
        tpl = Template(fd.read()).render(voltage=voltage)
    with NamedTemporaryFile(mode="w") as ntf:
        ntf.write(tpl)
        ntf.flush()
        cmd = f"sudo /usr/lib/throttled/lenovo_fix.py --monitor --log {log} --config {ntf.name}"
        print("voltage", voltage, "cmd", cmd)
        run(cmd, check=True, shell=True)

if __name__ == "__main__":
    cfg = toml.load("config.toml")
    voltages = list(range(cfg["voltage"]["start"], cfg["voltage"]["stop"], cfg["voltage"]["step"]))
    print(voltages)
    for voltage in tqdm(voltages):
        p = Process(target=undervolt_gpu, args=(voltage,))
        p.start()
        cmd = f"glmark2 -b :duration=2.0 --fullscreen | tee glmark___{voltage}.log"
        print("process launched")
        run(cmd, check=True, shell=True)
        print("process terminating")
        p.terminate()
        print("process terminated")

I generated a CSV report by regex parsing the data logged from glmark2 and lenovo_fix.py: report.py

# report.py

import re
import os.path
import toml
import csv

cfg = toml.load("config.toml")
voltages = list(range(cfg["voltage"]["start"], cfg["voltage"]["stop"], cfg["voltage"]["step"]))

with open('undervolting.csv', 'w', newline='') as csvfile:
    fieldnames = ['glmark', 'voltage', 'package_watts', 'graphics_watts', 'dram_watts']
    writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
    writer.writeheader()
    for voltage in voltages:
        glmark_p = f"glmark___{voltage}.log"
        undervolt_p = f"undervolt___{voltage}.log"
        try:
            with open(glmark_p) as fd:
                try:
                    glmark_s = re.search("(?:glmark2 Score: )(\d+)", fd.read()).group(1)
                except AttributeError:
                    continue
        except FileNotFoundError:
            continue
        try:
            with open(undervolt_p) as fd:
                for line in fd:
                    package, graphics, dram = getattr(re.search(
                        "(?:Package: )(\d*\.?\d+)(?: W - Graphics: )(\d*\.?\d+)(?: W - DRAM: )(\d*\.?\d+)", line
                    ), "groups", lambda: ("0", "0", "0",))()
                    writer.writerow(dict(
                        glmark=glmark_s,
                        voltage=voltage,
                        package_watts=package,
                        graphics_watts=graphics,
                        dram_watts=dram))
        except FileNotFoundError:
            continue

This GPU benchmark performs best and diminishing returns begin around -400 mV GPU voltage offset.

I have presented evidence that throttled GPU voltage offset adjustment has an effect on GPU performance. GPU voltage offset adjustment parameters are not on the same scale as those within the Windows-based Intel XTU utility. The relationship between throttled GPU voltage offset and Intel XTU voltage offset warrants further research to fully unlock the battery life and calculation performance potential of our machines.

To set your GPU voltage offset to -400 mV, edit lenovo_fix.conf which is located at /etc/lenovo_fix.conf on Arch Linux if you installed throttled from the throttled package. Make the following changes:

# ... other settings ...

[UNDERVOLT.BATTERY]
# ... other settings ...
GPU: -400
# ... other settings ...

[UNDERVOLT.AC]
# ... other settings ...
GPU: -400
# ... other settings ...

# ... other settings ...