Kā novērst “PyTorch CUDA Out of Memory” kļūdu modeļa apmācības laikā

PyTorch apmācības process var darboties vairākus soļus, bet pēc tam apstāties ar kļūdu torch.OutOfMemoryError vai ziņojumu, piemēram, CUDA out of memory. Tried to allocate .... Tiešais iemesls ir vienkāršs: nākamajai CUDA atmiņas rezervācijai nebija pietiekami daudz vietas. Noderīgākais jautājums ir kāpēc tai nebija pietiekami daudz vietas.

Apmācības laikā GPU atmiņā var atrasties modeļa parametri, gradienti, optimizatora stāvoklis, ievades tenzori, pagaidu darbvietas un atpakaļejošajai izplatīšanai saglabātās aktivizācijas. PyTorch izmanto arī kešatmiņas atmiņas dalītāju, tāpēc atmiņa, kas tiek rādīta kā “rezervēta”, nav identiska atmiņai, ko pašlaik aizņem aktīvie tenzori. Šī atšķirība ir svarīga, nosakot, vai samazināt slodzi, vai pētīt dalītāja fragmentāciju.

Šis ceļvedis balstās uz pašreizējo PyTorch dokumentāciju un izmanto pašreizējos AMP API nosaukumus. Konkrēti, PyTorch tagad dokumentē torch.amp.autocast("cuda") un torch.amp.GradScaler("cuda"); vecākie torch.cuda.amp.* ieejas punkti ir novecojuši. Skatiet PyTorch automātiskās jauktās precizitātes dokumentāciju.

Ātra triāža: ar kāda veida OOM kļūdu jūs saskaraties?

SimptomsIespējamais virziensLabākā pirmā darbība
OOM kļūda rodas pirmajā uzlādējošajā (forward) posmāAktīvā darba kopa ir pārāk lielaSamaziniet mikro-partijas izmēru vai ievades izmēru; pārliecinieties, vai pats modelis ietilpst atmiņā.
OOM kļūda rodas atpakaļejošajā (backward) posmāSaglabātās aktivizācijas kopā ar gradientiem pārsniedz VRAMIzmēģiniet AMP, aktivizāciju kontrolpunktus un mazāku mikro-partiju.
Atmiņa pieaug katrā iterācijāVar tikt saglabāts tenzors vai aprēķinu grafiksPārbaudiet sarakstus, metrikas, kešatmiņā saglabātos izvades datus un atsauces uz zaudējumu tenzoriem.
Aizņemtā atmiņa ir mērena, bet rezervētā atmiņa ir daudz lielākaVar būt nozīme kešatmiņai vai fragmentācijaiPirms dalītāja iestatījumu maiņas pārbaudiet memory_summary().
Cits process jau izmanto ievērojamu VRAM daudzumuNe visa GPU atmiņa pieder šim apmācības procesamIdentificējiet procesu un atbrīvojiet to GPU vai plānojiet uzdevumu citur.
AI ģenerēta ilustrācija par PyTorch CUDA atmiņas trūkuma ziņojumu terminālī
AI ģenerēta ilustrācija par tipisku CUDA atmiņas trūkuma ziņojumu. Precīzie skaitļi atšķiras atkarībā no modeļa, GPU un apmācības soļa.

1. solis: Izmēriet atmiņu, pirms maināt apmācības recepti

Sāciet, reģistrējot partijas izmēru, ievades dimensijas, precizitāti un brīdi, kad rodas kļūme. Pēc tam pārbaudiet gan aktīvo tenzoru atmiņu, gan dalītāja rezervēto atmiņu. PyTorch nodrošina piekļuvi funkcijām memory_allocated(), memory_reserved(), to maksimālajām vērtībām un memory_summary(). Pašreizējā CUDA atmiņas pārvaldības dokumentācija skaidro, ka kešatmiņas dalītājs saglabā atkārtoti lietojamus blokus, tāpēc neizmantotā rezervētā atmiņa GPU uzraudzības rīkos joprojām var izskatīties kā aizņemta. PyTorch CUDA atmiņas pārvaldība.

import torch

torch.cuda.reset_peak_memory_stats()

