Organisation d’un Monorepo ML avec Pants
Organisation Monorepo ML avec Pants
Avez-vous déjà copié-collé des morceaux de code utilitaire entre des projets, ce qui a entraîné plusieurs versions du même code dans différents référentiels ? Ou peut-être avez-vous dû faire des demandes de tirage à des dizaines de projets après la mise à jour du nom du bucket GCP dans lequel vous stockez vos données ?
Les situations décrites ci-dessus se produisent trop souvent dans les équipes de ML, et leurs conséquences vont de l’agacement d’un seul développeur à l’incapacité de l’équipe à livrer leur code comme requis. Heureusement, il existe un remède.
Plongeons dans le monde des monorepos, une architecture largement adoptée par les grandes entreprises technologiques comme Google, et voyons comment ils peuvent améliorer vos flux de travail en ML. Un monorepo offre une multitude d’avantages qui, malgré quelques inconvénients, en font un choix convaincant pour la gestion d’écosystèmes d’apprentissage automatique complexes.
Nous débattrons brièvement des mérites et des défauts des monorepos, examinerons pourquoi c’est un excellent choix d’architecture pour les équipes d’apprentissage automatique, et jetterons un œil sur la façon dont les BigTech l’utilisent. Enfin, nous verrons comment exploiter la puissance du système de compilation Pants pour organiser votre monorepo d’apprentissage automatique en un système de compilation solide CI/CD.
- Apportez votre propre IA en utilisant Amazon SageMaker avec Salesforce Data Cloud
- Utilisez l’intégration d’Amazon SageMaker et Salesforce Data Cloud pour alimenter vos applications Salesforce avec l’IA/ML.
- Évolution d’OpenAI une course vers GPT5
Attachez-vous les ceintures alors que nous nous embarquons dans ce voyage pour rationaliser la gestion de votre projet ML.
Qu’est-ce qu’un monorepo ?

Un monorepo (abrégé de dépôt monolithique) est une stratégie de développement de logiciels où le code de nombreux projets est stocké dans le même référentiel. L’idée peut être aussi large que l’ensemble du code de l’entreprise écrit dans une variété de langages de programmation stockés ensemble (quelqu’un a-t-il mentionné Google ?) ou aussi étroite que quelques projets Python développés par une petite équipe regroupés dans un seul référentiel.
Dans cet article de blog, nous nous concentrons sur les référentiels stockant du code d’apprentissage automatique.
Monorepos vs. polyrepos
Les monorepos sont en contraste marqué avec l’approche polyrepos, où chaque projet individuel ou composant a son propre référentiel distinct. Beaucoup de choses ont été dites sur les avantages et les inconvénients des deux approches, et nous n’entrerons pas trop dans les détails. Mettons simplement les bases sur la table.
L’architecture monorepo offre les avantages suivants :

