RTX 5090:n 12V-2×6-virtaliitin mittasi lokakuussa 2026 lämpötilaksi 91,7 °C 647 watin huipputeholla, kun kortti ajoi paikallista tekoälymallia täydellä kuormalla. Tieto tuli esiin vasta jälkikäteen videokortteja seuraavan VideoCardz-sivuston raportissa, ei käyttäjän omalta näytöltä. Suurin osa pelaajista ja kotikäyttäjistä, jotka ajavat Ollamaa tai LM Studiota RTX-näytönohjaimella, ei näe reaaliaikaista lämpötila-, teho- tai kellotaajuusdataa mistään. Tässä oppaassa rakennetaan oma Python-pohjainen GPU-monitorointi- ja benchmark-työkalu, joka lukee NVIDIA Management Libraryn (NVML) kautta kortin tilan, kirjoittaa sen CSV-lokiin ja mittaa samalla paikallisen kielimallin tokens-per-second-nopeuden. Lopputulos on käyttövalmis GPU-benchmark-skripti, joka toimii yhtä hyvin RTX 5090:llä kuin RTX 5060:lla.

Suomessa ja muualla Pohjoismaissa paikallisen tekoälyn ajaminen omalla koneella on yleistynyt sitä mukaa, kun RTX 50 -sarjan kortit ovat tulleet saataville laajemmin kuluttajamarkkinoille. Moni kehittäjä ajaa Ollamaa tai LM Studiota kotikoneella pelkästään siksi, että pilvipohjaisen API:n käyttö tulee kalliiksi jatkuvassa käytössä, mutta harva tietää, paljonko se todella maksaa sähkönä tai kuinka lähellä korttinsa on tehorajaansa. Tämä 11. lokakuuta 2026 päivitetty opas antaa työkalut, joilla vastaus saadaan numeroina, ei arvauksina. Koodi on kirjoitettu niin, että sen voi kopioida suoraan käyttöön eikä se vaadi ulkoisia palveluita tai maksullisia rajapintoja, ainoastaan paikallisesti asennetut kirjastot ja käynnissä olevan Ollaman tai LM Studion.

Miksi oma GPU-benchmark-työkalu kannattaa rakentaa itse

Valmiit monitorointisovellukset näyttävät kuvaajan ruudulla, mutta harva tallentaa dataa koneellisesti luettavaan muotoon. Kun ajat paikallista tekoälymallia tuntien ajan, haluat tietää keskilämpötilan, huipputehon ja watit per tuotettu token, ei vain hetkellistä lukemaa. Nvidia julkaisi 25. syyskuuta 2026 nvidia-ml-py-kirjaston version 13.615.71 PyPI:hin, ja se on juuri se rajapinta, jolla oma skripti kannattaa rakentaa NVML:n päälle. Pakettinimi on uudistunut, mutta Python-moduuli, jonka koodissa tuodaan, on edelleen pynvml, joten vanhat esimerkit eivät ole suoraan vääriä, vain nimeämiskäytäntö on muuttunut.

Toinen syy rakentaa oma gpu monitorointi pythonilla -ratkaisu on toistettavuus. Kun MyAIHardware-sivusto julkaisi RTX 5090 -kortin paikalliset LLM-benchmarkit, se mittasi 17 mallia Ollama 0.32.1:llä ja 5-10 ajoa per malli, ja tulokset vaihtelivat samalla mallilla jopa 397-454 tokens sekunnissa riippuen ajosta. Ilman tarkkaa lokia ajurin versiosta, kvantisoinnista ja kontekstin pituudesta mikään yksittäinen luku ei kerro mitään luotettavaa. Stack Overflow’n vuoden 2025 kehittäjäkyselyn mukaan Python oli 57,9 prosentin vastaajien käyttämä kieli, ja 84 prosenttia kertoi käyttävänsä tai suunnittelevansa tekoälytyökalujen käyttöä kehitystyössä. Juuri siksi Python on luonnollinen valinta myös laitteiston mittaamiseen: kirjastot ovat valmiina, ja skriptin voi ajaa samalla koneella kuin mallin.

Ajurin ja CUDA-version lokittaminen ei ole pelkkää byrokratiaa. Nvidia julkaisi 29. syyskuuta 2026 uuden GeForce Game Ready -ajurin, joka tuo tuen muun muassa Call of Duty: Modern Warfare 4:lle ja Microsoft Flight Simulator 2024:n DLSS 4.5 -päivitykselle. Samalla ajuripäivitys voi muuttaa myös tehonhallinnan ja kellotaajuuksien käyttäytymistä taustalla, vaikka pelituki olisi muutoksen näkyvin osa. Jos benchmarkkaa samaa mallia kahdella eri ajuriversiolla eikä tätä tietoa ole tallennettu, tulosten erosta on mahdotonta sanoa, johtuuko se ajurista, lämpötilasta vai jostain muusta. Tästä syystä jokaisen GPU-benchmark-ajon lokiin kannattaa kirjata ajuriversio, CUDA-versio ja NVML-headerin versio, ei vain itse mittaustulosta.

Mitä tässä oppaassa rakennetaan

Lopputuloksena syntyy kolmiosainen työkalu. Ensimmäinen osa lukee GPU:n lämpötilan, tehonkulutuksen, kellotaajuudet ja VRAM-käytön NVML:n kautta kahden sekunnin välein ja kirjoittaa ne CSV-tiedostoon. Toinen osa piirtää kerätystä datasta kuvaajan matplotlib:lla ja hälyttää, jos lämpötila tai teho ylittää asetetun rajan. Kolmas osa mittaa paikallisen tekoälymallin tokens/s-nopeuden Ollaman API:n kautta ja yhdistää sen samaan raporttiin GPU-telemetrian kanssa. Kaikki kolme osaa toimivat myös erikseen, mutta yhdessä ne muodostavat valmiin gpu benchmark -työkalun, joka sopii RTX 5090:stä RTX 5050:een ja toimii Windows 11:ssä sekä Ubuntussa.

Tämä opas ei käsittele itse mallien lataamista, kvantisointia tai Ollaman asennusta, koska nämä on käyty läpi erikseen muissa oppaissa. Oletuksena on, että Ollama tai LM Studio on jo asennettu ja ainakin yksi malli on ladattuna paikallisesti. Oppaan koodi ei myöskään vaadi internetyhteyttä NVML-osuudessa, mutta Ollaman API-kutsut tarvitsevat tietysti toimivan paikallisen palvelun portissa 11434.

Minkä RTX-kortin kannattaa valita benchmarkkaukseen

Tämä opas toimii samalla koodilla jokaisella RTX 50 -sarjan kortilla, mutta kortin valinta vaikuttaa siihen, mitä kannattaa mitata. RTX 5090:n 32 gigatavun VRAM riittää 70 miljardin parametrin malleihin yksittäisellä kortilla, mutta 575 watin tehobudjetti tarkoittaa, että watit per tuotettu token -mittari on erityisen tärkeä, jos kortti pyörii jatkuvasti. RTX 5080 ja RTX 5070 tarjoavat huomattavasti maltillisemman tehonkulutuksen, mutta RTX 5080:n 16 gigatavun ja RTX 5070:n 12 gigatavun VRAM rajoittaa käytännössä 13-14 miljardin parametrin malleihin kohtuullisella kvantisoinnilla. RTX 5060- ja RTX 5050-sarjan kortit sopivat pienempiin 7-8 miljardin parametrin malleihin, ja niillä watit per token -lukema on usein parempi kuin huipputehokkailla korteilla, koska tehobudjetti on pienempi suhteessa saavutettuun nopeuteen.

