LLMs pour code: the Good, the Bad and the Ugly

Examinons maintenant la question du point de vue du développeur : quel est l’état d’avancement de la génération de code et que devons-nous encore prendre en compte ? Pour faire court : les assistants IA ou les plugins pour IDE sont une aubaine pour ceux qui savent en faire bon usage, mais comme tous les systèmes d’IA, ils présentent aussi des inconvénients.

Avant-propos

Une partie de cette hype en termes d’IA générative est propulsée par des modèles de langages puissants – les grands modèles de langue ou LLM. Depuis la sortie du GPT-3 en 2020, ces modèles parviennent à écrire des textes normaux d’une certaine longueur. De là, il n’y a qu’un pas vers les langages de programmation. En effet, ils ont aussi une syntaxe et une sémantique.

Dans la pratique, il existe de nombreuses variantes de modèles de langue, chacun ayant ses forces et ses faiblesses, en fonction des choix faits par les créateurs pour les entraîner, et en fonction des données d’entraînement qui les sous-tendent. Testez vous-même certains des modèles open source existants sur votre propre ordinateur via l’outil GPT4All (voir également notre quick review de cet outil).

Le code informatique consiste en une collection de fichiers texte. Rien n’empêche un modèle de langue d’essayer de prédire les différents tokens (= unités grammaticales) qui composent le code, plutôt que des mots. Cependant, contrairement au texte brut, le code a beaucoup moins de tolérance à l’erreur : la moindre faute d’orthographe ou la plus petite variation peut invalider un morceau de code ou lui faire exécuter quelque chose de complètement différent.

Pourtant, aujourd’hui, les plus grands modèles de langue, tels que GPT-3.5 et les versions ultérieures, peuvent produire d’eux-mêmes des morceaux de code informatique tout à fait corrects en réponse à une requête. Cette fonctionnalité est due à la quantité massive de textes sur lesquels ils sont formés, notamment de nombreux tutoriels, articles de blog, questions et réponses provenant de forums de développeurs populaires tels que StackOverflow, et code documenté provenant de repositories de code publics tels que Github.

Canards en plastique bavards

Depuis Socrate, le dialogue est un moyen efficace de parvenir à de nouvelles perspectives. Ce n’est pas pour rien que le rubber ducking est une méthode de correction de bugs qui revient dans tous les cours de génie logiciel. Il existe entre-temps plusieurs plugins qui mettent à disposition une interface de chat alimentée par l’IA dans l’IDE même (par exemple ceux pour VS Code, beaucoup d’autres peuvent être trouvés via les marketplaces pour VS Code ou IntelliJ IDEA). Si ces plugins utilisent un service cloud externe, il vous suffit d’entrer votre propre clé API.

Un cadre de dialogue avec une dynamique de questions-réponses peut être bien utilisé pour générer des morceaux de code relativement autonomes, sans trop de dépendances externes. En général, pour obtenir le meilleur résultat, il faut pouvoir énoncer facilement toutes les conditions préalables et les hypothèses nécessaires dans le dialogue lui-même, de manière à ce qu’il s’inscrive dans la fenêtre contextuelle du modèle de langue. Les use cases comprennent entre autres :

  • La génération from scratch d’une version initiale du code ou d’un fichier de configuration
  • La génération de fonctions ou de procédures relativement courtes à partir d’une description
  • La génération de code snippets autonomes : requêtes SQL, expressions régulières…
  • Demande de modification d’un morceau de code ou d’un fichier de configuration
  • Correction de bugs : recherche d’erreurs dans un code qui ne fonctionne pas, poser des questions sur une erreur
  • Faire expliquer ce qu’un morceau de code fait

Les plus grands modèles de langue disposent de fenêtres contextuelles de plusieurs milliers de mots dans lesquelles il est possible d’insérer toutes les informations nécessaires. Un modèle de langue open source plus petit, installé localement sur du matériel moins puissant, sera sans aucun doute moins performant. Voici quelques exemples de conversations avec GPT-4 d’OpenAI, qui montrent qu’il est possible d’aller très loin avec quelques questions bien ciblées (cliquez sur l’image pour obtenir la pleine résolution) :

