GPU per i tuoi progetti · pagamento crypto senza KYC Come noleggiare
Italiano
Apri la console
Guida pratica / KERNODECK

Il DataLoader si blocca: verificare i dati prima dei worker

Inizia con num_workers=0 e un ordine fisso, verifica un campione e poi un batch completo, e separa lettura, trasformazioni, assemblaggio e trasferimento GPU. Reintroduci poi i worker gradualmente. Una GPU che aspetta non dimostra che lo storage è lento: un errore nei dati, una serializzazione o un assemblaggio costoso può bloccare la catena prima del calcolo.

7 min di lettura · Guida per sviluppatori

Definire ciò che il loader deve fornire

Scrivi il contratto di output prima di ottimizzare: numero di elementi, tipo di ogni campo, dimensioni, intervallo dei target e regola per le voci incomplete. Distingui l'identificatore dell'esempio dalla sua posizione in un batch. Una trasformazione può cambiare una forma o filtrare una voce; il programma di addestramento deve sapere se ciò è consentito.

Prendi un campione rappresentativo che comprenda un file ordinario, un caso limite e l'ultimo elemento del dataset. Apri ogni elemento con esattamente la stessa preparazione del Dataset. Poi esamina il loro assemblaggio. Un accesso individuale riuscito non prova che più risultati possano essere impilati. Per un testo, documenta padding e maschera; per un'immagine, canali, dimensioni e ordine degli assi.

Fissa il perimetro: dati presenti o remoti, decodifica inclusa o meno, trasformazioni fisse o casuali. Mantienilo tra due configurazioni; un guadagno apparente può derivare da un lavoro rimosso.

Tornare a un processo singolo per leggere l'errore

Riproduci prima con num_workers=0, shuffle=False e un batch piccolo. Il caricamento avviene allora nel processo principale e la traccia dell'errore è generalmente più leggibile. La documentazione DataLoader raccomanda questa possibilità per il debug. Registra l'identificatore dell'elemento che fallisce prima della sua decodifica, senza copiare il suo contenuto sensibile nei log.

Procedi per separazione: accesso grezzo, trasformazione, collate_fn, poi trasferimento. Se il percorso fallisce prima del trasferimento, modificare CUDA non è la prima pista. Se si blocca solo con più worker, esamina gli oggetti e le risorse trasmessi a questi processi. Confronta la prima iterazione e le successive: l'avvio dei worker può spiegare un'attesa iniziale senza stabilire un problema ricorrente.

Un timeout può rendere visibile un'attesa, ma non ripara né una sorgente non disponibile né un worker bloccato. Conserva l'ultimo passo noto e riduci il numero di voci invece di aumentare indefinitamente questo timeout.

Esempio pratico: tre canali attesi, un'immagine diversa

Consideriamo quattro record didattici. I primi tre danno un tensore di forma [3, 16, 16], il quarto [1, 16, 16]. Con un contratto che impone tre canali, il quarto elemento deve essere identificato prima dell'impilamento. Questo scenario non è stato eseguito qui; descrive un risultato atteso a partire dalle forme scelte.

La funzione qui sotto presuppone che ogni record abbia i campi id, x e y, che x sia un tensore CPU e che y sia un indice intero. Rifiuta l'incoerenza invece di eliminare discretamente l'immagine. Per il tuo progetto, decidi esplicitamente se un'immagine monocromatica debba essere convertita in tre canali o rifiutata all'importazione. Questa decisione dipende dal significato dei dati e dal preprocessing atteso dal modello.

Dopo la correzione, i quattro identificatori devono restare presenti e il tensore assemblato deve avere forma [4, 3, 16, 16]. Aggiungi una guardia adatta ai target: un'immagine correttamente dimensionata può ancora portare un'annotazione non valida.

Assemblaggio didattico proposto, non eseguito
import torch
from torch.utils.data import DataLoader

def assemble(records):
    for item in records:
        if tuple(item["x"].shape) != (3, 16, 16):
            raise ValueError(f"Forma inattesa per {item['id']}")
    return {
        "ids": [item["id"] for item in records],
        "x": torch.stack([item["x"] for item in records]),
        "y": torch.tensor([item["y"] for item in records],
                          dtype=torch.long),
    }

# dataset è il tuo Dataset che produce i record descritti.
# In uno script multiprocesso, crea il loader sotto la guardia main.
if __name__ == "__main__":
    loader = DataLoader(dataset, batch_size=4, num_workers=0,
                        shuffle=False, collate_fn=assemble)
    iterator = iter(loader)
    batch = next(iterator)

Reintrodurre i worker senza cambiare i dati

Passa da zero a un piccolo numero di worker mantenendo batch, ordine e trasformazioni. Prova un'epoca completa, poi una seconda: alcuni errori emergono solo al riavvio di un iteratore o quando delle risorse sono già state consumate. Aumentare il parallelismo è utile solo se il lavoro di preparazione può effettivamente procedere in parallelo.

