Choisir une question avant d’ouvrir une trace
Une trace répond mieux à une question précise : le calcul attend-il les données, une copie est-elle répétée, ou un petit opérateur est-il appelé trop souvent ? Définissez l’entrée, la sortie et les frontières de votre étape. Pour l’entraînement, précisez si elle contient backward, mise à jour de l’optimiseur et lecture du batch. Pour l’inférence, séparez chargement du modèle et requête.
Gardez un scénario court dont vous connaissez la correction. Notez formes, batch, précision, mode du modèle, compilation éventuelle et source des données. Une entrée simplifiée peut aider à isoler un comportement, mais elle ne représente plus nécessairement la charge complète. Mentionnez cette différence dans le relevé.
Fixez l’unité finale : millisecondes par étape ou exemples traités par seconde. Les sommes d’événements ne remplacent pas le temps écoulé. Comparez des fenêtres ayant les mêmes frontières de lecture et transfert.
Distinguer temps CPU, travail GPU et attente
Le CPU prépare et lance des opérations ; le GPU peut les exécuter plus tard. Un intervalle Python peut donc contenir du lancement, de l’attente ou les deux. La documentation CUDA de PyTorch indique que les mesures précises doivent tenir compte de cet asynchronisme, notamment par synchronisation ou événements adaptés au périmètre.
Pour une mesure de durée GPU ciblée, les événements CUDA peuvent convenir. Pour une latence de bout en bout, attendez la fin du travail compris dans cette latence et mesurez l’ensemble de la demande. Ne mélangez pas ces deux unités. Une synchronisation ajoutée entre chaque opération peut supprimer un chevauchement réel et transformer le programme que vous cherchez à comprendre.
Dans le profileur, les temps propres d’un opérateur et ses temps incluant des sous-opérations répondent aussi à des questions différentes. Regardez la chronologie avant d’additionner les lignes du tableau. Des activités simultanées ou imbriquées ne représentent pas des portions de temps écoulé disjointes.
Réserver une phase de démarrage et une fenêtre active
Le premier passage peut inclure initialisation, chargement ou compilation. Décidez si votre question porte sur ce démarrage ou sur une phase déjà stabilisée. Conservez les deux observations lorsqu’elles comptent pour l’usage, au lieu d’effacer le coût initial d’une moyenne présentée comme globale.
La fonction schedule permet de distinguer attente, préparation de la collecte et fenêtre active. L’échauffement du profileur n’est pas une preuve que votre modèle a atteint son régime stable. Vérifiez aussi les formes rencontrées et l’état des caches. Une application à entrées variables peut rencontrer de nouveaux chemins après plusieurs itérations.
Le planning pédagogique proposé plus bas attend une étape, en prépare une et en capture deux. Quatre étapes suffisent à expliquer le mécanisme, pas à établir une distribution de performance. Dans une vraie campagne, choisissez une fenêtre justifiée et répétez la mesure hors profileur après l’analyse.
Exemple proposé : quatre étapes avec des frontières lisibles
Le fragment suivant n’a pas été exécuté. Il suppose que model, optimizer, loss_fn, loader et device existent, que le modèle est sur device et que le chargeur fournit au moins quatre batches de paires x, y. Il effectue des mises à jour : utilisez un état expérimental prévu pour cela, pas une session dont vous devez préserver les poids.
Les libellés séparent lecture CPU, transfert et entraînement. L’itérateur est créé avant la fenêtre, ce qui exclut une partie de son démarrage du périmètre. Le fichier est choisi neuf pour éviter d’écraser une trace. L’exemple refuse une collecte GPU indisponible au lieu de présenter discrètement une trace CPU comme une analyse GPU.
Le signal step() avance le planning après chaque étape. La recette officielle décrit ce lien entre itérations et collecte. Sur une pile ROCm, le périphérique PyTorch reste nommé cuda ; la disponibilité de la collecte accélérateur dépend néanmoins du build et de ses outils. Vérifiez les activités réellement présentes dans le résultat.
from pathlib import Path
import torch
from torch.profiler import (
profile, schedule, record_function,
ProfilerActivity, supported_activities,
)
trace = Path("trace-etape.json")
if trace.exists():
raise FileExistsError("Choisissez un nouveau nom de trace")
activities = [ProfilerActivity.CPU]
if device.type == "cuda":
if ProfilerActivity.CUDA not in supported_activities():
raise RuntimeError("Collecte GPU indisponible dans ce build")
activities.append(ProfilerActivity.CUDA)
iterator = iter(loader)
model.train()
with profile(
activities=activities,
schedule=schedule(wait=1, warmup=1, active=2, repeat=1),
record_shapes=False, profile_memory=False, with_stack=False,
on_trace_ready=lambda p: p.export_chrome_trace(str(trace)),
) as prof:
for _ in range(4):
with record_function("lecture_batch_cpu"):
x, y = next(iterator)
with record_function("transfert"):
x, y = x.to(device), y.to(device)
with record_function("entrainement"):
optimizer.zero_grad(set_to_none=True)
loss = loss_fn(model(x), y)
loss.backward()
optimizer.step()
prof.step()
print(prof.key_averages().table(
sort_by="self_cpu_time_total", row_limit=8,
))Transformer une observation en hypothèse vérifiable
Commencez par la fenêtre active et vérifiez que les libellés attendus apparaissent. Examinez ensuite les espaces entre activités, les copies et les répétitions. Une longue attente dans lecture_batch_cpu oriente vers le pipeline d’entrée ; elle ne mesure pas directement le stockage. Une copie fréquente invite à examiner le placement des tenseurs, sans prouver qu’elle est inutile.
Formulez une seule hypothèse puis proposez une modification contrôlée. Par exemple, si une constante est reconstruite et transférée à chaque étape, vérifiez si sa durée de vie peut couvrir plusieurs étapes sans changer le résultat. Si un opérateur semble dominant, inspectez ses formes et le nombre d’appels avant d’en chercher un remplacement.
Les lignes ci-dessous sont des lectures possibles, pas des constats tirés d’une trace exécutée. Aucune durée ni accélération n’est annoncée. La conclusion utile est une expérience suivante qui peut confirmer ou réfuter la cause proposée.
Faites défiler le tableau pour lire toutes les colonnes.| Observation possible | Hypothèse | Contrôle suivant |
|---|---|---|
| GPU sans activité pendant la lecture | Le pipeline d’entrée ne suit pas | Même calcul avec batch déjà préparé |
| Copies récurrentes d’une constante | Placement ou durée de vie inadaptés | Déplacer une fois, vérifier les sorties |
| Nombreux petits lancements | Travail fragmenté | Examiner regroupement et coût global |
| Un opérateur long | Forme ou algorithme déterminant | Comparer le même opérateur et ses entrées |
Limiter le coût et les informations de l’instrumentation
Commencez avec une collecte courte et peu d’options. Activez les formes, les piles ou la mémoire seulement si une question le demande. L’API PyTorch précise que ces informations ajoutent un coût ; la collecte des formes peut même retenir des références aux tenseurs. Un profil détaillé peut donc changer les durées ou l’occupation mémoire du programme.
Une trace peut contenir des noms d’opérateurs, des formes et, selon les options, des chemins de code. Inspectez-la avant de la partager. Un libellé de zone doit décrire l’étape sans embarquer d’email, de token, de chemin privé ou de contenu d’entrée. Choisissez un lecteur de trace adapté à votre environnement et gardez la collecte sous votre contrôle.
Si les événements GPU attendus sont absents, ne remplissez pas leurs temps avec zéro. Indiquez qu’ils n’ont pas été observés et examinez le support de la collecte. Une absence d’événement dans l’outil n’est pas une preuve d’absence de calcul.
Valider l’optimisation hors profileur
Repartez du même état pertinent et comparez la correction avant et après modification. Pour l’entraînement, une étape supplémentaire change les poids ; deux captures lancées successivement ne constituent pas nécessairement une comparaison équivalente. Conservez code, paramètres et point de départ afin de pouvoir expliquer la différence.
Mesurez ensuite le scénario sans collecte détaillée, avec le même échauffement et plusieurs passages. Rapportez le périmètre, les valeurs brutes et leur dispersion. Une amélioration locale peut disparaître dans la boucle entière ou dégrader la qualité : les deux doivent rester dans la décision.
Utilisez enfin les observations pour préciser les ressources réellement nécessaires. Une trace de votre poste ne classe pas les offres Kernodeck et ne prouve ni le processeur hôte ni le réseau d’un serveur loué. La comparaison de GPU exige une charge, des conditions et des résultats comparables sur les ressources concernées.