# Šeit palaidiet vienu reprezentatīvu apmācības soli.

print("allocated GB:",
      torch.cuda.memory_allocated() / 1024**3)
print("reserved GB:",
      torch.cuda.memory_reserved() / 1024**3)
print("peak allocated GB:",
      torch.cuda.max_memory_allocated() / 1024**3)
print(torch.cuda.memory_summary(abbreviated=True))

Ja vienkāršs kopsavilkums nav pietiekams, PyTorch var uzņemt dalītāja momentuzņēmumus dziļākai analīzei. Tā atmiņas rīki var reģistrēt rezervāciju vēsturi un izveidot momentuzņēmumu, ko var pārbaudīt, izmantojot PyTorch atmiņas vizualizatoru. PyTorch norāda, ka šie rīki redz tikai to atmiņu, ko pārvalda PyTorch dalītājs; citas CUDA bibliotēkas veiktās rezervācijas tur var neparādīties. PyTorch ceļvedis CUDA atmiņas lietošanas izpratnei.

AI ģenerēta ilustrācija par nvidia-smi, kas rāda GPU atmiņas lietošanu
AI ģenerēta ilustrācija par kopējās GPU atmiņas lietošanas pārbaudi, izmantojot nvidia-smi; lietojiet to kopā ar PyTorch dalītāja statistiku, lai redzētu, vai cits process neizlieto VRAM.

Nekonsiderējiet torch.cuda.empty_cache() par vispārīgu OOM risinājumu

torch.cuda.empty_cache() atbrīvo neizmantotos kešatmiņas blokus, lai citas GPU lietotnes varētu tos izmantot. PyTorch skaidri norāda, ka tas neatbrīvo atmiņu, ko aizņem aktīvie tenzori, un tāpēc nepalielina GPU atmiņas daudzumu, kas pieejams PyTorch tenzoriem, kuri joprojām ir dzīvi. Tas var būt noderīgi starp atsevišķiem eksperimentiem vai pēc lielu objektu dzēšanas, taču tas nav aizstājējs aktīvās atmiņas pēdas samazināšanai.

2. solis: Vispirms samaziniet aktīvo darba kopu

Visuzticamākais pirmais risinājums parasti ir mazāka mikro-partija: paraugu skaits, ko apstrādā viens uzlādējošais/atpakaļejošais posms. Aktivizāciju atmiņa parasti pieaug līdz ar partijas izmēru, attēla izšķirtspēju, virknes garumu un citām ievades dimensijām. Ja modelis apmācās ar partijas izmēru 32, bet neizdodas ar 64, partijas samazināšana nav negatīva nozīme risinājums; tā ir tieša maksimālās atmiņas pieprasījuma samazināšana.

AI ģenerēta ilustrācija, kas rāda PyTorch apmācības partijas izmēra samazināšanu no 64 uz 16
AI ģenerēta ilustrācija par partijas izmēra samazināšanu vienā solī, lai samazinātu maksimālo CUDA atmiņas lietošanu.

Attēliem telpiskās izšķirtspējas vai apgriešanas izmēra samazināšana var radīt lielu atšķirību. Transformeru un citiem virkņu modeļiem virknes garuma samazināšana var būt vēl svarīgāka, jo daži starpposma tenzori strauji pieaug līdz ar virknes garumu. Precīza mērogošana ir atkarīga no arhitektūras, tāpēc mēriet, nevis pieņemiet.

Pārliecinieties arī, ka novērtēšanas kods nevajadzīgi neveido gradientus. PyTorch veiktspējas norādījumos ieteicams izslēgt gradientu aprēķināšanu validācijai vai secināšanai, kad gradienti nav nepieciešami, jo autograd citādi saglabā starpposma buferus. Tipisks paraugs ir:

model.eval()
with torch.no_grad():
    for x, y in val_loader:
        x = x.cuda(non_blocking=True)
        y = y.cuda(non_blocking=True)
        pred = model(x)