Jos tavoitteena on nimenomaan benchmarkata tehokkuutta, ei vain raakaa nopeutta, kannattaa ajaa samaa mallia ja kvantisointia useammalla kortilla, jos niitä on käytettävissä, ja verrata watit per token -lukuja keskenään. Tämä on juuri se mittari, joka puuttuu useimmista julkisista benchmark-taulukoista, ja se on samalla yksi parhaista syistä rakentaa oma gpu monitorointi pythonilla -työkalu sen sijaan, että luottaisi pelkästään toisten julkaisemiin tokens/s-lukuihin. Jos olet päivittämässä vanhempaa korttia, kannattaa ajaa sama benchmark-skripti ennen ja jälkeen päivityksen, jotta tiedät tarkalleen, mitä uudesta kortista käytännössä saa lisää, ei vain mitä valmistaja lupaa markkinointimateriaalissa.

Esivaatimukset: laitteisto ja ohjelmistoversiot

Tarkista seuraavat versiot ennen aloitusta. Nvidia ylläpitää ajuri- ja CUDA-yhteensopivuustaulukkoa omalla sivustollaan, ja juuri sitä kannattaa tarkistaa ennen asennusta, koska versiot päivittyvät muutaman kuukauden välein. Taulukko kannattaa tarkistaa myös silloin, kun jokin osa tästä oppaasta ei toimi odotetusti, koska suurin osa NVML-virheistä juontuu juuri ajurin, CUDA-version ja Python-kirjaston yhteensopivuusongelmista, ei itse koodista.

# Tarkista asennetut versiot nopeasti
pip show nvidia-ml-py
nvidia-smi --query-gpu=driver_version,name --format=csv
nvcc --version
KomponenttiSuositeltu versio (loka 2026)Huomio
Python3.10 tai uudempiKäytä erillistä virtuaaliympäristöä
nvidia-ml-py (PyPI)13.615.71Julkaistu 25.9.2026, tuo moduulin pynvml
CUDA Toolkit13.4.2Julkaistu syyskuussa 2026
NVML Headers13.4.92Sisältyy CUDA 13.4 -pakettiin
GeForce-ajuriGame Ready, julkaistu 29.9.2026Tarkista versio ajurisivulta ennen mittausta
Ollama0.32.1 tai uudempiPaikallisen mallin ajoon ja benchmarkkaukseen
KäyttöjärjestelmäWindows 11 tai Ubuntu 22.04+NVML toimii molemmilla, WSL2:ssa osittain rajoitetusti
GPURTX 50 -sarja (5090/5080/5070/5060/5050)Toimii myös RTX 40- ja 30-sarjalla

NVML vai nvidia-smi? Kaksi tapaa lukea GPU:ta

Nvidia-smi on komentorivityökalu, joka tulostaa GPU:n tilan tekstimuodossa. Sitä voi kutsua Pythonista subprocess-moduulilla, mutta jokainen kutsu käynnistää uuden prosessin ja jäsentää tekstiä, mikä on hidasta jatkuvassa silmukassa. NVML sen sijaan on kirjasto, jota vasten nvidia-smi itsekin on rakennettu, ja nvidia-ml-py tarjoaa siihen suoran Python-rajapinnan ilman tekstin jäsentämistä. Käytännön sääntö on yksinkertainen: käytä NVML:ää, kun ajat jatkuvaa monitorointisilmukkaa, ja pidä nvidia-smi-kutsu varamenetelmänä, joka toimii myös silloin, kun NVML-kirjastoa ei ole asennettu tai se heittää virheen tuntemattomasta syystä.

Yksi käytännön yksityiskohta kannattaa huomioida jo tässä vaiheessa: Pythonin GIL (Global Interpreter Lock) ei haittaa tätä käyttötapausta, koska NVML-kutsut ja HTTP-pyynnöt Ollaman API:in ovat molemmat niin kutsuttuja I/O-operaatioita. Kun rakennat vaiheessa 9 rinnakkaisen mittaussäikeen, se ei kilpaile merkittävästi suoritinajasta itse benchmark-pyynnön kanssa, koska molemmat odottavat suurimman osan ajasta vastausta käyttöjärjestelmältä tai verkosta, ei suorita raskasta laskentaa Python-tulkin sisällä.

Vaihe 1: Python-ympäristön pystyttäminen

Luo projektikansio ja erillinen virtuaaliympäristö, jotta GPU-kirjastot eivät sekoitu muihin projekteihin.

mkdir gpu-monitor
cd gpu-monitor
python -m venv venv

# Windows
venv\Scripts\activate

# Linux / macOS
source venv/bin/activate

python --version

Windowsilla venv\Scripts\activate-komento voi epäonnistua PowerShellin oletusasetuksilla virheellä, joka liittyy skriptien suoritusoikeuksiin. Tämä ei ole virhe itse Python-asennuksessa, vaan PowerShellin suojausasetus, joka estää allekirjoittamattomien skriptien ajamisen oletuksena. Ratkaisu on sallia skriptien ajo nykyiselle käyttäjälle komennolla Set-ExecutionPolicy -Scope CurrentUser RemoteSigned, jonka jälkeen aktivointi toimii normaalisti.

Vaihe 2: NVML-kirjaston asennus

Asenna nvidia-ml-py ja matplotlib graafeja varten. Testaa lopuksi, että NVML löytää näytönohjaimen.

pip install nvidia-ml-py matplotlib requests

python -c "import pynvml; pynvml.nvmlInit(); print(pynvml.nvmlDeviceGetName(pynvml.nvmlDeviceGetHandleByIndex(0))); pynvml.nvmlShutdown()"

Jos komento tulostaa GPU:n nimen, kirjasto toimii. Jos saat virheen “NVMLError_LibraryNotFound”, ajuri puuttuu tai sitä ei löydy PATH-muuttujasta. Katso vianmääritysosio myöhemmin tässä oppaassa.

Vaihe 3: Ensimmäiset GPU-lukemat NVML:llä

Seuraava skripti lukee lämpötilan, tehonkulutuksen, kellotaajuudet ja VRAM-käytön yhdellä kertaa.

import pynvml

pynvml.nvmlInit()
handle = pynvml.nvmlDeviceGetHandleByIndex(0)

name = pynvml.nvmlDeviceGetName(handle)
temp = pynvml.nvmlDeviceGetTemperature(handle, pynvml.NVML_TEMPERATURE_GPU)
power_w = pynvml.nvmlDeviceGetPowerUsage(handle) / 1000
power_limit_w = pynvml.nvmlDeviceGetPowerManagementLimit(handle) / 1000
util = pynvml.nvmlDeviceGetUtilizationRates(handle)
mem = pynvml.nvmlDeviceGetMemoryInfo(handle)
clock_core = pynvml.nvmlDeviceGetClockInfo(handle, pynvml.NVML_CLOCK_GRAPHICS)
clock_mem = pynvml.nvmlDeviceGetClockInfo(handle, pynvml.NVML_CLOCK_MEM)
driver = pynvml.nvmlSystemGetDriverVersion()