- Pipeline CI/CD unique, ce qui signifie qu’il n’y a pas de connaissances de déploiement cachées réparties entre les contributeurs individuels de différents référentiels ;
- Commits atomiques, étant donné que tous les projets résident dans le même référentiel, les développeurs peuvent apporter des modifications transversales qui s’étendent sur plusieurs projets mais sont fusionnées en un seul commit ;
- Partage facile d’utilitaires et de modèles entre les projets ;
- Unification facile des normes et des approches de codage ;
- Meilleure découvrabilité du code.
Naturellement, il n’y a pas de repas gratuit. Nous devons payer pour les avantages ci-dessus, et le prix se présente sous la forme de :
- Défis d’évolutivité : À mesure que la base de code se développe, la gestion d’un monorepo peut devenir de plus en plus difficile. À une échelle vraiment importante, vous aurez besoin d’outils et de serveurs puissants pour gérer des opérations telles que le clonage, la récupération et la publication des modifications, ce qui peut prendre un temps et des ressources considérables.
- Complexité : Un monorepo peut être plus complexe à gérer, notamment en ce qui concerne les dépendances et la gestion des versions. Un changement dans un composant partagé pourrait potentiellement avoir un impact sur de nombreux projets, il faut donc être particulièrement prudent pour éviter les changements qui cassent tout.
- Visibilité et contrôle d’accès : Avec tout le monde travaillant à partir du même référentiel, il peut être difficile de contrôler qui a accès à quoi. Bien que ce ne soit pas un inconvénient en soi, cela pourrait poser des problèmes d’ordre juridique dans les cas où le code est soumis à une NDA très stricte.
La décision de savoir si les avantages qu’offre un monorepo valent le prix à payer doit être déterminée par chaque organisation ou équipe individuellement. Cependant, à moins que vous n’opériez à une échelle prohibitivement grande ou que vous ne traitiez de missions top secrètes, je soutiendrais que – du moins en ce qui concerne mon domaine d’expertise, les projets d’apprentissage automatique – un monorepo est un bon choix d’architecture dans la plupart des cas.
Parlons de pourquoi cela est.
Apprentissage automatique avec les monorepos
Il existe au moins six raisons pour lesquelles les monorepos sont particulièrement adaptés aux projets d’apprentissage automatique.
-
1
Intégration du pipeline de données -
2
Cohérence entre les expérimentations -
3
Simplification de la gestion des versions des modèles -
4
Collaboration interfonctionnelle -
5
Changements atomiques -
6
Unification des normes de codage
Intégration du pipeline de données
Les projets d’apprentissage automatique impliquent souvent des pipelines de données qui prétraitent, transforment et alimentent les données dans le modèle. Ces pipelines peuvent être étroitement intégrés au code d’apprentissage automatique. Garder les pipelines de données et le code d’apprentissage automatique dans le même référentiel aide à maintenir cette intégration étroite et à rationaliser le flux de travail.
Cohérence entre les expérimentations
Le développement de l’apprentissage automatique implique beaucoup d’expérimentation. Avoir toutes les expérimentations dans un monorepo garantit des configurations d’environnement cohérentes et réduit le risque de divergences entre différentes expérimentations dues à des versions de code ou de données différentes.
Simplification de la gestion des versions des modèles
Dans un monorepo, les versions du code et du modèle sont synchronisées car elles sont intégrées dans le même référentiel. Cela facilite la gestion et la traçabilité des versions du modèle, ce qui peut être particulièrement important dans les projets où la reproductibilité de l’apprentissage automatique est essentielle.
Il suffit de prendre le SHA de validation à un moment donné, et cela donne des informations sur l’état de tous les modèles et services.
Collaboration interfonctionnelle
Les projets d’apprentissage automatique impliquent souvent une collaboration entre des data scientists, des ingénieurs en apprentissage automatique et des ingénieurs en logiciel. Un monorepo facilite cette collaboration interfonctionnelle en fournissant une source unique de vérité pour tout le code et les ressources liés au projet.
Changements atomiques
Dans le contexte de l’apprentissage automatique, les performances d’un modèle peuvent dépendre de divers facteurs interconnectés tels que la prétraitement des données, l’extraction des caractéristiques, l’architecture du modèle et le post-traitement. Un monorepo permet des changements atomiques – un changement de plusieurs de ces composants peut être validé en une seule fois, en veillant à ce que les interdépendances soient toujours synchronisées.
Unification des normes de codage
Enfin, les équipes d’apprentissage automatique incluent souvent des membres sans formation en génie logiciel. Ces mathématiciens, statisticiens et économètres sont des personnes intelligentes avec des idées brillantes et des compétences pour former des modèles qui résolvent des problèmes commerciaux. Cependant, écrire un code propre, facile à lire et à maintenir n’est pas toujours leur point fort.
Un monorepo aide en vérifiant et en appliquant automatiquement les normes de codage sur tous les projets, ce qui garantit non seulement une grande qualité de code, mais aide également les membres de l’équipe moins enclins à l’ingénierie à apprendre et à progresser.
Comment cela se fait-il dans l’industrie : célèbres monorepos
Dans le paysage du développement logiciel, certaines des plus grandes et des plus réussies entreprises du monde utilisent des monorepos. Voici quelques exemples notables.
- Google : Google est depuis longtemps un fervent partisan de l’approche monorepo. Leur base de code entière, estimée à contenir 2 milliards de lignes de code, est contenue dans un seul et immense référentiel. Ils ont même publié un article à ce sujet.
- Meta : Meta utilise également un monorepo pour leur vaste base de code. Ils ont créé un système de contrôle de version appelé “Mercurial” pour gérer la taille et la complexité de leur monorepo.
- Twitter : Twitter gère son monorepo depuis longtemps en utilisant Pants, le système de construction dont nous parlerons ensuite !
De nombreuses autres entreprises telles que Microsoft, Uber, Airbnb et Stripe utilisent également l’approche monorepo pour certaines parties de leurs bases de code.
Assez de théorie ! Voyons maintenant comment construire réellement un monorepo d’apprentissage automatique. Parce que simplement regrouper ce qui était autrefois des référentiels distincts dans un seul dossier ne suffit pas.
Comment configurer un monorepo d’apprentissage automatique avec Python ?
Tout au long de cette section, nous baserons notre discussion sur un référentiel d’apprentissage automatique que j’ai créé pour cet article. Il s’agit d’un simple monorepo contenant un seul projet, ou module : un classifieur de chiffres écrits à la main appelé mnist, d’après le célèbre ensemble de données qu’il utilise.
Tout ce que vous devez savoir pour l’instant, c’est qu’à la racine du monorepo se trouve un répertoire appelé mnist, et à l’intérieur, il y a du code Python pour l’entraînement du modèle, les tests unitaires correspondants et un Dockerfile pour exécuter l’entraînement dans un conteneur.