Apmācības laikā izmantojiet optimizer.zero_grad(set_to_none=True), ja vien jūsu algoritms nen paļaujas uz uzvedības atšķirību starp nulles gradientu un None gradientu. PyTorch optimizatora dokumentācija norāda, ka gradientu iestatīšana uz None parasti samazina atmiņas pēdu un var nedaudz uzlabot veiktspēju. PyTorch optimizatora zero_grad dokumentācija.

3. solis: Saglabājiet lielāku efektīvo partiju, izmantojot AMP un gradientu uzkrāšanu

Izmantojiet automātisko jaukto precizitāti, ja modelis to atbalsta

Automātiskā jauktā precizitāte (AMP) veic piemērotas operācijas zemākā precizitātē, saglabājot operācijas, kurām nepieciešams lielāks diapazons vai precizitāte, atbilstošos tipos. PyTorch dokumentē, ka AMP var uzlabot veiktspēju un samazināt atmiņas pēdu daudziem CUDA slodzes veidiem, taču tas nav skaitliski piemērots katram modelim. Konkrēti, PyTorch brīdina, ka daži modeļi, kas iepriekš apmācīti ar bfloat16, var piedzīvot pārpildi, izmantojot float16.

Pašreizējais CUDA AMP apmācības paraugs ir:

scaler = torch.amp.GradScaler("cuda")

for inputs, targets in train_loader:
    inputs = inputs.cuda(non_blocking=True)
    targets = targets.cuda(non_blocking=True)
    optimizer.zero_grad(set_to_none=True)

    with torch.amp.autocast("cuda", dtype=torch.float16):
        outputs = model(inputs)
        loss = loss_fn(outputs, targets)

    scaler.scale(loss).backward()
    scaler.step(optimizer)
    scaler.update()

PyTorch iesaka veikt uzlādējošo posmu un zaudējumu aprēķinu zem autocast, bet pirms atpakaļejošā posma atstāt autocast kontekstu. Ja float16 rada nestabilitāti, izpētiet, vai bfloat16 ir atbalstīts un piemērots jūsu aparatūrai un modelim, nevis pieņemiet, ka visi jauktas precizitātes režīmi uzvedas identiski.

Izmantojiet gradientu uzkrāšanu, ja nepieciešama lielāka efektīvā partija

Gradientu uzkrāšana apstrādā vairākas mazākas mikro-partijas, pirms atjaunina optimizatoru. Ja mikro-partija ir 4 un jūs uzkrājat 8 soļus, efektīvā partija vienam optimizatora atjauninājumam ir 32 paraugi uz vienu darbinieku, pieņemot, ka katrai mikro-partijai ir četri paraugi un datu paralēlā iestatīšana nemaina šo aritmētiku.

accum_steps = 8
optimizer.zero_grad(set_to_none=True)

for step, (inputs, targets) in enumerate(train_loader):
    inputs = inputs.cuda(non_blocking=True)
    targets = targets.cuda(non_blocking=True)

    with torch.amp.autocast("cuda", dtype=torch.float16):
        outputs = model(inputs)
        loss = loss_fn(outputs, targets) / accum_steps

    scaler.scale(loss).backward()

    if (step + 1) % accum_steps == 0:
        scaler.step(optimizer)
        scaler.update()
        optimizer.zero_grad(set_to_none=True)
AI ģenerēta ilustrācija par gradientu uzkrāšanu vairākās mazākās PyTorch mikro-partijās
AI ģenerēta ilustrācija par gradientu uzkrāšanu, kas apmaina vairāk uzlādējošo/atpakaļejošo soļu pret lielāku efektīvo partiju, vienlaikus neatrodot visu partiju VRAM vienlaikus.

Ražošanas kodā apstrādājiet arī pēdējo daļējo uzkrāšanas logu, ja partiju skaits nav dalāms ar accum_steps. Ja izmantojat sadalītu apmācību, gradientu sinhronizācijas uzvedība var mainīt atmiņas/veiktspējas kompromisu, tāpēc sekojiet sadalītās API uzkrāšanas norādījumiem, nevis kopējiet viena GPU ciklu nemainītu.

4. solis: Apmainiet aprēķinus pret atmiņu, pēc tam izpētiet saglabāšanu un fragmentāciju