Complétion de code sous stéroïdes

Au cours du développement, un développeur travaille sur de nombreux fichiers dans un IDE. À des endroits aléatoires de ces fichiers, le code doit être modifié, supprimé ou écrit. L’édition de code existant de cette manière n’a pas grand-chose à voir avec le dialogue ; en fait, nous préférerions utiliser l’auto-complétion avancée dans ce cas. Les modèles de langue peuvent également faire l’affaire, mais les modèles les plus appropriés sont plutôt ceux formés aux tâches de « remplir au milieu » – et qui peuvent donc prendre en compte le code présent avant et après l’endroit que l’on édite.

Après la sortie de GPT-3, OpenAI a travaillé avec Microsoft (qui possède Github) pour créer un modèle de langue spécialisé, formé exactement pour ce use case. Cette variante a été nommée Codex, et le premier outil à l’utiliser a été Codex. Depuis, nous en sommes à plusieurs versions, mais les plugins pour VSCode et IntelliJ fonctionnent toujours de la même manière : via un raccourci clavier dans l’éditeur, on peut utiliser CoPilot pour récupérer diverses suggestions, générées par Codex, qui pourraient correspondre à l’endroit où se trouve le curseur.

D’après notre expérience actuelle, le contexte pris en compte est généralement limité au contenu (partiel) du fichier édité. Cela implique évidemment le téléchargement vers le modèle de langue – veillez donc à respecter les directives en matière de confidentialité lorsque vous utilisez un service externe. Pour l’instant, nous semblons obtenir de meilleurs résultats dans les projets de programmation composés de quelques gros fichiers, tels que les pages web avec JavaScript en ligne, ou Jupyter Notebooks en Python, où il y a souvent un gros fichier à parcourir qui contient à la fois la documentation, le code et l’output. En revanche, dans les projets comportant de nombreux petits fichiers, il semble plus difficile de générer de bonnes suggestions, et il est plus important de disposer d’une documentation supplémentaire dans le fichier édité afin que le modèle de langue puisse puiser dans suffisamment d’informations contextuelles.

Github CoPilot dans VSCode. Suivant un schéma déjà présent dans le même fichier, un objet Rounding() doit être créé pour chaque élément d’un dictionnaire Python. L’itération fonctionne bien, mais CoPilot n’a manifestement aucune connaissance du function header, qui n’est définie ni dans ce même fichier ni dans la « connaissance générale » du modèle Codex de CoPilot : les suggestions proposent des paramètres qui n’existent pas. Immédiatement après avoir accepté cette solution erronée, le vérificateur de code statique intégré se plaint du paramètre manquant

L’une des alternatives les plus intéressantes au modèle commercial Github CoPilot est StarCoder, un modèle open source issu de l’initiative BigCode HuggingFace et ServiceNow. Bien que la performance soit moindre que CoPilot, ils font la différence dans de nombreux autres domaines qui peuvent être des obstacles dans des contextes commerciaux ou publics :

Plusieurs autres systèmes ont vu le jour cet été et ont obtenu de bons résultats dans de nombreux benchmarks : WizardLM et sa variante spécifique WizardCoder, qui est désormais considéré comme le nec plus ultra de l’open source, et PanGu-Coder, avec lequel Huawei s’est également lancé dans le monde des assistants IA pour le code. 

Au cœur de l’action