print(f"GPU: {name} | Ajuri: {driver}")
print(f"Lampotila: {temp} C | Teho: {power_w:.1f}/{power_limit_w:.0f} W")
print(f"Kuorma: GPU {util.gpu}% / Muisti {util.memory}%")
print(f"VRAM: {mem.used / 1024**3:.1f} / {mem.total / 1024**3:.1f} GB")
print(f"Kellot: ydin {clock_core} MHz, muisti {clock_mem} MHz")

pynvml.nvmlShutdown()

Esimerkkituloste RTX 5090 -kortilla joutokäynnissä näyttää tältä:

GPU: NVIDIA GeForce RTX 5090 | Ajuri: 581.42
Lampotila: 42 C | Teho: 38.0/575 W
Kuorma: GPU 2% / Muisti 4%
VRAM: 1.8 / 32.0 GB
Kellot: ydin 210 MHz, muisti 405 MHz

Sama skripti täyden tekoälykuorman aikana näyttää huomattavasti erilaiselta. Ero joutokäynnin ja kuormituksen välillä on juuri se, minkä jatkuva monitorointi tuo esiin paremmin kuin yksittäinen lukukerta.

GPU: NVIDIA GeForce RTX 5090 | Ajuri: 581.42
Lampotila: 74 C | Teho: 521.0/575 W
Kuorma: GPU 98% / Muisti 71%
VRAM: 19.4 / 32.0 GB
Kellot: ydin 2520 MHz, muisti 1750 MHz

Vaihe 4: nvidia-smi varamenetelmänä

Pidä aina varamenetelmä, jos NVML-kutsu epäonnistuu esimerkiksi ajuripäivityksen jälkeen. Tämä funktio kutsuu nvidia-smi:tä suoraan ja jäsentää tuloksen CSV-muodosta.

import subprocess

def read_via_nvidia_smi():
    query = "temperature.gpu,power.draw,utilization.gpu,memory.used"
    cmd = ["nvidia-smi", f"--query-gpu={query}", "--format=csv,noheader,nounits"]
    result = subprocess.run(cmd, capture_output=True, text=True, timeout=5)
    temp, power, util, mem_used = result.stdout.strip().split(", ")
    return {
        "temp_c": float(temp),
        "power_w": float(power),
        "util_pct": float(util),
        "mem_used_mb": float(mem_used),
    }

print(read_via_nvidia_smi())

Vaihe 5: Jatkuva monitorointisilmukka ja CSV-lokitus

Tämä on työkalun ydin: silmukka, joka lukee arvot kahden sekunnin välein ja kirjoittaa ne tiedostoon aikaleiman kanssa. Kahden sekunnin väli on tasapaino tarkkuuden ja CPU-kuorman välillä. Alle 0,5 sekunnin mittausväli alkaa jo itsessään kuormittaa järjestelmää turhaan.

import csv
import time
import pynvml
from datetime import datetime

def monitor_loop(duration_s=300, interval_s=2.0, out_file="gpu_log.csv"):
    pynvml.nvmlInit()
    handle = pynvml.nvmlDeviceGetHandleByIndex(0)
    driver = pynvml.nvmlSystemGetDriverVersion()

    with open(out_file, "w", newline="") as f:
        writer = csv.writer(f)
        writer.writerow(["timestamp", "driver", "temp_c", "power_w", "gpu_pct", "mem_pct", "vram_gb"])

        started = time.time()
        while time.time() - started < duration_s:
            temp = pynvml.nvmlDeviceGetTemperature(handle, pynvml.NVML_TEMPERATURE_GPU)
            power = pynvml.nvmlDeviceGetPowerUsage(handle) / 1000
            util = pynvml.nvmlDeviceGetUtilizationRates(handle)
            mem = pynvml.nvmlDeviceGetMemoryInfo(handle)

            writer.writerow([
                datetime.now().isoformat(),
                driver, temp, round(power, 1),
                util.gpu, util.memory,
                round(mem.used / 1024**3, 2),
            ])
            f.flush()
            time.sleep(interval_s)

    pynvml.nvmlShutdown()

monitor_loop(duration_s=600, interval_s=2.0)

Vaihe 6: Tulosten visualisointi matplotlib:lla

Kun CSV-tiedosto on kerätty, piirrä lämpötila ja teho samaan kuvaajaan ajan funktiona. Tämä paljastaa nopeasti, nouseeko lämpötila tasaisesti vai hyppääkö se piikkeinä, mikä kertoo usein eri asiasta kuin keskiarvo.

import csv
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt

timestamps, temps, powers = [], [], []
with open("gpu_log.csv") as f:
    for row in csv.DictReader(f):
        timestamps.append(row["timestamp"][11:19])
        temps.append(float(row["temp_c"]))
        powers.append(float(row["power_w"]))

fig, ax1 = plt.subplots(figsize=(10, 5))
ax1.plot(temps, color="tab:red", label="Lampotila (C)")
ax1.set_ylabel("Lampotila (C)")

ax2 = ax1.twinx()
ax2.plot(powers, color="tab:blue", label="Teho (W)")
ax2.set_ylabel("Teho (W)")

plt.title("GPU-telemetria ajan funktiona")
fig.tight_layout()
plt.savefig("gpu_report.png", dpi=150)
print("Tallennettu: gpu_report.png")

Headless-palvelimella tai ilman näyttöä ajettaessa matplotlib kaatuu oletusbackendillä. Siksi skripti asettaa Agg-backendin ennen pyplot-tuontia, jolloin kuvaaja tallentuu suoraan tiedostoon ikkunaa avaamatta.

Kahden y-akselin kuvaaja on hyvä lähtökohta, mutta kun dataa kertyy useamman ajon verran, kannattaa lisätä kolmas subplot VRAM-käytölle omaan ruutuunsa, koska lämpötilan ja tehon asteikko eroaa VRAM-gigatavuista niin paljon, että ne eivät mahdu luettavasti samaan kuvaajaan. Matplotlibin plt.subplots(3, 1)-kutsu ja kolme erillistä ax-oliota riittävät tähän, ja koodi pysyy silti alle 40 rivissä.

Vaihe 7: Hälytysrajat ylikuumenemiselle ja tehopiikeille

RTX 5090:n raportoitu korttikohtainen enimmäiskulutus on 575 wattia. Lokakuun 2026 VideoCardz-mittauksessa virtaliittimen lämpötila nousi 91,7 celsiusasteeseen 647 watin huipputeholla. Tällaiset arvot kannattaa merkitä poikkeukselliseksi mittaustulokseksi, ei normaaliksi käyttöarvoksi, mutta ne ovat hyvä perusta omille hälytysrajoille.

def check_alerts(temp_c, power_w, power_limit_w, temp_limit=85, power_margin=0.95):
    alerts = []
    if temp_c >= temp_limit:
        alerts.append(f"VAROITUS: lampotila {temp_c} C ylitti rajan {temp_limit} C")
    if power_w >= power_limit_w * power_margin:
        alerts.append(f"VAROITUS: teho {power_w:.0f} W lahella tehobudjettia {power_limit_w:.0f} W")
    return alerts

# Kaytto silmukan sisalla:
# for msg in check_alerts(temp, power, power_limit_w):
#     print(msg)

