GPU pour vos projets · paiement crypto sans KYC Comment louer
Français
Ouvrir la console
Guide pratique / KERNODECK

Le DataLoader bloque : vérifier les données avant les workers

Commencez avec num_workers=0 et un ordre fixe, vérifiez un échantillon puis un batch complet, et séparez lecture, transformations, assemblage et transfert GPU. Réintroduisez ensuite les workers progressivement. Un GPU qui attend ne prouve pas que le stockage est lent : une erreur de données, une sérialisation ou un assemblage coûteux peut bloquer la chaîne avant le calcul.

7 min de lecture · Guide pour développeurs

Définir ce que le chargeur doit livrer

Écrivez le contrat de sortie avant d’optimiser : nombre d’éléments, type de chaque champ, dimensions, plage des cibles et règle des entrées incomplètes. Distinguez l’identifiant de l’exemple de sa position dans un batch. Une transformation peut changer une forme ou filtrer une entrée ; le programme d’entraînement doit savoir si cela est autorisé.

Prenez un échantillon représentatif comprenant un fichier ordinaire, un cas limite et le dernier élément du jeu. Ouvrez chaque élément avec exactement la même préparation que le Dataset. Puis examinez leur assemblage. Un accès individuel réussi ne prouve pas que plusieurs résultats peuvent être empilés. Pour un texte, documentez padding et masque ; pour une image, canaux, dimensions et ordre des axes.

Fixez le périmètre : données présentes ou distantes, décodage inclus ou non, transformations fixes ou aléatoires. Gardez-le entre deux réglages ; un gain apparent peut venir d’un travail supprimé.

Revenir à un processus pour lire l’erreur

Reproduisez d’abord avec num_workers=0, shuffle=False et un petit batch. Le chargement se déroule alors dans le processus principal et la trace d’erreur est généralement plus lisible. La documentation DataLoader recommande cette possibilité pour déboguer. Enregistrez l’identifiant de l’élément qui échoue avant son décodage, sans recopier son contenu sensible dans les journaux.

Procédez par séparation : accès brut, transformation, collate_fn, puis transfert. Si le parcours échoue avant le transfert, modifier CUDA n’est pas la première piste. S’il bloque seulement avec plusieurs workers, examinez les objets et ressources transmis à ces processus. Comparez la première itération et les suivantes : le démarrage des travailleurs peut expliquer une attente initiale sans établir un problème récurrent.

Un timeout peut rendre une attente visible, mais ne répare ni une source indisponible ni un worker bloqué. Gardez la dernière étape connue et réduisez le nombre d’entrées au lieu d’augmenter indéfiniment ce délai.

Exemple travaillé : trois canaux attendus, une image différente

Considérons quatre enregistrements pédagogiques. Les trois premiers donnent un tenseur de forme [3, 16, 16], le quatrième [1, 16, 16]. Avec un contrat imposant trois canaux, le quatrième élément doit être identifié avant l’empilement. Ce scénario n’a pas été exécuté ici ; il décrit un résultat attendu à partir des formes choisies.

La fonction ci-dessous suppose que chaque enregistrement possède les champs id, x et y, que x est un tenseur CPU et que y est un indice entier. Elle refuse l’incohérence au lieu de supprimer discrètement l’image. Pour votre projet, décidez explicitement si une image monochrome doit être convertie en trois canaux ou rejetée à l’import. Cette décision dépend du sens des données et du prétraitement attendu par le modèle.

Après correction, les quatre identifiants doivent rester présents et le tenseur assemblé doit avoir la forme [4, 3, 16, 16]. Ajoutez une garde adaptée aux cibles : une image correctement dimensionnée peut encore porter une annotation invalide.

Assemblage pédagogique proposé, non exécuté
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"Forme inattendue pour {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 est votre Dataset produisant les enregistrements décrits.
# Dans un script multiprocessus, créer le loader sous le garde main.
if __name__ == "__main__":
    loader = DataLoader(dataset, batch_size=4, num_workers=0,
                        shuffle=False, collate_fn=assemble)
    iterator = iter(loader)
    batch = next(iterator)

Réintroduire les workers sans changer les données

Passez de zéro à un petit nombre de workers en conservant batch, ordre et transformations. Testez une époque complète, puis une seconde : certaines erreurs n’apparaissent qu’au redémarrage d’un itérateur ou lorsque des ressources ont été consommées. Augmenter le parallélisme n’est utile que si le travail de préparation peut effectivement avancer en parallèle.

Les méthodes de démarrage dépendent du système et de la version de Python. Avec spawn, protégez l’entrée du programme par if __name__ == '__main__' et définissez Dataset, collate_fn et fonctions de workers au niveau du module plutôt que dans des lambdas locales. La documentation des processus explique aussi pourquoi des verrous ou threads hérités peuvent provoquer des blocages. Gardez l’initialisation des accès propres à chaque processus quand la bibliothèque l’exige.

