`torch.export`: La Nouvelle API PyTorch pour un Déploiement Modèle Simplifié

Towardsdatascience

Lorsqu’on se lance dans un nouveau projet d’intelligence artificielle ou d’apprentissage automatique, l’essentiel de l’attention se porte naturellement sur des tâches monumentales : la curation de vastes ensembles de données, l’architecture de modèles sophistiqués et la sécurisation de puissants clusters GPU pour l’entraînement. Pourtant, ce sont souvent les détails apparemment mineurs qui deviennent des obstacles inattendus, entraînant des bugs frustrants et des retards de production significatifs. Un excellent exemple est le transfert d’un modèle entraîné de l’environnement de développement vers son homologue d’inférence. Bien que cette étape puisse sembler simple, la réalité des différentes bibliothèques d’exécution, des configurations matérielles et du versionnage peut la transformer en un casse-tête considérable. Les développeurs doivent s’assurer que la définition du modèle et ses poids entraînés se chargent correctement et, surtout, que son comportement reste inchangé.

Traditionnellement, deux méthodes principales ont été employées pour cette capture et ce déploiement critiques de modèles. La première, et la plus simple, consiste à sauvegarder uniquement les poids du modèle à l’aide de torch.save. Cette approche offre une flexibilité maximale, permettant des optimisations spécifiques à la machine dans l’environnement d’inférence. Cependant, elle nécessite de redéfinir explicitement l’architecture du modèle dans le cadre du déploiement, ce qui peut introduire des cauchemars de versionnage et des incompatibilités de dépendances, en particulier dans des environnements contraints où le contrôle sur les bibliothèques d’exécution est limité. La séparation de la définition et des poids devient souvent un terrain fertile pour des “bugs disgracieux”, exigeant une gestion rigoureuse des versions.

Pendant des années, la solution plus complète a été TorchScript, qui regroupe à la fois la définition du modèle et les poids dans une représentation graphique sérialisable. TorchScript offrait deux fonctionnalités distinctes : torch.jit.script et torch.jit.trace. Le scripting effectue une analyse statique du code source, capable de capturer des éléments complexes comme le flux de contrôle conditionnel et les formes d’entrée dynamiques. Le traçage, par contraste, enregistre le chemin d’exécution réel d’un modèle sur un échantillon d’entrée, ce qui le rend moins sujet à certaines défaillances mais incapable de gérer un comportement dynamique. Souvent, une combinaison des deux était nécessaire, mais même alors, TorchScript peinait fréquemment avec des modèles complexes, exigeant des réécritures de code minutieuses et intrusives pour assurer la compatibilité. Nos propres expériences avec un modèle génératif d’image-vers-texte de HuggingFace ont démontré cette limitation : si l’encodeur à entrée fixe pouvait être tracé, le décodeur à forme dynamique échouait systématiquement à être scripté sans modifications significatives du code de la bibliothèque sous-jacente.

Voici torch.export, la nouvelle solution plus robuste de PyTorch pour la capture de modèles. Similaire à torch.jit.trace, torch.export fonctionne en traçant l’exécution du modèle. Cependant, il améliore significativement son prédécesseur en intégrant le support du dynamisme et du flux de contrôle conditionnel, surmontant ainsi de nombreuses limitations historiques de TorchScript. Le résultat est une représentation graphique intermédiaire, connue sous le nom d’Export IR, qui peut être chargée et exécutée comme un programme PyTorch autonome avec des dépendances minimales. Un avantage clé de torch.export est sa compatibilité avec torch.compile, permettant des optimisations supplémentaires à la volée dans l’environnement d’inférence, une capacité qui n’était pas étendue aux modèles TorchScript. Cette fonctionnalité est sous-tendue par Torch Dynamo, un composant central de la solution de compilation graphique de PyTorch.

Malgré ses puissantes capacités, torch.export est encore une fonctionnalité prototype et présente son propre ensemble de défis. Un obstacle courant est la “rupture de graphe” (graph break), qui se produit lorsque la fonction d’exportation rencontre du code Python intraçable. Contrairement à la compilation de modèles, où PyTorch pourrait revenir à l’exécution impatiente (eager execution), torch.export interdit strictement les ruptures de graphe, obligeant les développeurs à réécrire leur code pour les contourner. Le débogage des graphes exportés peut également être délicat ; bien qu’ils se comportent comme des objets torch.nn.Module standard, les débogueurs traditionnels ne peuvent pas entrer dans leur fonction forward compilée. Des problèmes surviennent souvent lorsque des variables de l’environnement d’exportation sont “intégrées” par inadvertance dans le graphe en tant que constantes, entraînant des erreurs d’exécution dans différents environnements. Par exemple, notre décodeur exporté a initialement échoué sur un GPU en raison de références de périphériques CPU codées en dur depuis son environnement d’exportation, nécessitant un “monkey-patching” manuel de la bibliothèque HuggingFace pour résoudre le problème. Bien qu’efficace pour notre modèle jouet, de telles modifications intrusives sont déconseillées pour les systèmes de production sans tests approfondis.

Lors des tests sur notre modèle d’exemple, torch.export a réussi à capturer à la fois l’encodeur et le décodeur sans rencontrer de ruptures de graphe, ce qui représente une amélioration significative par rapport à TorchScript. Le déploiement du modèle exporté corrigé sur une instance Amazon EC2 a montré une modeste accélération de 10,7 % du temps d’inférence par rapport au modèle original. Fait intéressant, l’application de torch.compile au modèle exporté, bien que prometteuse, a inopinément augmenté le temps d’exécution dans ce scénario spécifique, soulignant la nécessité d’un ajustement minutieux des paramètres de compilation.

En résumé, torch.export représente un bond en avant convaincant dans le déploiement de modèles PyTorch. Il démontre un support supérieur pour les modèles complexes, permettant la capture d’architectures qui auparavant déconcertaient TorchScript. Les modèles exportés résultants sont hautement portables, capables d’une exécution autonome sans dépendances de paquets étendues, et sont compatibles avec de puissantes optimisations spécifiques à la machine via torch.compile. Cependant, en tant que prototype en évolution rapide, il présente actuellement des limitations, y compris la possibilité que des valeurs spécifiques à l’environnement non intentionnelles soient intégrées dans le graphe et un ensemble naissant d’outils de débogage. Malgré ces imperfections, torch.export est une amélioration substantielle par rapport aux solutions antérieures, et il est très prometteur pour rationaliser le dernier kilomètre critique du développement de modèles d’IA.