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.
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.| Réglage | Éléments vérifiés | Durée observée | Conclusion attendue |
|---|---|---|---|
| workers=0 | Identifiants, formes, cibles | À mesurer en secondes | Référence correcte |
| Petit nombre de workers | Même ensemble d’entrées | À mesurer en secondes | Gain ou surcoût réel |
| Même réglage, seconde époque | Aucune perte ni duplication | À mesurer en secondes | Effet 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.