Pour un IterableDataset, vérifiez la partition entre workers au moyen d’identifiants : plusieurs travailleurs ne doivent pas consommer chacun tout le même flux. Ne jugez pas seulement le nombre de batches ; recherchez aussi doublons et éléments manquants.

Mesurer l’attente et le débit avec une unité claire

Utilisez deux observations complémentaires. Un parcours du chargeur seul compte les exemples préparés pendant un intervalle défini. Un parcours intégré examine ce qui se passe quand le modèle consomme ces données. Le premier aide à isoler la préparation ; il ne représente pas automatiquement le débit de l’entraînement.

Dans votre protocole, comptez des exemples effectivement livrés, puis divisez par des secondes écoulées. Déclarez les passages exclus pour démarrage, le cache de données, les transformations et le nombre de répétitions. Gardez les valeurs de chaque passage au lieu de sélectionner seulement le meilleur. Le tableau ci-dessous est une feuille de relevé : aucune performance n’est remplie.

Si les formes varient, un nombre d’exemples par seconde peut masquer un changement de charge. Ajoutez l’unité pertinente, comme pixels décodés ou tokens effectivement préparés, en conservant aussi les exemples. Pour localiser les attentes dans la boucle complète, nommez la lecture du prochain batch séparément du calcul.

Faites défiler le tableau pour lire toutes les colonnes.
Relevé à remplir avec votre charge, sans valeurs de performance supposées
RéglageÉléments vérifiésDurée observéeConclusion attendue
workers=0Identifiants, formes, ciblesÀ mesurer en secondesRéférence correcte
Petit nombre de workersMême ensemble d’entréesÀ mesurer en secondesGain ou surcoût réel
Même réglage, seconde époqueAucune perte ni duplicationÀ mesurer en secondesEffet du démarrage et des caches

Traiter mémoire, préchargement et transferts séparément

Les workers et les batches en attente consomment de la mémoire hôte. Surveillez-la pendant votre essai avant de conclure que seule la VRAM compte. Un préchargement plus profond peut déplacer l’attente tout en augmentant l’occupation ; il ne garantit pas davantage de résultats par seconde. Réduisez d’abord la variable suspecte et comparez le même périmètre.

pin_memory et les transferts non bloquants concernent le passage de données vers un accélérateur. La recette d’optimisation PyTorch les présente comme des leviers à examiner avec le matériel et la charge. Ils ne corrigent pas un décodage erroné. Commencez par des données CPU dans les workers, puis organisez le transfert dans le processus qui pilote le calcul. Le bénéfice et le chevauchement effectif doivent être observés, pas supposés.

Si persistent_workers est utilisé, pensez aux ressources et états conservés entre deux époques. Un réglage satisfaisant sur un seul batch ne suffit pas à vérifier la fermeture des fichiers ou le renouvellement de la source.

Accepter un réglage seulement si les données restent correctes

Le résultat attendu est une boucle qui reçoit toutes les entrées prévues, dans le cadre choisi, sans erreur silencieuse. Comparez les identifiants et les cibles avant et après optimisation. Expliquez drop_last si vous écartez le dernier batch incomplet. Si les transformations sont aléatoires, contrôlez leur politique plutôt que d’exiger une égalité de pixels qui contredirait cette politique.

Conservez le réglage le plus simple qui répond au besoin mesuré. Une hausse du nombre de workers peut ne rien améliorer si le stockage, le décodage ou le modèle impose déjà une limite. Les ressources CPU, RAM et stockage d’un serveur ne se déduisent pas du nom de son GPU : précisez ces besoins séparément lorsque vous préparez votre environnement Kernodeck.

Vos questions

num_workers=0 désactive-t-il l’entraînement GPU ?

Non. Il place le chargement des données dans le processus principal. Le modèle peut toujours calculer sur GPU. Ce réglage permet surtout de lire plus directement les erreurs de lecture, transformation et assemblage.

Faut-il choisir autant de workers que de cœurs CPU ?

Pas automatiquement. Le bon réglage dépend du travail de préparation, de la mémoire, des accès aux données et de la vitesse de consommation du modèle. Comparez quelques valeurs en conservant la même charge et en contrôlant les entrées livrées.

Un batch plus petit corrige-t-il un worker qui s’arrête ?

Il peut modifier la pression mémoire, mais ne corrige pas une annotation invalide, une ressource non sérialisable ou un fichier illisible. Reproduisez d’abord avec zéro worker pour identifier l’étape en cause.

Le temps passé dans next(iterator) mesure-t-il le disque ?

Non. Après iterator=iter(loader), next(iterator) attend un batch. Décodage, transformations, assemblage, communication entre processus et préchargement peuvent intervenir. Une mesure de lecture seule et une trace de la boucle complète répondent à des questions différentes.