Nous utiliserons ce petit exemple pour simplifier les choses, mais dans un monorepo plus important, mnist ne serait qu’un des nombreux dossiers de projet à la racine du repo, chacun contenant au moins du code source, des tests, des fichiers dockerfiles et des fichiers de dépendances.
Système de construction : pourquoi en avez-vous besoin et comment le choisir ?
Pourquoi ?
Pensez à toutes les actions, autres que l’écriture du code, que les différentes équipes développant différents projets au sein du monorepo effectuent dans le cadre de leur flux de développement. Elles exécutent des linters pour s’assurer de respecter les normes de style, exécutent des tests unitaires, construisent des artefacts tels que des conteneurs Docker et des roues Python, les poussent vers des dépôts d’artefacts externes et les déploient en production.
Prenons les tests.
Vous avez apporté une modification à une fonction utilitaire que vous maintenez, vous avez exécuté les tests et tout est vert. Mais comment pouvez-vous être sûr que votre modification ne casse pas le code des autres équipes qui pourraient importer votre utilitaire ? Vous devez bien sûr exécuter leur suite de tests également.
Mais pour cela, vous devez savoir exactement où le code que vous avez modifié est utilisé. À mesure que la base de code se développe, il devient difficile de le découvrir manuellement. Bien sûr, en alternative, vous pouvez toujours exécuter tous les tests, mais encore une fois : cette approche ne s’échelonne pas très bien.

Un autre exemple, le déploiement en production.
Que vous déployiez une fois par semaine, quotidiennement ou en continu, lorsqu’il est temps, vous construiriez tous les services du monorepo et les pousseriez en production. Mais hé, est-ce que vous avez besoin de tous les construire à chaque occasion ? Cela pourrait être chronophage et coûteux à grande échelle.
Certains projets n’ont peut-être pas été mis à jour depuis des semaines. D’un autre côté, le code utilitaire partagé qu’ils utilisent a pu recevoir des mises à jour. Comment décidons-nous quoi construire ? Encore une fois, tout dépend des dépendances. Idéalement, nous ne construirions que les services qui ont été affectés par les modifications récentes.