Aktivizāciju kontrolpunkti

Aktivizāciju kontrolpunkti samazina atmiņu, nesaglabājot izvēlētās uzlādējošās aktivizācijas līdz atpakaļejošajam posmam. Tā vietā PyTorch tos pārrēķina atpakaļejošā posma laikā. Tas apmaina papildu aprēķinus pret zemāku aktivizāciju atmiņas pēdu. Pašreizējā PyTorch kontrolpunktu dokumentācija iesaka skaidri nodot use_reentrant=False. PyTorch aktivizāciju kontrolpunktu dokumentācija.

from torch.utils.checkpoint import checkpoint

def forward(self, x):
    x = checkpoint(self.block1, x, use_reentrant=False)
    x = checkpoint(self.block2, x, use_reentrant=False)
    return self.head(x)

Izveidojiet kontrolpunktus slāņiem ar lielām saglabātajām aktivizācijām un pieņemamu pārrēķina izmaksu. Nepieņemiet, ka kontrolpunktu izveide katrai operācijai ir optimāla; tas var būtiski palēnināt apmācību.

Meklējiet tenzorus, kas uztur aprēķinu grafikus dzīvus

Ja atmiņa pieaug katrā iterācijā, nevis sasniedz aptuveni vienādu līmeni, pārbaudiet Python atsauces. Bieži sastopams paraugs ir grafika savienotu tenzoru glabāšana sarakstā:

# Riskanti, ja saglabāts daudzos soļos:
loss_history.append(loss)

# Glabājiet Python skaitli tā vietā:
loss_history.append(loss.item())

Tā pati problēma var rasties, kešojot modeļa izvades, uzmanības kartes, slēptos stāvokļus vai validācijas tenzorus, neatvienojot tos vai nepārvietojot no GPU. Dzēsiet atsauces, kuras vairs nevajag, un izmantojiet detach() tikai tad, ja apzināti vēlaties tenzoru, kas ir atvienots no autograd.

Pielāgojiet dalītāju tikai tad, kad statistika norāda uz fragmentāciju

Pašreizējā PyTorch dokumentācija dod priekšroku vides mainīgajam PYTORCH_ALLOC_CONF. Vecākais PYTORCH_CUDA_ALLOC_CONF paliek kā alias atpakaļejošai saderībai. Šī nosaukuma detaļa mainījās pašreizējā dokumentācijā, tāpēc jaunām konfigurācijām jāizmanto vēlamais nosaukums. PyTorch CUDA vides mainīgie.

Divas dalītāja opcijas ir īpaši nozīmīgas:

  • expandable_segments:True ir eksperimentāls un paredzēts, lai samazinātu nelietojamus atmiņas gabaliņus, mainoties rezervāciju izmēriem, piemēram, slodzēm, kuru partiju vai tenzoru izmēri mainās.
  • max_split_size_mb var samazināt fragmentāciju ar dabisko dalītāju, bet PyTorch to skaidri raksturo kā pēdējo līdzekli slodzēm, kas neizdodas ar OOM, vienlaikus rādot lielu daudzumu neaktīvu sadalītu bloku. Tas var arī pasliktināt veiktspēju, un cudaMallocAsync aizmugurē tas tiek ignorēts.
# Piemērs slodzei ar mainīgiem rezervāciju izmēriem:
export PYTORCH_ALLOC_CONF=expandable_segments:True

Nekopējiet dalītāja karodziņus no citas mašīnas, nepārbaudot memory_summary() vai momentuzņēmumu. Patiesu kapacitātes problēmu—kur aktīvie tenzori jau aizpilda GPU—nevar atrisināt ar fragmentācijas pielāgošanu.

Kad viens GPU joprojām nevar ietilpināt modeli

Ja viena parauga apstrāde ar mazāko praktisko ievades izmēru joprojām izraisa OOM, problēma var būt modelis un optimizatora stāvoklis, nevis partija. Šajā brīdī apsveriet mazāku arhitektūru, zemākas precizitātes parametrus, kur tas ir skaitliski piemērots, CPU/offload stratēģijas vai sadalītu sadalītu apmācību.

