6.4.1. GPU kihasználtság maximalizálása

2025.10.06.
AI Biztonság Blog

Elindítasz egy masszív, több tízezer promptból álló jailbreak tesztet egy lokálisan futtatott modellen, majd órák múlva ránézel a folyamatra, és azt látod, hogy alig haladt. 

Kapcsolati űrlap

AI Biztonság kérdésed van? Itt elérsz minket:

Megnyitod a terminált, beírod az nvidia-smi parancsot, és a szemed elé tárul a lesújtó valóság: GPU-Util: 5%. A drága, csúcskategóriás hardver szinte unatkozik, miközben te a hajadat téped! Ez a jelenség sokkal gyakoribb, mint gondolnád, és a megoldása nem a hardver cseréje, hanem a munkafolyamat intelligens optimalizálása.

A GPU-k elképesztő párhuzamos számítási kapacitással rendelkeznek, de ezt csak akkor tudják kamatoztatni, ha folyamatosan és hatékonyan „etetjük” őket adatokkal. Az alacsony kihasználtság szinte mindig arra utal, hogy a rendszerben valahol máshol van a szűk keresztmetszet, a „palacknyak”. A GPU csak vár. Vár az adatra, a CPU-ra, a merevlemezre. Ebben a fejezetben felderítjük ezeket a rejtett palacknyakakat, és megnézzük, hogyan pörgetheted fel a hardveredet a maximális hatékonyság érdekében.

A csendes hardver rejtélye: Hol a palacknyak?

Mielőtt bármit optimalizálnánk, diagnosztizálnunk kell. A Red Teaming során végzett tesztek (legyen az prompt injection, adatlopás vagy károskód-generálás) gyakran ismétlődő, de kis számítási igényű feladatok sorozatából állnak. 

Ez tökéletes táptalaj a palacknyakak kialakulásához. Az első lépés mindig a rendszer viselkedésének megfigyelése.

Diagnosztikai eszközök

  • nvidia-smi: Az alapvető parancssori eszköz. A -l 1 kapcsolóval másodpercenként frissül, így valós időben láthatod a GPU kihasználtságát (GPU-Util), a memóriafoglalást (Memory-Usage) és az energiafogyasztást.
  • nvtop vagy gpustat: Felhasználóbarátabb, interaktívabb alternatívák, amelyek folyamatosan frissülő, részletesebb képet adnak a GPU-k és a rajtuk futó processzek állapotáról.
  • CPU monitorozó eszközök (htop, top): Elengedhetetlenek, hogy lásd, a CPU magok 100%-on pörögnek-e, miközben a GPU alszik.
  • Python Profilerek (cProfile, Py-Spy): Mélyebb elemzést tesznek lehetővé, megmutatva, hogy a kódodon belül melyik függvényhívás mennyi időt emészt fel.

A megfigyelések alapján általában a következő mintázatok egyikével találkozol:

Megfigyelt jelenség Valószínű ok (Palacknyak) Tipikus Red Teaming példa
Magas CPU, alacsony GPU kihasználtság CPU-kötött feladat Minden egyes prompt tokenizálása és előkészítése a fő szálon történik, mielőtt a GPU-ra kerülne.
Alacsony CPU, alacsony GPU kihasználtság I/O-kötött feladat (adatbetöltés) A teszt promptok egy hatalmas fájlból, lassú hálózati meghajtóról vagy adatbázisból töltődnek be egyenként.
Ingadozó GPU kihasználtság (pl. 0% -> 90% -> 0%) Túl kicsi, szekvenciális munkacsomagok Egy for ciklusban egyesével küldöd a promptokat a modellnek feldolgozásra.
Magas GPU memória, alacsony GPU kihasználtság Memóriatranszfer vagy kernel indítási overhead Nagyon sok apró adatcsomag mozog a CPU és GPU memória között, a tényleges számítás ideje eltörpül a transzfer mellett.

Esettanulmány: Egy tömeges jailbreak kísérlet optimalizálása

Vegyünk egy konkrét példát. Adott egy 50.000 promptot tartalmazó lista, amellyel egy lokális Llama 3 modellt tesztelünk. A célunk, hogy megtaláljuk azokat a variációkat, amelyek kikerülik a modell biztonsági szűrőit.

A naiv megközelítés: A türelmes for ciklus

A legegyszerűbb, legkézenfekvőbb megoldás egy egyszerű ciklus, ami végigmegy a listán, és minden elemet feldolgoz.


import torch
from transformers import AutoTokenizer, AutoModelForCausalLM

# Betöltjük a modellt és a tokenizert (ez időigényes, de csak egyszer történik meg)
model = AutoModelForCausalLM.from_pretrained("meta-llama/Meta-Llama-3-8B", device_map="auto")
tokenizer = AutoTokenizer.from_pretrained("meta-llama/Meta-Llama-3-8B")