Tout cela peut être géré avec un simple script shell et une petite base de code, mais à mesure que cela s’échelonne et que les projets commencent à partager du code, des défis apparaissent, dont beaucoup tournent autour de la gestion des dépendances.
Choisir le bon système
Tout ce qui précède n’est plus un problème si vous investissez dans un système de construction approprié. La tâche principale d’un système de construction est de construire du code. Et il devrait le faire de manière intelligente : le développeur devrait seulement avoir besoin de lui dire quoi construire (“construire les images Docker affectées par ma dernière validation”, ou “exécuter uniquement les tests qui couvrent le code qui utilise la méthode que j’ai mise à jour”), mais le comment doit être laissé à la charge du système.
Il existe plusieurs excellents systèmes de construction open-source. Étant donné que la plupart de l’apprentissage automatique est effectué en Python, concentrons-nous sur ceux qui offrent le meilleur support Python. Les deux choix les plus populaires à cet égard sont Bazel et Pants.
Bazel est une version open-source du système de construction interne de Google, Blaze. Pants est également fortement inspiré par Blaze et vise des objectifs de conception technique similaires à Bazel. Un lecteur intéressé trouvera une bonne comparaison entre Pants et Bazel dans cet article de blog (mais gardez à l’esprit qu’il provient des développeurs de Pants). Le tableau en bas de monorepo.tools offre une autre comparaison.
Les deux systèmes sont excellents, et je n’ai pas l’intention de déclarer une solution “meilleure” ici. Cela étant dit, Pants est souvent décrit comme plus facile à configurer, plus abordable et bien optimisé pour Python, ce qui en fait un choix parfait pour les monorepos d’apprentissage automatique.
Dans mon expérience personnelle, le facteur décisif qui m’a fait choisir Pants était sa communauté active et serviable. Chaque fois que vous avez des questions ou des doutes, il vous suffit de les poster sur le canal Slack de la communauté, et un groupe de personnes solidaires vous aidera bientôt.
Présentation de Pants
D’accord, il est temps d’entrer dans le vif du sujet ! Nous allons avancer étape par étape, en présentant les différentes fonctionnalités de Pants et comment les implémenter. Vous pouvez consulter le dépôt d’exemples associé ici.
Configuration
Pants peut être installé avec pip. Dans ce tutoriel, nous utiliserons la version stable la plus récente au moment de la rédaction, la version 2.15.1.
pip install pantsbuild.pants==2.15.1Pants est configurable via un fichier de configuration global appelé pants.toml. Nous pouvons y configurer le comportement de Pants ainsi que les paramètres des outils associés dont il dépend, tels que pytest ou mypy.
Commençons par un fichier pants.toml minimal :
[GLOBAL] pants_version = "2.15.1" backend_packages = [ "pants.backend.python", ] [source] root_patterns = ["/"] [python] interpreter_constraints = ["==3.9.*"]Dans la section globale, nous définissons la version de Pants et les packages backend dont nous avons besoin. Ces packages sont les moteurs de Pants qui prennent en charge différentes fonctionnalités. Pour commencer, nous incluons uniquement le backend Python.
Dans la section source, nous définissons la source comme étant la racine du référentiel. Depuis la version 2.15, pour nous assurer que cela est pris en compte, nous devons également ajouter un fichier BUILD_ROOT vide à la racine du référentiel.
Enfin, dans la section Python, nous choisissons la version de Python à utiliser. Pants parcourra notre système à la recherche d’une version correspondant aux conditions spécifiées ici, assurez-vous donc d’avoir cette version installée.
C’est un début ! Ensuite, penchons-nous sur le cœur de tout système de construction : les fichiers BUILD.
Fichiers BUILD
Les fichiers BUILD sont des fichiers de configuration utilisés pour définir les cibles (ce qu’il faut construire) et leurs dépendances (ce dont elles ont besoin pour fonctionner) de manière déclarative.
Vous pouvez avoir plusieurs fichiers BUILD à différents niveaux de l’arborescence des répertoires. Plus il y en a, plus le contrôle de la gestion des dépendances est granulaire. En fait, Google a un fichier BUILD dans pratiquement chaque répertoire de son référentiel.
Dans notre exemple, nous utiliserons trois fichiers BUILD :
- mnist/BUILD – dans le répertoire du projet, ce fichier BUILD définira les dépendances Python du projet et le conteneur Docker à construire ;
- mnist/src/BUILD – dans le répertoire du code source, ce fichier BUILD définira les sources Python, c’est-à-dire les fichiers couverts par des vérifications spécifiques à Python ;
- mnist/tests/BUILD – dans le répertoire des tests, ce fichier BUILD définira quels fichiers exécuter avec Pytest et quelles dépendances sont nécessaires pour exécuter ces tests.
Jetons un coup d’œil au fichier mnist/src/BUILD :
python_sources( name="python", resolve="mnist", sources=["**/*.py"], )En même temps, mnist/BUILD ressemble à ceci :
python_requirements( name="reqs", source="requirements.txt", resolve="mnist", )Les deux entrées dans les fichiers BUILD sont appelées cibles. Tout d’abord, nous avons une cible de sources Python, que nous appelons judicieusement python, bien que le nom puisse être n’importe quoi. Nous définissons nos sources Python comme tous les fichiers .py du répertoire. Cela est relatif à l’emplacement du fichier BUILD, c’est-à-dire que même si nous avions des fichiers Python en dehors du répertoire mnist/src, ces sources ne captureraient que le contenu du dossier mnist/src. Il y a également un champ de résolution ; nous en parlerons dans un instant.
Ensuite, nous avons la cible des exigences Python. Elle indique à Pants où trouver les exigences nécessaires à l’exécution de notre code Python (encore une fois, par rapport à l’emplacement du fichier BUILD, qui se trouve à la racine du projet mnist dans ce cas).
C’est tout ce dont nous avons besoin pour commencer. Pour nous assurer que la définition du fichier BUILD est correcte, exécutons :
pants tailor --check update-build-files --check ::Comme prévu, nous obtenons : “Aucune modification requise dans les fichiers BUILD trouvée.” en sortie. Parfait !
Passons un peu plus de temps sur cette commande. En résumé, un simple pants tailor peut créer automatiquement des fichiers BUILD. Cependant, il a parfois tendance à en ajouter trop pour les besoins de chacun, c’est pourquoi je préfère les ajouter manuellement, suivis de la commande ci-dessus qui vérifie leur exactitude.
Le double point-virgule à la fin est une notation de Pants qui lui indique d’exécuter la commande sur l’ensemble du monorépo. Alternativement, nous aurions pu le remplacer par mnist: pour l’exécuter uniquement sur le module mnist.
Dépendances et fichiers de verrouillage
Pour une gestion efficace des dépendances, Pants s’appuie sur des fichiers de verrouillage. Les fichiers de verrouillage enregistrent les versions spécifiques et les sources de toutes les dépendances utilisées par chaque projet. Cela comprend à la fois les dépendances directes et les dépendances transitives.
En capturant ces informations, les fichiers de verrouillage garantissent que les mêmes versions de dépendances sont utilisées de manière cohérente dans différents environnements et lors des builds. En d’autres termes, ils servent de capture instantanée du graphe de dépendances, garantissant la reproductibilité et la cohérence des builds.
Pour générer un fichier de verrouillage pour notre module mnist, nous avons besoin de l’ajout suivant à pants.toml:
[python] interpreter_constraints = ["==3.9.*"] enable_resolves = true default_resolve = "mnist" [python.resolves] mnist = "mnist/mnist.lock"Nous activons les résolutions (terme de Pants pour les environnements des fichiers de verrouillage) et en définissons un pour mnist en passant un chemin de fichier. Nous le choisissons également comme résolution par défaut. C’est la résolution que nous avons passée aux sources Python et à la cible des exigences Python auparavant: c’est ainsi qu’ils savent quelles dépendances sont nécessaires. Nous pouvons maintenant exécuter:
pants generate-lockfilespour obtenir:
Terminé : Générer le fichier de verrouillage pour mnist Fichier de verrouillage écrit pour la résolution `mnist` à mnist/mnist.lockCela a créé un fichier à mnist/mnist.lock. Ce fichier doit être vérifié avec git si vous avez l’intention d’utiliser Pants pour votre CI/CD distant. Et naturellement, il doit être mis à jour chaque fois que vous mettez à jour le fichier requirements.txt.
Avec plus de projets dans le monorépo, vous préféreriez générer les fichiers de verrouillage sélectivement pour le projet qui en a besoin, par exemple pants generate-lockfiles mnist: .
C’est tout pour la configuration ! Maintenant, utilisons Pants pour faire quelque chose d’utile pour nous.
Unifier le style de code avec Pants
Pants prend en charge nativement plusieurs linters Python et outils de formatage de code tels que Black, yapf, Docformatter, Autoflake, Flake8, isort, Pyupgrade ou Bandit. Ils sont tous utilisés de la même manière ; dans notre exemple, implémentons Black et Docformatter.
Pour cela, nous ajoutons deux backends appropriés à pants.toml:
[GLOBAL] pants_version = "2.15.1" colors = true backend_packages = [ "pants.backend.python", "pants.backend.python.lint.docformatter", "pants.backend.python.lint.black", ]Nous pourrions configurer les deux outils si nous le voulions en ajoutant des sections supplémentaires ci-dessous dans le fichier toml, mais restons-en aux valeurs par défaut pour le moment.
Pour utiliser les formateurs, nous devons exécuter ce qu’on appelle un objectif Pants. Dans ce cas, deux objectifs sont pertinents.
Tout d’abord, l’objectif lint exécutera les deux outils (dans l’ordre dans lequel ils sont répertoriés dans les packages backend, donc Docformatter en premier, Black en second) en mode de vérification.
pants lint :: Terminé : Formatter avec docformatter - docformatter n'a apporté aucun changement. Terminé : Formatter avec Black - black n'a apporté aucun changement. ✓ black réussi. ✓ docformatter réussi.Il semble que notre code respecte les normes des deux formateurs ! Cependant, si ce n’était pas le cas, nous pourrions exécuter l’objectif fmt (abrégé de “format”) qui adapterait le code de manière appropriée:
pants fmt ::En pratique, vous voudriez peut-être utiliser plus de ces deux formateurs. Dans ce cas, vous devriez mettre à jour la configuration de chaque formateur pour vous assurer qu’il est compatible avec les autres. Par exemple, si vous utilisez Black avec sa configuration par défaut comme nous l’avons fait ici, il s’attendra à ce que les lignes de code ne dépassent pas 88 caractères.
Mais si vous souhaitez ensuite ajouter isort pour trier automatiquement vos importations, ils entreront en conflit : isort tronque les lignes après 79 caractères. Pour rendre isort compatible avec Black, vous devriez inclure la section suivante dans le fichier toml:
[isort] args = [ "-l=88", ]Tous les formateurs peuvent être configurés de la même manière dans pants.toml en passant les arguments à leur outil sous-jacent.
Test avec Pants
Lançons quelques tests! Pour cela, nous avons besoin de deux étapes.
Tout d’abord, nous ajoutons les sections appropriées à pants.toml:
[test] output = "all" report = false use_coverage = true [coverage-py] global_report = true [pytest] args = ["-vv", "-s", "-W ignore::DeprecationWarning", "--no-header"]Ces paramètres garantissent qu’à mesure que les tests sont exécutés, un rapport de couverture des tests est produit. Nous passons également quelques options personnalisées à pytest pour adapter sa sortie.
Ensuite, nous devons revenir à notre fichier mnist/tests/BUILD et ajouter une cible de tests Python:
python_tests( name="tests", resolve="mnist", sources=["test_*.py"], )Nous l’appelons tests et spécifions la résolution (c’est-à-dire le verrouillage) à utiliser. Les sources sont les emplacements où pytest sera autorisé à rechercher les tests à exécuter; ici, nous passons explicitement tous les fichiers .py préfixés par “test_”.
Maintenant, nous pouvons exécuter:
pants test :: pour obtenir: ✓ mnist/tests/test_data.py:../tests réussi en 3,83s. ✓ mnist/tests/test_model.py:../tests réussi en 2,26s. Nom Instrs Manquer Couverture ------------------------------------------------------ __global_coverage__/no-op-exe.py 0 0 100% mnist/src/data.py 14 0 100% mnist/src/model.py 15 0 100% mnist/tests/test_data.py 21 1 95% mnist/tests/test_model.py 20 1 95% ------------------------------------------------------ TOTAL 70 2 97%Comme vous pouvez le voir, il a fallu environ trois secondes pour exécuter cette suite de tests. Maintenant, si nous les exécutons à nouveau, nous obtiendrons les résultats immédiatement:
✓ mnist/tests/test_data.py:../tests réussi en 3,83s (mémorisé). ✓ mnist/tests/test_model.py:../tests réussi en 2,26s (mémorisé).Remarquez comment Pants nous indique que ces résultats sont mémorisés, ou mis en cache. Étant donné qu’aucune modification n’a été apportée aux tests, au code testé ou aux exigences, il n’est pas nécessaire de les exécuter à nouveau – leurs résultats sont garantis identiques, donc ils sont simplement servis à partir du cache.
Vérification de la typage statique avec Pants
Ajoutons encore une vérification de la qualité du code. Pants permet d’utiliser mypy pour vérifier le typage statique en Python. Tout ce que nous avons à faire est d’ajouter le backend mypy dans pants.toml: “pants.backend.python.typecheck.mypy”.
Vous voudrez peut-être également configurer mypy pour rendre sa sortie plus lisible et informative en ajoutant également la section de configuration suivante:
[mypy] args = [ "--ignore-missing-imports", "--local-partial-types", "--pretty", "--color-output", "--error-summary", "--show-error-codes", "--show-error-context", ] Avec cela, nous pouvons exécuter pants check :: pour obtenir: Terminé: Vérification des types avec MyPy - mypy - mypy réussi. Succès: aucun problème trouvé dans 6 fichiers source. ✓ mypy réussi.Expédition de modèles d’apprentissage automatique avec Pants
Parlons d’expédition. La plupart des projets d’apprentissage automatique impliquent un ou plusieurs conteneurs Docker, par exemple pour le traitement des données d’entraînement, l’entraînement d’un modèle ou son utilisation via une API à l’aide de Flask ou FastAPI. Dans notre projet fictif, nous avons également un conteneur pour l’entraînement du modèle.
Pants prend en charge la construction et l’envoi automatiques d’images Docker. Voyons comment cela fonctionne.
Tout d’abord, nous ajoutons le backend Docker dans pants.toml: pants.backend.docker. Nous allons également configurer notre docker, en lui passant un certain nombre de variables d’environnement et un argument de construction qui nous sera utile dans un instant:
[docker] build_args = ["SHORT_SHA"] env_vars = ["DOCKER_CONFIG=%(env.HOME)s/.docker", "HOME", "USER", "PATH"] Maintenant, dans le fichier mnist/BUILD, nous ajouterons deux autres cibles: une cible de fichiers et une cible d'image Docker. files( name="module_files", sources=["**/*"], ) docker_image( name="train_mnist", dependencies=["mnist:module_files"], registries=["docker.io"], repository="michaloleszak/mnist", image_tags=["latest", "{build_args.SHORT_SHA}"], )Nous appelons la cible Docker “train_mnist”. Comme dépendance, nous devons lui transmettre la liste des fichiers à inclure dans le conteneur. La manière la plus pratique de le faire est de définir cette liste comme une cible de fichiers séparés. Ici, nous incluons simplement tous les fichiers du projet mnist dans une cible appelée “module_files” et la transmettons comme dépendance à la cible de l’image Docker.
Naturellement, si vous savez que seul un sous-ensemble de fichiers sera nécessaire par le conteneur, il est conseillé de ne passer que ceux-ci en tant que dépendance. C’est essentiel car ces dépendances sont utilisées par Pants pour déterminer si un conteneur a été affecté par un changement et nécessite une reconstruction. Ici, avec “module_files” incluant tous les fichiers, si un fichier dans le dossier mnist change (même un fichier readme !), Pants considérera que l’image Docker “train_mnist” a été affectée par ce changement.
Enfin, nous pouvons également définir le registre externe et le dépôt vers lesquels l’image peut être poussée, ainsi que les étiquettes avec lesquelles elle sera poussée : ici, je pousserai l’image vers mon dépôt personnel DockerHub, toujours avec deux étiquettes : “latest” et le SHA court du commit qui sera transmis en tant qu’argument de construction.
Avec cela, nous pouvons construire une image. Juste une dernière chose : étant donné que Pants travaille dans ses environnements isolés, il ne peut pas lire les variables d’environnement de l’hôte. Par conséquent, pour construire ou pousser l’image qui nécessite la variable “SHORT_SHA”, nous devons la transmettre avec la commande Pants.
Nous pouvons construire l’image de cette manière :
SHORT_SHA=$(git rev-parse --short HEAD) pants package mnist:train_mnistpour obtenir :
Terminé : Construction de l'image Docker docker.io/michaloleszak/mnist:latest +1 étiquette supplémentaire. Images Docker construites : * docker.io/michaloleszak/mnist:latest * docker.io/michaloleszak/mnist:0185754Un rapide contrôle révèle que les images ont effectivement été construites :
docker images REPOSITORY TAG IMAGE ID CREATED SIZE michaloleszak/mnist 0185754 d86dca9fb037 Il y a environ une minute 3.71GB michaloleszak/mnist latest d86dca9fb037 Il y a environ une minute 3.71GBNous pouvons également construire et pousser des images en une seule fois en utilisant Pants. Il suffit de remplacer la commande “package” par la commande “publish”.
SHORT_SHA=$(git rev-parse --short HEAD) pants publish mnist:train_mnistCela a construit les images et les a poussées vers mon DockerHub, où elles ont effectivement été placées.
Pants dans CI/CD
Les mêmes commandes que nous avons exécutées manuellement en local peuvent être exécutées dans le cadre d’un pipeline CI/CD. Vous pouvez les exécuter via des services tels que GitHub Actions ou Google CloudBuild, par exemple en tant que vérification PR avant qu’une branche de fonctionnalité ne soit autorisée à être fusionnée dans la branche principale, ou après la fusion, pour vérifier qu’elle est verte et construire et pousser des conteneurs.
Dans notre dépôt bidon, j’ai implémenté un hook de pré-validation de commit qui exécute des commandes Pants lors d’un push git et ne le laisse passer que s’ils réussissent tous. Dans celui-ci, nous exécutons les commandes suivantes :
pants tailor --check update-build-files --check :: pants lint :: pants --changed-since=main --changed-dependees=transitive check pants test ::Vous pouvez voir de nouveaux drapeaux pour la commande “pants check”, qui est la vérification de typage avec mypy. Ils s’assurent que la vérification n’est exécutée que sur les fichiers qui ont changé par rapport à la branche principale et leurs dépendances transitives. C’est utile car mypy a tendance à prendre du temps pour s’exécuter. Limiter sa portée à ce qui est réellement nécessaire accélère le processus.
À quoi ressemblerait une construction et un déploiement Docker dans un pipeline CI/CD ? Quelque chose comme cela :
pants --changed-since=HEAD^ --changed-dependees=transitive --filter-target-type=docker_image publishNous utilisons la commande “publish” comme précédemment, mais avec trois arguments supplémentaires :
- –changed-since=HEAD^ et –changed-dependees=transitive s’assurent que seuls les conteneurs affectés par les changements par rapport au commit précédent sont construits ; cela est utile pour l’exécution sur la branche principale après la fusion.
- –filter-target-type=docker_image s’assure que Pants ne fait que construire et pousser des conteneurs Docker ; cela est dû au fait que la commande “pants publish” peut faire référence à des cibles autres que Docker : par exemple, elle peut être utilisée pour publier des charts Helm dans des registres OCI.
Il en va de même pour le package pants : en plus de construire des images docker, il peut également créer un package Python ; pour cette raison, il est recommandé de passer l’option –filter-target-type.
Conclusion
Les monorepos sont plus souvent qu’autrement un excellent choix d’architecture pour les équipes d’apprentissage automatique. Cependant, les gérer à grande échelle nécessite un investissement dans un système de construction approprié. L’un de ces systèmes est Pants : il est facile à configurer et à utiliser, et offre une prise en charge native de nombreuses fonctionnalités Python et Docker souvent utilisées par les équipes d’apprentissage automatique.
De plus, il s’agit d’un projet open-source avec une communauté importante et serviable. J’espère qu’après avoir lu cet article, vous allez l’essayer. Même si vous n’avez pas actuellement de référentiel monolithique, Pants peut néanmoins rationaliser et faciliter de nombreux aspects de votre travail quotidien !
Références
- Documentation de Pants : https://www.pantsbuild.org/
- Article de blog Pants vs. Bazel : https://blog.pantsbuild.org/pants-vs-bazel/
- monorepo.tools : https://monorepo.tools/
We will continue to update IPGirl; if you have any questions or suggestions, please contact us!
Was this article helpful?
93 out of 132 found this helpful
Related articles
- Auto-attention dans les Transformers
- JupyterAI IA générative + JupyterLab
- Que pouvez-vous faire lorsque l’IA ment à votre sujet?
- La personnalisation d’images IA révolutionnaire de Nvidia la méthode de perfusion
- Adaptez votre LLM sur une seule GPU avec le Gradient Checkpointing, LoRA et la Quantification.
- Mettez-moi rapidement au centre Subject-Diffusion est un modèle d’IA qui peut réaliser une génération de texte vers image personnalisée dans un domaine ouvert.
- Déployez MusicGen en un rien de temps avec les points de terminaison d’inférence