Oletusarvo power_margin=0.95 tarkoittaa, että hälytys laukeaa, kun teho ylittää 95 prosenttia kortin tehobudjetista. Tämä on hyvä lähtökohta useimmille korteille, mutta jos kortti on aliviritetty (undervolted) matalammalle tehorajalle, kannattaa laskea myös temp_limit-arvoa vastaavasti, koska aliviritetty kortti saavuttaa usein saman suorituskyvyn matalammalla lämpötilalla, ja alkuperäinen 85 asteen raja voi olla tarpeettoman korkea.

Vaihe 8: Paikallisen tekoälymallin tokens/s-benchmark Ollamalla

Seuraava osa mittaa, montako tokenia sekunnissa paikallinen malli tuottaa Ollaman kautta. Mittari lasketaan dekoodausnopeutena eval_count/eval_duration-suhteella, samalla tavalla kuin MyAIHardware-sivusto raportoi omissa RTX-benchmarkeissaan.

Promptin pituus kannattaa pitää vakiona kaikissa testeissä, koska pidempi syöte kasvattaa promptin käsittelyaikaa eri suhteessa kuin varsinaista dekoodausta. Lyhyt, muutaman sanan prompti sopii hyvin raakaan tokens/s-vertailuun, mutta jos haluat simuloida todellista käyttöä, kannattaa käyttää samanmittaista ja -tyyppistä promptia kuin tuotantokäytössä, esimerkiksi muutaman kappaleen mittaista tekstiä, jos sovellus käsittelee pääasiassa pitkiä syötteitä.

import requests
import time

def benchmark_ollama(model="llama3.1:8b", prompt="Selita lyhyesti mika on NVML.", runs=5):
    results = []
    for i in range(runs):
        start = time.time()
        resp = requests.post(
            "http://localhost:11434/api/generate",
            json={"model": model, "prompt": prompt, "stream": False},
            timeout=120,
        )
        data = resp.json()
        wall_s = time.time() - start

        eval_count = data.get("eval_count", 0)
        eval_duration_s = data.get("eval_duration", 0) / 1e9
        tokens_per_s = eval_count / eval_duration_s if eval_duration_s > 0 else 0

        results.append(tokens_per_s)
        print(f"Ajo {i+1}/{runs}: {tokens_per_s:.1f} tokens/s (kokonaisaika {wall_s:.1f} s)")

    warm_runs = results[1:] if len(results) > 1 else results
    avg = sum(warm_runs) / len(warm_runs)
    print(f"Keskiarvo (ensimmainen ajo pois lukien): {avg:.1f} tokens/s")
    return results

benchmark_ollama()

Ensimmäinen ajo kannattaa jättää keskiarvon ulkopuolelle, koska se lämmittää mallin VRAM-cachen. Hylkäämällä ensimmäisen tuloksen vältetään tilanne, jossa yksittäinen hitaampi ajo vääristää koko keskiarvoa.

Vaihe 9: Telemetrian ja tokens/s-datan yhdistäminen

Arvokkain luku ei ole tokens/s yksinään, vaan watit per tuotettu token. Se kertoo, kumpi kortti tai asetus on todella tehokkaampi, ei vain nopeampi. Yhdistä GPU-mittaus ja benchmark samaan ajoon rinnakkaisella säikeellä, jotta telemetria kertyy koko benchmarkin ajalta.

Watit per token muuttuu konkreettiseksi rahaksi, kun kerrot keskitehon ajossa kuluneilla tunneilla ja sen jälkeen paikallisella sähkön hinnalla kilowattituntia kohden. Jos esimerkiksi RTX 5090 kuluttaa benchmarkin aikana keskimäärin 420 wattia tasaisesti tunnin ajan, se vastaa 0,42 kilowattituntia, ja saman laskun voi toistaa minkä tahansa GPU:n ja ajojakson kohdalla korvaamalla luvut omalla mittaustuloksella. Tämä on erityisen hyödyllistä, kun vertailee kannattaako paikallinen malli ajaa omalla koneella vai pilvipalvelun API:n kautta, koska pelkkä tokens/s-luku ei kerro mitään todellisesta kustannuksesta.

import threading

def combined_report(model="llama3.1:8b"):
    samples = []
    stop_flag = threading.Event()

    def sampler():
        pynvml.nvmlInit()
        handle = pynvml.nvmlDeviceGetHandleByIndex(0)
        while not stop_flag.is_set():
            power = pynvml.nvmlDeviceGetPowerUsage(handle) / 1000
            samples.append(power)
            time.sleep(1)
        pynvml.nvmlShutdown()

    t = threading.Thread(target=sampler)
    t.start()

    tokens_results = benchmark_ollama(model=model, runs=5)

    stop_flag.set()
    t.join()

    avg_power = sum(samples) / len(samples) if samples else 0
    avg_tps = sum(tokens_results[1:]) / len(tokens_results[1:])
    watt_per_token = avg_power / avg_tps if avg_tps > 0 else 0

    print(f"Keskiteho benchmarkin aikana: {avg_power:.1f} W")
    print(f"Keskinopeus: {avg_tps:.1f} tokens/s")
    print(f"Tehokkuus: {watt_per_token:.3f} W per token")

Huomaa, että sampler-säie kirjoittaa listaan power_samples ja pääsäie lukee sen vasta stop_flag.set()-kutsun ja t.join()-odotuksen jälkeen, joten kilpailutilannetta (race condition) listan lukemisessa ei pääse syntymään. Jos haluat tarkempaa dataa, voit tallentaa myös lämpötilan samaan näytteeseen power_samples-listan rinnalle ja laskea sille oman keskiarvon, jolloin lopullinen raportti kertoo myös keskilämpötilan koko benchmarkin ajalta, ei vain yksittäisiä hälytysrajan ylityksiä.

Vaihe 10: Ajastettu automaattiajo ja raportointi

Jos haluat seurata GPU:ta pitkällä aikavälillä esimerkiksi palvelinkoneessa, ajasta skripti crontabilla Linuxissa tai Task Schedulerilla Windowsissa. Käytä aina absoluuttista polkua virtuaaliympäristön Python-tulkkiin. Muuten ajastettu tehtävä ei löydä asennettuja kirjastoja.

# Linux crontab: aja joka tunti
0 * * * * /home/kayttaja/gpu-monitor/venv/bin/python /home/kayttaja/gpu-monitor/monitor.py >> /home/kayttaja/gpu-monitor/cron.log 2>&1

# Windows: schtasks-komento vastaavaan ajastukseen
schtasks /create /tn "GPUMonitor" /tr "C:\gpu-monitor\venv\Scripts\python.exe C:\gpu-monitor\monitor.py" /sc hourly

RTX 50 -sarjan teho, VRAM ja tokens/s vertailussa

Seuraava taulukko kokoaa Hardware Cornerin llama.cpp-mittaukset 8B-mallilla Q4-kvantisoinnilla ja 4K-kontekstilla, julkaistu Presenc AI:n RTX 5090 -benchmarkkikatsauksessa. Lukuja ei pidä verrata suoraan Ollama-mittauksiin, koska backend, kvantisointi ja kontekstin pituus vaikuttavat tulokseen merkittävästi.