PyTorch Fully Sharded Data Parallel (FSDP) var sadalīt modeļa parametrus starp datu paralēliem darbiniekiem, un tā FULL_SHARD stratēģija sadala arī gradientus un optimizatora stāvokļus. Tas var samazināt atmiņu uz vienu GPU, salīdzinot ar pilnībā replicētu datu paralēlismu, par cenu komunikācijas un sarežģītākas apmācības uzvedības. PyTorch FSDP dokumentācija.

Praktiska darbību secība

PrioritāteIzmaiņaAtmiņas ieguvumsGalvenais kompromiss
1Samaziniet mikro-partiju vai ievades izmēruTieši samazina aktīvo darba kopuVar samazināt caurlaidspēju vai mainīt optimizācijas uzvedību
2Izmantojiet AMPVar samazināt aktivizāciju/tenzoru atmiņuNepieciešama skaitliska validācija
3Izmantojiet gradientu uzkrāšanuSaglabā mikro-partijas mazas, vienlaikus saglabājot lielāku efektīvo partijuVairāk soļu vienam optimizatora atjauninājumam
4Izmantojiet aktivizāciju kontrolpunktusSamazina saglabātās aktivizācijasPapildu pārrēķins
5Noņemiet saglabātos tenzorus/grafikusAptur negaidītu pieaugumuNepieciešama koda pārbaude
6Pielāgojiet dalītāja iestatījumusVar palīdzēt fragmentācijas ierobežotos gadījumosAtkarīgs no slodzes; var samazināt veiktspēju
7Sadaliet vai mainiet modeliVar samazināt parametru/stāvokļa atmiņu uz vienu GPUVislielākā sarežģītība

Kontrolsaraksts: kā zināt, ka OOM kļūda ir patiešām novērsta

  • Palaidiet vairākus reprezentatīvus apmācības iterācijas, nevis tikai vienu veiksmīgu uzlādējošo posmu.
  • Atiestatiet un reģistrējiet max_memory_allocated(), lai zinātu jauno pīķi.
  • Pārliecinieties, ka GPU atmiņa sasniedz stabilu diapazonu, nevis pieaug katrā iterācijā.
  • Validējiet zaudējumus un gradientus pēc jauktas precizitātes iespējošanas.
  • Pārliecinieties, ka gradientu uzkrāšana saglabā jūsu paredzēto optimizatora atjaunināšanas grafiku.
  • Palaidiet validācijas posmu zem torch.no_grad(), kad gradienti nav nepieciešami.
  • Ja mainījāt dalītāja iestatījumus, salīdziniet atmiņas statistiku un caurlaidspēju pirms un pēc.
  • Nenosauciet problēmu atrisinātu tikai tāpēc, ka nvidia-smi rāda mazāk rezervētas atmiņas pēc empty_cache(); pašai apmācības slodzei ir jāpabeidz ar savu parasto pīķi.

CUDA OOM kļūda ir vislabāk jāuztver kā atmiņas budžeta problēma, nevis kā viena PyTorch kļūda. Izmēriet pīķi, vispirms samaziniet dzīvo darba kopu, pēc tam izmantojiet jaukto precizitāti, uzkrāšanu un kontrolpunktus kā apzinātus kompromisus. Pārejiet uz dalītāja pielāgošanu tikai tad, kad dalītāja statistika norāda uz fragmentāciju, un pārejiet uz sadalīšanu vai citu modeli, kad pats modelis vairs ērti neietilpst vienā GPU.

Atstājiet komentāru

Kā novērst kļūdu "ENOSPC: sasniegts failu vērotāju sistēmas ierobežojums" operētājsistēmā Linux

Kā novērst kļūdu "ENOSPC: sasniegts failu vērotāju sistēmas ierobežojums" operētājsistēmā Linux

Novērsiet Linux ENOSPC failu vērotāja kļūdas, pārbaudot inotify ierobežojumus, atrodot procesus, kuros ir daudz vērotāja resursu, droši paaugstinot ierobežojumus un padarot izmaiņas pastāvīgas.

Kā novērst kļūdu “Tailwind CSS stili netiek atjaunināti” Vite React lietotnē