prompts = load_jailbreak_prompts("prompts.txt") # 50,000 prompt betöltése
results = []

for prompt in prompts:
 # 1. Tokenizálás (CPU munka)
 inputs = tokenizer(prompt, return_tensors="pt").to("cuda")

 # 2. Generálás (GPU munka)
 outputs = model.generate(**inputs, max_new_tokens=100)
 
 # 3. Dekódolás (CPU munka)
 response = tokenizer.decode(outputs[0], skip_special_tokens=True)
 results.append(response)

# A GPU az idő nagy részében arra vár, hogy a CPU előkészítse a következő promptot.

Ez a kód funkcionálisan helyes, de borzasztóan ineffektív. A ciklus minden egyes iterációjában a CPU tokenizál, átmásolja az adatot a GPU-ra, a GPU elvégzi a számítást (ami egyetlen prompt esetén villámgyors), majd az eredmény visszakerül a CPU-ra dekódolásra. A GPU az idő 95%-ában tétlenül várakozik.

Naiv (szekvenciális) végrehajtás: CPU GPU CPU GPU Jelentős üresjárat a GPU-n Optimalizált (kötegelt) végrehajtás: CPU (batch prep) GPU (batch process)

A megoldás: A GPU folyamatos etetése

A célunk, hogy a GPU-t egy nagy, folyamatos adatárammal lássuk el, minimalizálva az üresjáratokat. Ezt több technika kombinációjával érhetjük el.

1. Kötegelés (Batch Processing)

A legfontosabb és leghatékonyabb módszer. Ahelyett, hogy egyesével küldenénk a promptokat, csoportosítjuk őket egy „kötegbe” (batch), és ezt a köteget egyszerre adjuk át a modellnek. A GPU párhuzamos architektúrája révén egy 32 promptból álló köteg feldolgozása alig tart tovább, mint egyetlen prompté. Az adatmozgatás és a kernel indításának overheadje eloszlik a köteg elemei között.


from torch.utils.data import DataLoader, TensorDataset

# ... modell betöltése ...

prompts = load_jailbreak_prompts("prompts.txt")
# A tokenizert is kötegelten használjuk, padding=True biztosítja az azonos hosszúságot
inputs = tokenizer(prompts, return_tensors="pt", padding=True, truncation=True).to("cuda")

BATCH_SIZE = 32
results = []

# A ciklus most a kötegeken megy végig, nem az egyes elemeken
for i in range(0, len(prompts), BATCH_SIZE):
 batch_inputs = {key: val[i:i+BATCH_SIZE] for key, val in inputs.items()}
 
 with torch.no_grad(): # Inferencia során nincs szükségünk gradiensekre
 outputs = model.generate(**batch_inputs, max_new_tokens=100)
 
 responses = tokenizer.batch_decode(outputs, skip_special_tokens=True)
 results.extend(responses)

A kötegelés annyira alapvető fontosságú technika, hogy a következő, 6.4.2 Batch processing stratégiák című fejezetben sokkal részletesebben, különböző stratégiákat és buktatókat bemutatva foglalkozunk vele.

2. Párhuzamos adat-előkészítés

Ha az adat-előkészítés (pl. komplex promptok generálása, fájlokból való olvasás) továbbra is lassú, a kötegelés önmagában nem elég. Ilyenkor az adatbetöltést és -feldolgozást külön CPU szálakra vagy processzekre kell szervezni. A PyTorch DataLoader osztálya a num_workers paraméterrel pontosan erre való. Míg a GPU az N. köteget dolgozza fel, addig a háttérben futó workerek már készítik elő az N+1. köteget, így a GPU-nak sosem kell várnia.

3. Kevert pontosság (Mixed Precision)

A modern GPU-k (Tensor Cores) sokkal gyorsabban tudnak számolni alacsonyabb pontosságú számokkal (pl. 16-bites lebegőpontos, FP16) a hagyományos 32-bites (FP32) helyett. A kevert pontosságú számítás (Automatic Mixed Precision, AMP) során a modell bizonyos részei FP16-ban, más, pontosságra érzékeny részei pedig FP32-ben futnak. Ez nemcsak a számítást gyorsítja, de a memóriafoglalást is csökkenti, ami lehetővé teszi nagyobb kötegek használatát, tovább növelve a kihasználtságot.

Az AI Red Teaming tesztek sebessége és hatékonysága nem csak a kreatív támadási vektorokon múlik, hanem a rendelkezésre álló erőforrások maximális kihasználásán is. Egy jól optimalizált tesztelési folyamat órákat, sőt napokat spórolhat meg, lehetővé téve, hogy több variációt, több modellt és több forgatókönyvet teszteljünk ugyanannyi idő alatt. A GPU-kihasználtság maximalizálása nem csupán technikai bűvészkedés, hanem stratégiai előny a modellek gyengeségeinek feltárásában!