GPUVRAMOletusteho8B-malli, tokens/s (llama.cpp, Q4)Huomio
RTX 509032 GB575 W~20012V-2×6-liitin mitattu 91,7 °C, 647 W huipputeho
RTX 409024 GB450 W~186-189Edeltäjäsukupolvi, yhä yleinen vertailukohta
RTX 508016 GB360 W~182-185Ollama-testissä samalla mallilla nähty myös 397-454 t/s vaihteluväli
RTX 309024 GB350 W~158-162Vanhin vertailtu kortti, ei virallista valmistajatukea uusimmille ajureille
2x RTX 5090 (Ollama)64 GB yhdistettynä2x 575 W26,9 (Llama 3.3 70B)DeepSeek-R1 70B 27,0 t/s, Qwen 110B 7,2 t/s yli 90 % VRAM-käytöllä

Huomaa, kuinka paljon yksittäinen Ollama-mittaus RTX 5080:lla poikkeaa llama.cpp-vertailusta samalla mallikokoluokalla. MyAIHardware-sivuston mittauksessa yhden mallin viisi ajoa tuottivat 397,42-454,33 tokens sekunnissa, keskiarvolla 429,62, mikä on selvästi korkeampi kuin yleiset 8B-vertailuluvut. Syy on todennäköisesti erilainen malli, kvantisointi tai mittaustapa, ei kortin suorituskyky sinänsä. Tämä on juuri se syy, miksi oma benchmark-skripti ja huolellinen lokitus ovat arvokkaampia kuin yksittäinen luku jostain toisesta sivustosta.

Kahden kortin yhdistäminen samaan malliin ei tuota lineaarista nopeushyötyä, kuten kaksi RTX 5090 -korttia Llama 3.3 70B -mallilla osoittavat: 26,9 tokens sekunnissa kahdella kortilla on huomattavasti vähemmän kuin kaksinkertainen yksittäisen kortin 8B-mallin nopeudesta, koska suuri malli pitää jakaa korttien kesken ja korttien väliseen tiedonsiirtoon kuluu aikaa. Qwen 110B -mallin 7,2 tokens sekunnissa yli 90 prosentin VRAM-käytöllä havainnollistaa, miten lähellä muistirajaa moni-GPU-kokoonpano voi joutua suurimmilla avoimen lähdekoodin malleilla, vaikka käytössä olisi yhteensä 64 gigatavua VRAM:ia.

NVML-kenttien merkitys: mitä kannattaa lokittaa

NVML-funktioMitä se kertooEsimerkkiarvo
nvmlDeviceGetTemperatureGPU-ydinlämpötila celsiusasteina72 C
nvmlDeviceGetPowerUsageHetkellinen tehonkulutus milliwatteina410000 (410 W)
nvmlDeviceGetPowerManagementLimitTehobudjetin yläraja milliwatteina575000 (575 W)
nvmlDeviceGetUtilizationRatesGPU- ja muistikuorma prosentteinagpu=97, memory=61
nvmlDeviceGetMemoryInfoVRAM käytössä ja kokonaismäärä tavuinaused=21474836480
nvmlDeviceGetClockInfoYdin- ja muistikellotaajuus megahertseinä2520 MHz
nvmlDeviceGetFanSpeedTuulettimen nopeus prosentteina maksimista68 %
nvmlSystemGetDriverVersionAsennetun GeForce-ajurin versioesim. 581.42

Tallenna aina myös ajuriversio ja CUDA-versio osana jokaista mittausta, ei vain erillisenä muistiinpanona. Nvidia-sivusto listaa ajurien ja CUDA Toolkitin yhteensopivuuden, ja juuri tuo yhdistelmä selittää usein, miksi kaksi käyttäjää saa eri tuloksen samalla kortilla samaa mallia ajaessaan.

Kaikkia kenttiä ei kannata lokittaa yhtä usein. Lämpötila, teho ja kuormaprosentit muuttuvat sekunneissa, joten niille sopii kahden sekunnin mittausväli. Ajuriversio, CUDA-versio ja GPU:n nimi sen sijaan muuttuvat harvoin kesken ajon, joten ne riittää lukea kerran ajon alussa ja liittää joka riviin muuttumattomana. Tämä pitää CSV-tiedoston rakenteen yksinkertaisena, mutta säilyttää silti kaiken tiedon, jota tarvitaan tuloksen toistamiseen myöhemmin. Hälytysrajoja kannattaa asettaa vain niille kentille, joiden ylitys on todella toiminnallisesti merkittävä, eli lämpötilalle ja tehobudjetille. Jos hälytys laukeaa jokaisesta pienestä kuormaprosentin vaihtelusta, lokista tulee nopeasti hyödytön.

Yleisimmät sudut työkalua rakentaessa

  • Pynvml ja nvidia-ml-py sekoittuvat. Asenna aina paketti nimellä nvidia-ml-py, mutta tuo moduuli koodissa nimellä pynvml. Vanha, erikseen ylläpidetty pynvml-paketti on eri asia ja saattaa olla vanhentunut. Jos molemmat on asennettu samaan ympäristöön vahingossa, Python saattaa tuoda väärän version riippuen asennusjärjestyksestä, joten turvallisin tapa on poistaa molemmat ja asentaa vain nvidia-ml-py uudelleen.
  • NVML-yhteyttä ei suljeta. Jos nvmlShutdown() jää kutsumatta pitkässä ajossa tai virhetilanteessa, resurssit voivat jäädä varattuina. Käytä try/finally-rakennetta tuotantokoodissa.
  • Tokens/s-lukuja verrataan väärin. Eri kvantisointi, kontekstin pituus ja backend tekevät vertailusta mahdotonta, vaikka GPU olisi sama. Kirjaa aina kaikki neljä muuttujaa: malli, kvantisointi, konteksti ja backend.
  • Mittausväli on liian lyhyt. Alle 0,5 sekunnin silmukka alkaa itsessään syödä suoritintehoa ja vääristää tuloksia, erityisesti kun samalla ajetaan raskasta tekoälymallia.
  • Tehonkulutuksen yksikkö unohtuu. NVML palauttaa tehon milliwatteina, nvidia-smi oletuksena watteina. Jos nämä kaksi lähdettä yhdistetään samaan raporttiin tarkistamatta yksikköä, luvut voivat näyttää sata kertaa väärältä.
  • Ensimmäistä benchmark-ajoa ei hylätä. Malli lämmittää VRAM-cachen ensimmäisellä ajolla, mikä hidastaa sitä verrattuna seuraaviin. Jos tätä ajoa ei jätetä pois keskiarvosta, koko mittaus vinouttaa alaspäin.
  • Taustaprosessit unohtuvat kokonaan. Selain, pelikäynnistin tai toinen Ollama-malli voi käyttää samaa GPU:ta taustalla ja syödä osan VRAM:sta tai suorituskyvystä. Tarkista aina nvidia-smi:n prosessilistauksella, että ainoastaan benchmarkattava prosessi käyttää korttia ennen mittauksen aloittamista.
  • Lämpötilan vaikutusta kellotaajuuteen ei huomioida. Nvidia-kortit laskevat kellotaajuuttaan automaattisesti, jos lämpötila nousee riittävän korkeaksi pitkässä ajossa. Jos benchmark kestää vain muutaman sekunnin, tätä ilmiötä ei ehdi näkyä, mutta tunnin mittaisessa ajossa tulos voi hitaasti hidastua, vaikka mikään koodissa ei muutu.