Le StarCoder paper offre un bel aperçu du fonctionnement d’un modèle de langue pour le code.
Ce n’est certainement pas comme si vous pouviez « brancher » votre propre codebase pour obtenir des suggestions adaptées. Si vous voulez vraiment affiner le modèle (et vous ne ferez cet énorme effort que si vous n’y arrivez pas avec des modifications astucieuses du prompt), il y a beaucoup de choses à faire, du prétraitement des données d’entraînement au post-traitement de l’output brut du modèle de langue. Ne vous attendez pas non plus à ce que le réglage fin soit trop élevé : StarCoder l’a fait pour Python, mais n’a obtenu que quelques points de pourcentage d’amélioration par rapport au modèle global qui pourrait traiter tous les langages de programmation. Le peaufinage est difficile et il n’y a aucune garantie de succès ; il y a même un risque d’overfitting, ce qui pourrait dégrader les résultats.

L’étape la plus importante est probablement la collecte et le nettoyage des données. Ces données sont constituées de code, mais tous les codes ne sont pas inclus : vous devez également être autorisé à utiliser le code (licences) et, de préférence, l’avoir aussi correct que possible et écrit dans le langage de programmation que vous souhaitez soutenir. Le code est également collecté à partir des issue trackers et du commit history. En outre, un filtrage additionnel peut être effectué pour supprimer les (quasi-)doublons, et des pondérations peuvent être attribuées ici et là pour maintenir l’équilibre : un peu moins de poids pour le code « boilerplate », et/ou un peu plus pour les repositories très populaires qui sont susceptibles d’être de meilleure qualité. Le code source peut contenir des informations sensibles qui doivent être rendues anonymes ou supprimées au préalable, pour éviter qu’elles ne soient divulguées ou suggérées (adresses IP, mots de passe, identifiants, adresses électroniques, coordonnées…). Tout cela, bien sûr, de préférence aussi automatiquement que possible.

Le code source se compose non seulement de code, mais aussi de descriptions, de commentaires et d’autres informations. Dans une étape de formatage, le code est donc enrichi par l’ajout de métadonnées et de tokens supplémentaires qui rendent explicites certaines structures implicites. Cela peut avoir des conséquences : si tout ce prétraitement a été effectué sur l’ensemble des données d’apprentissage, le modèle résultant ne fonctionnera correctement sur de nouvelles données que s’il a subi le même prétraitement. Ainsi, les plugins éditeur qui souhaitent utiliser un tel modèle peuvent, pour obtenir un bon résultat, devoir d’abord effectuer un prétraitement similaire sur le code qu’ils souhaitent envoyer au modèle de langue.

Pour que le modèle puisse mieux distinguer les différentes parties du code source, les données d’entraînement sont enrichies de métadonnées et de ce que l’on appelle des « tokens sentinelles ». « sentinel tokens », comme cette liste tirée du StarCoder paper.

Exactitude et autres benchmarks

Comme c’est le cas pour les LLM, il ne peut y avoir de garantie concluante de l’exactitude ou de l’exhaustivité de ce qu’un tel plugin présente, tant sur le plan syntaxique que sur le plan sémantique. Cette précision est évidemment importante : un morceau de code généré ne doit pas seulement être syntaxiquement correct et compiler sans faille, mais aussi être sémantiquement significatif et s’exécuter correctement. La métrique « pass@x » est devenue une mesure importante à cet égard. Elle exprime en pourcentage si un modèle de langue pour une mission donnée peut passer avec succès les tests correspondants après X tentatives. « pass@1 » est le pourcentage pour lequel le modèle de langue utilisé pour la première fois a pu générer la bonne réponse, « pass@10 » est le pourcentage pour lequel au moins 1 tentative sur 10 a été correcte.

Dans le monde de l’IA générative, il existe un besoin général de pouvoir comparer les nouveaux modèles, qui apparaissent désormais presque quotidiennement, avec le meilleur de la technologie. Il n’y a donc pas de pénurie de benchmarks, et de nouveaux modèles plus importants apparaissent régulièrement. Des résumés utiles sont les « leaderboards », qui montrent en temps réel quels modèles représentent l’état actuel de la technique selon une série de benchmarks. L’étape peut changer chaque semaine. Voici quelques leaderboards généraux intéressants :