I metodi di avvio dipendono dal sistema e dalla versione di Python. Con spawn, proteggi l'ingresso del programma con if __name__ == '__main__' e definisci Dataset, collate_fn e funzioni dei worker a livello di modulo invece che in lambda locali. La documentazione dei processi spiega anche perché lock o thread ereditati possono causare blocchi. Mantieni l'inizializzazione degli accessi specifica per ogni processo quando la libreria lo richiede.

Per un IterableDataset, verifica la partizione tra i worker tramite identificatori: più lavoratori non devono consumare ciascuno lo stesso identico flusso. Non giudicare solo il numero di batch; cerca anche duplicati ed elementi mancanti.

Misurare attesa e throughput con un'unità chiara

Usa due osservazioni complementari. Un percorso del solo loader conta gli esempi preparati durante un intervallo definito. Un percorso integrato esamina ciò che accade quando il modello consuma questi dati. Il primo aiuta a isolare la preparazione; non rappresenta automaticamente il throughput dell'addestramento.

Nel tuo protocollo, conta gli esempi effettivamente consegnati, poi dividi per i secondi trascorsi. Dichiara i passaggi esclusi per avvio, la cache dei dati, le trasformazioni e il numero di ripetizioni. Conserva i valori di ogni passaggio invece di selezionare solo il migliore. La tabella qui sotto è un foglio di rilevazione: non è compilata alcuna prestazione.

Se le forme variano, un numero di esempi al secondo può mascherare un cambiamento di carico. Aggiungi l'unità pertinente, come pixel decodificati o token effettivamente preparati, mantenendo anche gli esempi. Per localizzare le attese nel ciclo completo, nomina la lettura del batch successivo separatamente dal calcolo.

Scorri la tabella per leggere tutte le colonne.
Rilevazione da compilare con il tuo carico, senza valori di prestazione presunti
ImpostazioneElementi verificatiDurata osservataConclusione attesa
workers=0Identificatori, forme, targetDa misurare in secondiRiferimento corretto
Piccolo numero di workerStesso insieme di inputDa misurare in secondiGuadagno o sovraccosto reale
Stessa impostazione, seconda epocaNessuna perdita né duplicazioneDa misurare in secondiEffetto dell'avvio e delle cache

Trattare memoria, precaricamento e trasferimenti separatamente

I worker e i batch in attesa consumano memoria host. Monitorala durante la tua prova prima di concludere che conti solo la VRAM. Un precaricamento più profondo può spostare l'attesa aumentando l'occupazione; non garantisce più risultati al secondo. Riduci prima la variabile sospetta e confronta lo stesso perimetro.

pin_memory e i trasferimenti non bloccanti riguardano il passaggio dei dati verso un acceleratore. La guida all'ottimizzazione di PyTorch li presenta come leve da esaminare insieme all'hardware e al carico. Non correggono una decodifica errata. Inizia con dati su CPU nei worker, poi organizza il trasferimento nel processo che guida il calcolo. Il beneficio e la sovrapposizione effettiva devono essere osservati, non presunti.

Se usi persistent_workers, tieni conto delle risorse e degli stati conservati tra due epoche. Un'impostazione soddisfacente su un singolo batch non basta a verificare la chiusura dei file o il rinnovo della sorgente.

Accettare un'impostazione solo se i dati restano corretti

Il risultato atteso è un ciclo che riceve tutti gli input previsti, nell'ambito scelto, senza errori silenziosi. Confronta gli identificatori e i target prima e dopo l'ottimizzazione. Spiega drop_last se escludi l'ultimo batch incompleto. Se le trasformazioni sono casuali, controlla la loro politica invece di pretendere un'uguaglianza di pixel che contraddirebbe tale politica.

Mantieni l'impostazione più semplice che risponde al bisogno misurato. Un aumento del numero di worker può non migliorare nulla se l'archiviazione, il decoding o il modello impongono già un limite. Le risorse CPU, RAM e archiviazione di un server non si deducono dal nome della sua GPU: specifica questi requisiti separatamente quando prepari il tuo ambiente Kernodeck.

Le tue domande

num_workers=0 disattiva l'addestramento su GPU?

No. Colloca il caricamento dei dati nel processo principale. Il modello può comunque calcolare su GPU. Questa impostazione serve soprattutto a leggere più direttamente gli errori di lettura, trasformazione e assemblaggio.

Bisogna scegliere tanti worker quanti sono i core CPU?

Non automaticamente. La buona impostazione dipende dal lavoro di preparazione, dalla memoria, dagli accessi ai dati e dalla velocità di consumo del modello. Confronta qualche valore mantenendo lo stesso carico e controllando gli input forniti.

Un batch più piccolo corregge un worker che si interrompe?

Può modificare la pressione sulla memoria, ma non corregge un'annotazione non valida, una risorsa non serializzabile o un file illeggibile. Riproduci prima con zero worker per identificare la fase in causa.

Il tempo trascorso in next(iterator) misura il disco?

No. Dopo iterator=iter(loader), next(iterator) attende un batch. Decoding, trasformazioni, assemblaggio, comunicazione tra processi e precaricamento possono intervenire. Una misura di sola lettura e una traccia del ciclo completo rispondono a domande diverse.