Vianmääritys: yleisimmät ongelmat ja ratkaisut

  • “NVMLError_LibraryNotFound” – GeForce-ajuri puuttuu kokonaan tai NVML-kirjaston polku ei ole järjestelmän PATH-muuttujassa. Asenna tai päivitä ajuri Nvidian omalta ajurisivulta ja käynnistä kone uudelleen.
  • “NVMLError_NotSupported” jollain kentällä – vanhempi GPU-sukupolvi tai virtualisoitu ympäristö ei tue kaikkia NVML-kenttiä, esimerkiksi kellotaajuustietoja. Kääri kyseinen kutsu try/except-lohkoon ja jatka muilla mittareilla.
  • pip install nvidia-ml-py epäonnistuu – tarkista, että virtuaaliympäristö on aktivoitu ja Python-versio on vähintään 3.10. Vanha pip-versio kannattaa päivittää komennolla pip install –upgrade pip.
  • Skripti näyttää 0 % GPU-kuormaa, vaikka malli pyörii – järjestelmässä on useampi GPU, ja benchmark käyttää eri korttia kuin indeksi 0. Listaa kortit nvmlDeviceGetCount()-funktiolla ja kohdista oikeaan indeksiin.
  • CSV-tiedosto kasvaa hallitsemattomasti – lisää tiedoston rotaatio tai aloita uusi tiedosto aikaleimalla joka ajokerralla, erityisesti jos skripti on ajastettu tunnin välein.
  • matplotlib ei näytä kuvaajaa palvelimella – käytä Agg-backendiä (matplotlib.use(“Agg”)) ja tallenna kuvaaja PNG-tiedostoon sen sijaan, että yritetään avata ikkuna.
  • Ollama-API vastaa “Connection refused” – Ollama-palvelu ei ole käynnissä. Käynnistä se komennolla ollama serve ja tarkista, että portti 11434 on vapaana.
  • Tokens/s vaihtelee merkittävästi ajojen välillä – hylkää ensimmäinen lämmitysajo keskiarvosta ja varmista, ettei taustalla pyöri muita GPU:ta kuormittavia prosesseja samaan aikaan.
  • WSL2 näkee GPU:n, mutta tehonkulutus puuttuu – WSL2:n GPU-läpivienti ei tue kaikkia NVML-kenttiä täysin. Aja monitorointiskripti sen sijaan suoraan Windows-isännässä, jos tarvitset täydet telemetriakentät.
  • Ajastettu cron-tehtävä ei löydä Pythonia – crontab ei peri interaktiivisen kuoren PATH-muuttujia. Käytä aina absoluuttista polkua virtuaaliympäristön Python-tulkkiin, kuten vaiheen 10 esimerkissä.
  • requests-kirjasto heittää aikakatkaisun (timeout) isoilla malleilla – pitkä kontekstin pituus tai suuri parametrimäärä voi viedä benchmarkissa yli oletetun ajan. Nosta timeout-parametria benchmark_ollama-funktiossa, erityisesti jos testaat 70 miljardin parametrin malleja hitaammalla kortilla.
  • CSV-tiedoston desimaalierotin sekoittaa taulukkolaskentaohjelman – Python kirjoittaa desimaalipisteen, mutta suomenkielinen Excel odottaa pilkkua. Avaa tiedosto tuontivelhon kautta tai määritä erotin eksplisiittisesti, jotta luvut eivät näy tekstinä.

Edistyneet vinkit: moni-GPU, Prometheus ja Grafana

Kun perusversio toimii, seuraava askel on laajentaa työkalua useammalle GPU:lle silmukalla nvmlDeviceGetCount()-funktion ympärille ja tallentaa kortin indeksi omaan sarakkeeseensa CSV-tiedostossa.

def read_all_gpus():
    count = pynvml.nvmlDeviceGetCount()
    rows = []
    for i in range(count):
        handle = pynvml.nvmlDeviceGetHandleByIndex(i)
        name = pynvml.nvmlDeviceGetName(handle)
        temp = pynvml.nvmlDeviceGetTemperature(handle, pynvml.NVML_TEMPERATURE_GPU)
        power = pynvml.nvmlDeviceGetPowerUsage(handle) / 1000
        rows.append({"index": i, "name": name, "temp_c": temp, "power_w": power})
    return rows

Tämä funktio palauttaa listan, jossa on yksi rivi per fyysinen GPU, ja se sopii suoraan CSV-kirjoittajalle lisäämällä index-kentän joka riville. Kahden kortin järjestelmässä tämä paljastaa nopeasti, jos kuorma ei jakaudu tasaisesti, esimerkiksi kun toinen kortti käsittelee suurimman osan mallin kerroksista ja toinen jää lähes joutokäynnille.

Jos haluat tuoda datan Grafanaan, muunna lukemat Prometheus-yhteensopivaan tekstimuotoon ja työnnä ne Pushgatewayn kautta, tai kirjoita yksinkertainen HTTP-endpoint, jota Prometheus skreippaa suoraan skriptistä. Watit per tuotettu token kannattaa laskea erikseen joka mallille ja tallentaa pitkäaikaiseen taulukkoon, jotta näet ajan myötä, parantaako ajuripäivitys tai uusi Ollama-versio todellista tehokkuutta eikä vain raakaa nopeutta. Jos ajat työkalua konttiympäristössä, NVIDIA Container Toolkit tarvitaan, jotta kontti näkee isännän GPU:n lainkaan, ja NVML-kutsut toimivat kontin sisällä samalla tavalla kuin isännässä.

Toinen hyödyllinen laajennus on raportin automaattinen vienti sähköpostiin tai Slack-webhookiin aina, kun hälytysraja ylittyy. Tämä vaatii vain muutaman rivin lisäyksen check_alerts-funktioon: sen sijaan, että hälytys tulostetaan konsoliin, se lähetetään HTTP POST -pyyntönä webhook-osoitteeseen. Kannattaa myös harkita, että skripti tallentaa erillisen “huippuarvo”-rivin CSV-tiedoston loppuun jokaisen ajon jälkeen, jotta pitkän aikavälin trendiä ei tarvitse laskea uudelleen koko raakadatasta joka kerta. Jos vertailet useampia GPU-malleja ajan myötä, kannattaa tallentaa myös GPU:n sarjanumero tai yksilöivä tunniste osaksi lokia, jotta tiedät varmasti, minkä fyysisen kortin tulos on kyseessä, jos koneessa on useampi samaa mallia.

Tietoturvanäkökulma: oikeudet ja lokien säilytys

GPU-telemetria itsessään ei ole arkaluonteista tietoa, mutta benchmark-lokit voivat sisältää promptien sisältöä, jos tallennat koko pyynnön vastauksen debug-tarkoituksessa. Tämä pätee erityisesti silloin, kun benchmark-skriptiä käytetään samalla koneella kuin oikeaa tuotantoliikennettä, esimerkiksi kun testataan asiakaspalvelubotin vastausnopeutta oikeilla kysymyksillä. Rajaa lokiin vain mittausarvot, ei mallin tuottamaa tekstiä, jos skripti ajetaan koneessa, jossa käsitellään asiakasdataa. Pidä myös CSV-tiedostot kansiossa, jota muut käyttäjät samalla koneella eivät pääse lukemaan, varsinkin jos ajat skriptiä palvelimella, jolla on useampi käyttäjä. Ajastettujen tehtävien oikeudet kannattaa rajata siten, että skripti ajetaan tavallisella käyttäjätunnuksella, ei pääkäyttäjänä, koska NVML-lukeminen ei vaadi korotettuja oikeuksia tavallisessa työpöytäkäytössä.