En ce qui concerne le code, il existe des benchmarks qui fonctionnent plus ou moins comme un concours de programmation. L’idée est de confier un ensemble de tâches au modèle de langue, d’évaluer les résultats automatiquement et de mesurer le « pass@1 » et, si possible, d’autres paramètres. Souvent, il s’agit de « remplir la fonction » : à partir d’une description de l’input, de l’output et d’un function header, le contenu de la fonction doit être généré. L’inconvénient est que ce type de problème n’est parfois pas très représentatif de celui auquel est confronté le développeur lambda. Parmi les initiatives intéressantes, on peut citer :

Bien entendu, le fait qu’un morceau de code généré survive aux tests ne signifie pas qu’il s’agit d’un code sécurisé ou qu’il respecte les « best practices ». Entre-temps, il existe de nombreux exemples connus de code généré qui s’avère sensible aux « buffer overflows », à l’injection SQL et à d’autres risques classiques. Le benchmark de sécurité « Asleep at the Keyboard » consiste en 89 scénarios de génération de code basés sur la liste MITRE top-25 vulnerabilityStarcoder paper montre que même les meilleurs modèles génèrent encore du code non sécurisé dans 40 % de ces scénarios. En outre, il ne semble guère y avoir de différence entre les meilleurs modèles et les autres – le choix d’un meilleur modèle semble garantir des résultats syntaxiquement plus corrects, mais pas encore un code plus sûr. Il est donc possible que nous devions nous pencher sur les données d’apprentissage elles-mêmes, où le code non sécurisé devrait être encore mieux filtré. Quoi qu’il en soit, il convient de rappeler que l’utilisation de code généré dans un projet doit impérativement s’accompagner d’une solide politique de test et d’acceptation.

Performance

En ce qui concerne plus particulièrement les exigences computationnelles, le leaderboard Huggingface OpenLLM-perf et les benchmarks sur le site web TextSynth Server constituent des sources intéressantes. Ce dernier montre quelques chiffres de performance utiles pour ceux qui envisagent un hébergement par leurs propres moyens. Ceux qui n’ont pas de GPU peuvent compter sur une vitesse de 12 tokens par seconde avec le modèle LLaMa2 de 13 milliards de paramètres, avec un processeur de serveur EPYC 7313 relativement haut de gamme. Dans un code informatique, un token ne représente parfois qu’un seul caractère, de sorte qu’à cette vitesse, il faut parfois attendre une dizaine de secondes pour obtenir une suggestion de complétion de code. La dernière carte graphique RTX-4090 peut le faire 7 fois plus vite, mais pas encore au point de l’exprimer en millisecondes.

Les besoins en mémoire sont proportionnels au nombre de paramètres d’un modèle, et la vitesse de génération inversement proportionnelle. À titre d’approximation, on peut supposer qu’un modèle comportant 13 milliards de paramètres doit également effectuer 13 milliards de calculs pour chaque token de sortie, même s’il ne comporte qu’un seul caractère. En outre, si chaque paramètre est un nombre de 32 bits, il faut au moins 52 Go de stockage et autant de mémoire (V)RAM. Une « quantization« , arrondissant les paramètres à 8 bits ou même à 4 bits, peut réduire proportionnellement ce besoin en mémoire.

GPT4All permet de l’essayer sur votre propre matériel. Cela donne une idée de l’énorme puissance de calcul qu’OpenAI, Microsoft Azure ou Amazon AWS déploient pour que leurs modèles, dont beaucoup sont encore plus grands que les LLM disponibles en libre accès, fonctionnent aussi vite qu’ils le proposent. On parle d’investissements de milliards de dollars en matériel informatique, si importants qu’ils déstabiliseraient le marché mondial.

Même les solutions open source sont loin d’être légères, en dépit des grandes initiatives d’optimisation. On peut en tout cas supposer que le déploiement local n’est possible que sur du matériel récent et puissant. Actuellement, on ne peut pas s’attendre à ce qu’une installation locale sur un ordinateur portable de bureau moyen offre une expérience fluide à l’utilisateur.

Productivité