Kā novērst kļūdu “Tailwind CSS stili netiek atjaunināti” Vite React lietotnē

Novērsiet Tailwind CSS stilu neatjaunināšanu pakalpojumā Vite React, pārbaudot Tailwind v4 iestatījumus, CSS importēšanu, avota noteikšanu, dinamiskās klases, HMR un novecojušas kešatmiņas.

Kā novērst ModuleNotFoundError kļūdu: Python 3 nav moduļa ar nosaukumu “pip”

Kā novērst ModuleNotFoundError kļūdu: Python 3 nav moduļa ar nosaukumu “pip”

Novērsiet Python 3 ModuleNotFoundError kļūdu pip funkcijai operētājsistēmās Windows, macOS un Linux, izmantojot ensurepip, OS pakotnes, virtuālās vides un interpretētāja pārbaudes.

Kā GitHub SSH novērst kļūdu "Atļauja liegta (publiskā atslēga)"

Kā GitHub SSH novērst kļūdu "Atļauja liegta (publiskā atslēga)"

Novērsiet GitHub SSH atļaujas liegšanu (publiskā atslēga), pārbaudot resursdatoru, aktīvo SSH atslēgu, GitHub kontu, SSO autorizāciju, attālo URL un 22. porta piekļuvi.

Kā novērst kļūdu “Git Push noraidīts: nepārtīšana uz priekšu”, nezaudējot izmaiņas

Kā novērst kļūdu “Git Push noraidīts: nepārtīšana uz priekšu”, nezaudējot izmaiņas

Droši izlabojiet Git ne-ātrās pārtīšanas kļūdu. Aizsargājiet lokālo darbu, ielādējiet attālinātus izmaiņu izmaiņu ierakstus, izvēlieties apvienošanu vai atkārtotu bāzi, atrisiniet konfliktus un veiciet izmaiņu pārtīšanu, nezaudējot izmaiņas.

Kā novērst kļūdu "Nginx 502 Bad Gateway", veicot starpniekservera darbību ar Node.js

Kā novērst kļūdu "Nginx 502 Bad Gateway", veicot starpniekservera darbību ar Node.js

Izlabojiet Nginx 502 Bad Gateway kļūdas ar Node.js augšupējo resursu, pārbaudot lietotnes portu, NGINX žurnālus, proxy_pass adresi, konteineru tīklošanu, taimautus un atkārtotu ielādi.

Kā TypeScript labot kļūdu “Type 'null' nav piešķirams tipam”

Kā TypeScript labot kļūdu “Type 'null' nav piešķirams tipam”

Novērsta TypeScript kļūda “Tips 'null' nav piešķirams tipam”, izmantojot apvienošanas tipus, sašaurināšanu, noklusējuma vērtības un drošas apgalvojumus, izmantojot strictNullChecks.

Kā novērst kļūdu “Prisma Client has not been generated yet”

Kā novērst kļūdu “Prisma Client has not been generated yet”

Novērsiet Prisma Client ģenerēšanas kļūdu, pārbaudot savu ģeneratoru, shēmu, izvades ceļu, importus, versijas, monorepo iestatījumu un izvietošanas būvēšanas soļus.

Kā novērst kļūdu "ERR_MODULE_NOT_FOUND" Node.js ESM importā

Kā novērst kļūdu "ERR_MODULE_NOT_FOUND" Node.js ESM importā

Izlabojiet Node.js ERR_MODULE_NOT_FOUND kļūdu ESM, pārbaudot importēšanas ceļus, failu paplašinājumus, pakotņu instalēšanu, eksportēšanu, ESM režīmu un tīrās instalācijas.

Kā novērst SSL sertifikāta problēmu: Nevar iegūt vietējo izdevēja sertifikātu Git

Kā novērst SSL sertifikāta problēmu: Nevar iegūt vietējo izdevēja sertifikātu Git

Novērsiet Git kļūdu “nevar iegūt vietējo izdevēja sertifikātu”, identificējot uzticības aizmugurprogrammu, instalējot pareizo CA ķēdi un saglabājot SSL verifikāciju iespējotu.