Jos skripti lähettää dataa webhookiin tai ulkoiseen Prometheus-palveluun, varmista, että osoite on HTTPS ja että webhook-URL ei sisällä arkaluonteista tunnistetietoa suoraan osoitteessa, koska URL-osoitteet päätyvät herkästi palomuurien ja reitittimien lokeihin selväkielisenä. Pidä myös huoli siitä, että Ollaman API ei ole avoinna julkiseen verkkoon oletuskokoonpanolla, koska portti 11434 kuuntelee oletuksena vain paikallista konetta, mutta jos olet avannut sen kuuntelemaan kaikkia verkkorajapintoja etäkäyttöä varten, kuka tahansa samassa lähiverkossa voi lähettää sille pyyntöjä ilman tunnistautumista.

Valmis projekti: koko skripti kokonaisuudessaan

Kokoa edelliset vaiheet yhdeksi tiedostoksi, monitor.py. Tämä on minimiversio, joka yhdistää jatkuvan telemetrialokituksen, hälytysrajat ja Ollama-benchmarkin yhdeksi ajettavaksi työkaluksi.

import csv
import time
import threading
import pynvml
import requests
from datetime import datetime

TEMP_LIMIT_C = 85
POWER_MARGIN = 0.95

def read_metrics(handle):
    temp = pynvml.nvmlDeviceGetTemperature(handle, pynvml.NVML_TEMPERATURE_GPU)
    power_w = pynvml.nvmlDeviceGetPowerUsage(handle) / 1000
    limit_w = pynvml.nvmlDeviceGetPowerManagementLimit(handle) / 1000
    util = pynvml.nvmlDeviceGetUtilizationRates(handle)
    mem = pynvml.nvmlDeviceGetMemoryInfo(handle)
    return temp, power_w, limit_w, util, mem

def benchmark_ollama(model="llama3.1:8b", runs=5):
    results = []
    for _ in range(runs):
        resp = requests.post(
            "http://localhost:11434/api/generate",
            json={"model": model, "prompt": "Selita lyhyesti mika on NVML.", "stream": False},
            timeout=120,
        )
        data = resp.json()
        eval_count = data.get("eval_count", 0)
        eval_duration_s = data.get("eval_duration", 0) / 1e9
        results.append(eval_count / eval_duration_s if eval_duration_s > 0 else 0)
    return results[1:] if len(results) > 1 else results

def main():
    pynvml.nvmlInit()
    handle = pynvml.nvmlDeviceGetHandleByIndex(0)
    driver = pynvml.nvmlSystemGetDriverVersion()

    power_samples = []
    stop_flag = threading.Event()

    def sampler():
        with open("gpu_log.csv", "w", newline="") as f:
            writer = csv.writer(f)
            writer.writerow(["timestamp", "driver", "temp_c", "power_w"])
            while not stop_flag.is_set():
                temp, power, limit, util, mem = read_metrics(handle)
                power_samples.append(power)
                writer.writerow([datetime.now().isoformat(), driver, temp, round(power, 1)])
                f.flush()
                if temp >= TEMP_LIMIT_C:
                    print(f"VAROITUS: lampotila {temp} C")
                if power >= limit * POWER_MARGIN:
                    print(f"VAROITUS: teho {power:.0f} W lahella rajaa {limit:.0f} W")
                time.sleep(2)

    t = threading.Thread(target=sampler)
    t.start()

    tokens = benchmark_ollama()

    stop_flag.set()
    t.join()
    pynvml.nvmlShutdown()

    avg_power = sum(power_samples) / len(power_samples) if power_samples else 0
    avg_tps = sum(tokens) / len(tokens) if tokens else 0
    watt_per_token = avg_power / avg_tps if avg_tps > 0 else 0

    print(f"Ajuri: {driver}")
    print(f"Keskiteho: {avg_power:.1f} W")
    print(f"Keskinopeus: {avg_tps:.1f} tokens/s")
    print(f"Tehokkuus: {watt_per_token:.3f} W per token")

if __name__ == "__main__":
    main()

Aja tiedosto komennolla python monitor.py Ollaman ollessa käynnissä taustalla. Tulokseksi saat samaan aikaan kerätyn CSV-lokin lämpötila- ja tehotiedoista sekä yhteenvedon mallin tokens/s-nopeudesta ja tehokkuudesta watteina tuotettua tokenia kohden.

Miten tulkitset benchmark-raportin tuloksia

Täydellä skriptillä ajettu benchmark tulostaa konsoliin yhteenvedon tähän tapaan:

Ajuri: 581.42
Keskiteho: 418.6 W
Keskinopeus: 148.2 tokens/s
Tehokkuus: 2.825 W per token

Ajuriversio kertoo, millä ohjelmistopohjalla tulos on saatu, ja se kannattaa liittää mukaan, jos jaat tuloksen muille tai vertaat sitä myöhempään ajoon toisella ajuriversiolla. Keskiteho on laskettu koko benchmarkin ajalta, ei vain hetkellisestä huipusta, joten se kuvaa todellista sähkönkulutusta paremmin kuin kortin ilmoitettu tehobudjetti. Keskinopeus on tokens/s-arvo ensimmäisen lämmitysajon jälkeen laskettuna, joten se kuvaa realistista jatkuvaa suorituskykyä. Tehokkuusluku, watit per token, on arvoista kaikkein hyödyllisin pitkällä aikavälillä: jos ajuripäivityksen tai mallin kvantisoinnin vaihdon jälkeen tämä luku laskee, kortti tuottaa saman työn pienemmällä sähkönkulutuksella, vaikka tokens/s-luku pysyisi samana tai laskisi hieman. Jos taas tehokkuusluku nousee ajan myötä samalla laitteistolla, se on usein merkki lämpötilan aiheuttamasta kellotaajuuden laskusta tai taustalla pyörivästä toisesta prosessista, joka varaa osan GPU:n kapasiteetista.

Kun vertailet kahta ajoa keskenään, katso aina myös CSV-tiedoston lämpötilakäyrää, ei vain yhteenvedon lukuja. Tasaisesti nouseva lämpötila, joka vakiintuu selvästi alle 85 asteen, kertoo terveestä jäähdytyksestä. Jos lämpötila nousee nopeasti ja pysyy lähellä hälytysrajaa koko ajon ajan, kannattaa tarkistaa kotelon ilmanvaihto ennen kuin syyttää mallia tai ajuria hitaudesta.

Ollama vai LM Studio: API-erot benchmarkkauksessa

Tämä opas käyttää Ollaman natiivia API:a, joka palauttaa eval_count- ja eval_duration-kentät suoraan vastauksessa, mikä tekee tokens/s-laskennasta suoraviivaista. LM Studio tarjoaa sen sijaan OpenAI-yhteensopivan rajapinnan, jossa näitä kenttiä ei palauteta samassa muodossa, vaan dekoodausnopeus pitää laskea itse mittaamalla vastauksen kokonaisaika ja jakamalla se palautettujen tokenien määrällä. Käytännössä tämä tarkoittaa, että LM Studion kanssa tokens/s-luku on hieman epätarkempi, koska se sisältää myös pyynnön käsittelyyn kuluneen ylimääräisen ajan, ei vain puhdasta dekoodausaikaa.

import requests
import time