Internet regorge de contes de fées sur le développeur 10x, et les gourous de l’IA générative aimeraient vous faire croire que cette technologie peut élever n’importe quel programmeur à ce niveau. La réalité est plus nuancée. Les développeurs ne passent pas 100 % de leur temps à écrire du code, pas plus que les médecins ne passent 100 % de leur temps à rédiger des ordonnances. La majorité des développeurs passe moins d’une heure par jour à coder. Le reste de leur temps est consacré à l’analyse, à la lecture, à l’apprentissage, aux tâches de maintenance, à la communication, etc. Jusqu’à présent, cette réflexion et cette consultation avec les collègues ne sont pas comprimées par l’emploi de LLM.

Il est difficile de trouver des chiffres précis sur la productivité parce qu’elle est difficile à définir et donc à mesurer. Une première estimation utile provient de Google même, qui a examiné le temps d’itération (de la connaissance du problème à la solution). Avec une première version de son propre assistant de complétion de code par l’IA, l’entreprise a pu constater un gain de temps de 6 %. Github affirme que le codage pur peut être environ 55 % plus rapide avec son CoPilot – bien qu’il précise dans le même temps que l’intervalle de confiance à 95 % de sa mesure est de [21 %-89 %]. En outre, l’adoption d’un outil n’apporte aucune valeur ajoutée si elle n’est pas accompagnée d’un parcours pour apprendre à l’utiliser de manière optimale (tout comme aujourd’hui encore, de nombreux employés de bureau perdent du temps avec Office en raison d’une connaissance ou d’une expérience insuffisante de tous les types de références, de formules et de raccourcis).

Le code généré fournit une solution initiale rapide, mais cette solution doit encore être comprise par le programmeur. Un score « pass@1 » de 50 % signifie que la moitié des bouts de code générés nécessitent encore des ajustements manuels avant de passer les tests unitaires – sans parler de l’optimalité ou de la sécurité. Le code généré peut être complexe et utiliser des constructions qui dépassent le niveau de connaissance du programmeur. Le code généré est donc plus difficile à maintenir et à corriger que le code écrit manuellement. Un code généré qui n’a pas été correctement examiné et testé ajoute une dette technique considérable à un projet.

L’utilisation de plugins qui vont jusqu’à générer des blocs entiers de code et de documentation en un claquement de doigts (ou un peu plus lentement) n’est une bonne idée que si plusieurs autres aspects du processus d’ingénierie logicielle sont en ordre : des normes élevées doivent être maintenues dans tous les domaines en termes de stratégie de test, de code reviews, de documentation de code et de savoir-faire des développeurs.

Confidentialité

Les entreprises et les gouvernements ont rarement le luxe d’utiliser n’importe quel modèle de langue. Il existe non seulement des barrières contractuelles, mais aussi des questions de confidentialité, en particulier lors de l’utilisation du cloud. Après tout, on n’obtient une bonne suggestion de modèle de langue qu’en introduisant suffisamment d’informations au préalable. Ne pas tout mettre en place en interne implique inévitablement de donner à un tiers l’accès à vos données.

Le degré d’ouverture et de licence peut varier considérablement – à un extrême, tout est en « boîte noire » et uniquement accessible via le cloud/API (c’est là que vous trouverez OpenAI, Anthropic, Cohere et la plupart des autres start-ups établies). Celles-ci promettent dans les versions Enterprise parfois plus de garanties – mais vous n’avez pas d’autre choix que de les croire sur parole. À l’autre extrême, tout est en « open access » (libre accès) et sous licence permissive. Entre les deux, une entreprise peut également construire un modèle de langue en libre accès sur un dataset fermé. Au moins un de ces datasets a depuis été divulgué comme contenant des ebooks illégalement copiés et protégés par le droit d’auteur, ce qui constituera sans aucun doute un argument de poids dans le recours collectif intenté contre Meta sur ce sujet. Les ensembles de données des Code LLM Salesforce CodeGen et Tsinghua CodeGeeX ne sont pas non plus publics.

Transparence, licences, options de déploiement, prix, taille et scalabilité… l’importance relative de toutes ces caractéristiques dictera les outils que vous pourrez utiliser. Ceux qui souhaitent une transparence maximale seront souvent limités aux LLM en Open Access. Certaines licences ouvertes limitent en outre l’utilisation à des fins non commerciales. La nécessité d’accéder à des données de formation ou la facilité d’héberger soi-même une instance sur site limitent davantage les choix.

Conclusion

Les outils basés sur le dialogue (chatGPT et autres) peuvent vous être utiles en tant que développeur pour, entre autres, les tâches suivantes :

  • Initialiser un projet/fichier/classe/configuration : créer une première version de quelque chose
  • Correction de bugs et modification sous forme de questions-réponses
  • Morceaux de code relativement indépendants

Les outils qui complètent le code ou remplissent le code manquant (type Github Co-Pilot) sont utiles, entre autres, pour :

  • Compléter du code à partir d’exemples déjà réalisés
  • Documenter le code
  • Apporter des modifications au milieu d’un fichier plus volumineux

Pour un développeur, l’environnement de développement optimal est quelque chose de tout à fait personnel et chacun aura sa propre préférence. À notre avis, ces deux façons d’obtenir des suggestions de code sont quelque peu complémentaires, et une combinaison intelligente des deux peut permettre d’obtenir les meilleurs gains de productivité. Dans le même temps, nous tenons à dire qu’une gestion de projet saine, avec une attention portée à la qualité du code, aux tests, aux révisions, à la documentation, etc. est indispensable.

Le monde de l’IA est en pleine effervescence. De nouveaux modèles d’IA pouvant servir de base aux plugins IDE sont ajoutés avec une grande régularité. Pour les industries où la confidentialité du code est importante, les variantes open source sont très intéressantes. Même si les benchmarks montrent qu’ils sont encore moins performants aujourd’hui que les dernières initiatives commerciales basées sur le cloud, nous pouvons nous attendre à ce que de meilleures versions apparaissent à l’avenir. De nombreux efforts sont déjà déployés pour créer des modèles pouvant fonctionner sur du matériel grand public (certes haut de gamme).

P.S.

Quelques heures après la publication de cet article, HuggingFace annonce la venue de SafeCoder : une solution d’entreprise pour les assistants de codage basés sur LLM qui peut être déployée sur site. Huggingface fournit le tout dans des conteneurs qui peuvent être installés dans un data center propre et fournir des endpoints privés, ainsi que des plugins compatibles avec les principaux IDE. D’autres frameworks de déploiement général existent depuis un certain temps, notamment SeldonBentoML et KServe, qui peuvent également héberger des LLM, TextSynth Server et GPT4All peuvent fonctionner comme des endpoints d’API. Cependant, vous avez toujours besoin de plugins pour les utiliser dans l’IDE lui-même, et pour effectuer les traitements préalables et postérieurs nécessaires – et s’ils ne sont pas fournis, vous devez en créer un vous-même ou modifier un plugin existant.

P.P.S.

Ces derniers mots à peine écrits, Meta a lancé CodeLLama, une variante de LLaMa 2 spécifiquement entraînée pour le code. Les médias sociaux suggèrent qu’il est possible de faire tourner la version originale avec 34 milliards de paramètres sur un ordinateur équipé de 4 GPU RTX3090 avec 24 GB de VRAM chacun, générant environ 20 tokens par seconde. Il est sans doute plus facile d’essayer la version de chat en ligneLes versions quantisées suivront sans doute très prochainement, et nous attendons les premiers benchmarks sur les différents leaderboards.


Cette contribution a été soumise par Joachim Ganseman, consultant IT chez Smals Research. Elle a été rédigée en son nom propre et ne prend pas position au nom de Smals.

newsletter

Abonnez-vous gratuitement à ITdaily !

Category(Required)
This field is for validation purposes and should be left unchanged.