def benchmark_lmstudio(model="llama-3.1-8b-instruct", runs=5):
    results = []
    for _ in range(runs):
        start = time.time()
        resp = requests.post(
            "http://localhost:1234/v1/chat/completions",
            json={
                "model": model,
                "messages": [{"role": "user", "content": "Selita lyhyesti mika on NVML."}],
            },
            timeout=120,
        )
        data = resp.json()
        duration_s = time.time() - start
        completion_tokens = data.get("usage", {}).get("completion_tokens", 0)
        results.append(completion_tokens / duration_s if duration_s > 0 else 0)
    return results[1:] if len(results) > 1 else results

Jos vertailet Ollamaa ja LM Studiota suoraan toisiinsa, muista, että tässä LM Studio-esimerkissä mitattu aika sisältää koko HTTP-pyynnön kokonaisajan, kun Ollama-esimerkki vaiheessa 8 laski vain puhtaan dekoodausajan. Reilu vertailu vaatii, että molemmista mitataan samaa suuretta, joten käytännössä kannattaa joko lisätä Ollama-mittaukseen kokonaisaika tai vähentää LM Studio-mittauksesta arvioitu promptin käsittelyaika ennen vertailua.

Yhteenveto: mitä opit ja mitä kannattaa tehdä seuraavaksi

Kymmenen vaiheen jälkeen koneella pyörii GPU-benchmark-skripti, joka lukee NVML:n kautta lämpötilan, tehon, kellotaajuudet ja VRAM-käytön kahden sekunnin välein, kirjoittaa ne CSV-lokiin, piirtää niistä kuvaajan ja mittaa samalla paikallisen tekoälymallin tokens/s-nopeuden Ollaman API:n kautta. Tärkein yksittäinen oppi on se, että raakanopeus ja tehokkuus ovat kaksi eri asiaa: watit per tuotettu token kertoo enemmän todellisesta kustannuksesta kuin pelkkä tokens/s-luku. Seuraava järkevä askel on ajaa skriptiä muutaman päivän ajan taustalla normaalikäytössä, jotta näet, miten lämpötila ja tehonkulutus vaihtelevat erilaisten mallien ja kuormien välillä, ennen kuin asetat lopulliset hälytysrajat tuotantokäyttöön. Kun perusversio on vakaa, koko lähdekoodi tässä oppaassa on tarkoituksella pidetty yhdessä, hyvin kommentoidussa tiedostossa, jotta sitä on helppo laajentaa omiin tarpeisiin sen sijaan, että joutuisit kokoamaan osia useista erillisistä lähteistä.

Usein kysytyt kysymykset

Pitääkö asentaa pynvml vai nvidia-ml-py?

Asenna pip-paketti nimellä nvidia-ml-py, mutta tuo se koodissa edelleen nimellä pynvml. Nvidia julkaisi tästä paketista version 13.615.71 PyPI:hin 25. syyskuuta 2026, ja se on ylläpidetty, ajantasainen tapa päästä käsiksi NVML-rajapintaan Pythonista.

Toimiiko tämä opas myös AMD- tai Intel-näytönohjaimilla?

Ei suoraan. NVML on Nvidian omistama rajapinta, ja se toimii vain GeForce- ja Nvidia-datakeskuskorteilla. AMD-korteille vastaava tieto saadaan ROCm-työkaluilla ja Intel-korteille Intelin omalla XPU-Manager-rajapinnalla, mutta CSV-lokituksen ja visualisoinnin logiikka tästä oppaasta on sovellettavissa niihinkin. Käytännössä ainoa osa, joka täytyy kirjoittaa uudelleen, on read_metrics-funktio, joka korvataan kyseisen valmistajan oman kirjaston kutsuilla.

Miksi tokens/s-luku vaihtelee niin paljon ajojen välillä?

Ensimmäinen ajo lämmittää mallin VRAM-cachen ja on tyypillisesti hitaampi. Muita syitä ovat taustalla ajavat muut prosessit, lämpötilasta johtuva kellotaajuuden lasku ja erot kontekstin pituudessa eri kysymysten välillä. MyAIHardware-sivuston RTX 5080 -mittauksessa samalla mallilla nähtiin 397-454 tokens sekunnissa viiden ajon välillä, mikä kuvaa hyvin vaihteluvälin suuruutta.

Voiko monitorointiskripti itse hidastaa GPU:n suorituskykyä?

Kahden sekunnin mittausvälillä NVML-kutsujen kuormitus on käytännössä mitätön verrattuna itse mallin ajoon. Jos mittausväli lyhennetään alle 0,5 sekuntiin tai käytetään nvidia-smi-subprocess-kutsua jatkuvassa silmukassa tekstin jäsentämisen kanssa, prosessorikuorma voi nousta havaittavasti.

Mikä mittausväli on järkevä jatkuvaan monitorointiin?

Yhdestä kahteen sekuntia on hyvä lähtökohta useimmissa tapauksissa. Lyhyempi väli antaa tarkempia piikkejä, mutta kasvattaa CSV-tiedoston kokoa nopeasti ja lisää CPU-kuormaa turhaan pitkissä ajoissa. Jos ajat monitorointia viikkojen ajan taustapalveluna, viiden sekunnin väli riittää useimpiin käyttötarkoituksiin ja pitää lokitiedoston koon hallittavana.

Toimiiko NVML WSL2:ssa tai Docker-kontissa?

Osittain. WSL2:n GPU-läpivienti ei tue kaikkia NVML-kenttiä, erityisesti tarkkoja tehonkulutuslukemia. Docker-kontissa NVML toimii, kunhan NVIDIA Container Toolkit on asennettu isäntäkoneelle ja kontti käynnistetään GPU-tuella, esimerkiksi docker run –gpus all -lipulla. Tarkista aina kontin sisältä käsin nvidia-smi-komennolla, että kortti näkyy, ennen kuin oletat NVML-kutsujen toimivan.

Mistä löydän ajantasaiset ajuri- ja CUDA-versiot skriptin dokumentointiin?

Nvidia ylläpitää virallista ajurisivua ja erillistä ajuri/CUDA-yhteensopivuustaulukkoa, joista molemmista näkee uusimman julkaistun version ja julkaisupäivän. Näiden tietojen tallentaminen skriptin lokiin on paras tapa pitää omat benchmark-tulokset toistettavina myöhemminkin. Myös PyPI-sivu kertoo nvidia-ml-py-paketin uusimman version ja julkaisupäivän suoraan, mikä on nopein tapa tarkistaa, kannattaako kirjasto päivittää ennen uutta benchmark-ajoa.

Kannattaako käyttää Ollamaa vai LM Studiota benchmarkkaukseen?

Molemmat toimivat, mutta tässä oppaassa käytetty API-pohjainen eval_count/eval_duration-mittaus on suunniteltu Ollaman rajapinnalle. Jos haluat vertailla Ollamaa ja LM Studiota keskenään, aja molemmat samalla mallilla, kvantisoinnilla ja kontekstin pituudella samalla koneella, äläkä vertaa valmiita lukuja eri lähteistä suoraan toisiinsa. Ollaman natiivi API on yksinkertaisempi lähtökohta oman benchmark-skriptin rakentamiseen, koska se palauttaa dekoodausajan valmiina, kun LM Studion OpenAI-yhteensopivalla rajapinnalla se pitää laskea itse.