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

Cet article est aussi disponible en français.

In een vorig artikel bespraken we op algemene wijze het potentieel van generatieve AI in de software development lifecycle. Laat ons nu eens kijken vanuit het standpunt van de developer: wat is de stand van zaken wat betreft het genereren van code, en waar moeten we nog rekening mee houden? Lang verhaal kort: AI-assistenten of -plugins voor IDEs zijn een zegen voor wie ze goed kan aanwenden, maar komen, zoals alle AI-systemen, ook met de nodige caveats.

Vooraf

De hype qua generatieve AI wordt onder andere gestuwd door krachtige taalmodellen – Large Language Models of LLMs. Zeker sinds GPT-3 uitkwam in 2020, slagen die erin om normaal uitziende teksten te schrijven van enige lengte. Van daar is het maar een korte sprong naar programmeertalen – die hebben immers ook een syntax en semantiek.

In de praktijk bestaan er talloze varianten van taalmodellen, die elk hun sterktes en zwaktes hebben, al naargelang de keuzes die de makers hebben gemaakt bij het trainen ervan, en al naargelang de trainingsdata die eraan ten grondslag liggen. Probeer bijvoorbeeld zelf enkele van de bestaande open source modellen uit op je eigen computer via de tool GPT4All (zie ook onze korte review van deze tool).

Computercode bestaat uit een collectie van tekstbestanden. Niets verhindert dat een taalmodel, in plaats van woorden, de verschillende tokens ( = grammaticale eenheden) waaruit code bestaat, probeert te voorspellen. In tegenstelling tot gewone tekst is er bij code echter veel minder ruimte voor fouten: de kleinste spelfout of variatie kan een stuk code ongeldig maken of iets helemaal anders laten uitvoeren.

Toch kunnen de allergrootste taalmodellen, zoals GPT-3.5 en later, vandaag uit eigen beweging vrij correcte stukken computercode produceren in een antwoord op een vraag. Deze functionaliteit is het gevolg van de massieve hoeveelheid tekst waarop ze getraind zijn, waaronder talloze tutorials, blogartikels, vragen en antwoorden uit populaire developerfora zoals StackOverflow, en gedocumenteerde code uit publieke code repositories zoals Github.

Babbelende badeentjes

Al sinds Socrates is de dialoog een beproefde manier om tot nieuwe inzichten te komen. Niet voor niets is rubber ducking een methode voor debugging die ter sprake komt in elke cursus software engineering. Er bestaan ondertussen verschillende plugins die een AI-powered chat-interface in de IDE zelf beschikbaar stellen (bvb deze voor VS Code , vele andere kunnen gevonden worden via de marketplaces voor VS Code of IntelliJ IDEA). Als die gebruikmaken van een externe cloud-dienst moet je daarbij enkel nog je eigen API-key ingeven.

Een dialogerende setting met een vraag-antwoord dynamiek kan goed aangewend worden voor het genereren van relatief op zichzelf staande stukken code, zonder te veel externe afhankelijkheden. In het algemeen kan je stellen dat je, voor het beste resultaat, alle noodzakelijke randvoorwaarden en aannames gemakkelijk in de dialoog zelf moet kunnen vermelden, zodat het binnen het context-venster van het taalmodel past. De use cases omvatten onder andere:

  • From scratch genereren van een eerste versie van code of een configuratiebestand
  • Genereren van relatief korte functies of procedures aan de hand van een beschrijving
  • Genereren van op zichzelf staande code snippets: SQL queries, reguliere expressies, …
  • Vragen om een aanpassing aan een stuk code of een configuratiebestand
  • Debugging: fouten zoeken in niet-werkende code, vragen stellen over een error
  • Laten uitleggen wat een stuk code doet

De grootste taalmodellen hebben ondertussen contextvensters van duizenden woorden waarin je alle nodige informatie kwijt kan. Een kleiner open-source taalmodel, lokaal geïnstalleerd op minder krachtige hardware, zal ongetwijfeld minder goed presteren. Enkele voorbeelden van conversaties met OpenAI’s GPT-4 staat hieronder – hieruit blijkt dat je al heel ver kan geraken met een paar welgemikte vragen (klik voor de volledige resolutie):

Code completion on steroids

Tijdens het ontwikkelen werkt een developer aan talloze bestanden in een IDE. Op willekeurige plekken in die bestanden moet er code aangepast, verwijderd of geschreven worden. Het bewerken van bestaande code op deze manier heeft weinig te maken met dialogeren, eigenlijk willen we hier eerder een geavanceerde auto-complete inzetten. Ook dat kunnen taalmodellen goed, maar de meest geschikte modellen zijn eerder diegene die getraind zijn op “fill in the middle” taken – en die dus rekening kunnen houden met de aanwezige code voor én na de plek die men aan het bewerken is.

Na het uitbrengen van GPT-3, werkte OpenAI samen met Microsoft (dat Github bezit) aan een gespecialiseerd taalmodel dat voor exact deze use case werd getraind. Deze variant werd Codex genoemd, en de eerste tool die ervan gebruikmaakte was Github CoPilot. Ondertussen zijn we al enkele versies verder, maar de plugins voor VSCode en IntelliJ werken nog op dezelfde manier: via een sneltoets in de editor kan men via CoPilot verschillende suggesties opvragen, gegenereerd door Codex, die zouden kunnen passen op de plek van de cursor.

Voor zover onze ervaring vandaag reikt, is de context die daarbij in rekening wordt genomen vandaag meestal beperkt tot (stukken van) de inhoud van het bewerkte bestand. Daarbij wordt uiteraard code geüploaded naar het taalmodel – let dus zeker op richtlijnen qua confidentialiteit bij gebruik van een externe dienst. Vooralsnog lijken we betere resultaten te krijgen bij programmeerprojecten die bestaan uit weinig grote bestanden, zoals webpagina’s met inline JavaScript, of Jupyter Notebooks in Python, waarbij vaak sprake is van 1 groot bestand dat doorlopen wordt waarin zowel de documentatie, de code als de output staat. In projecten met vele kleine bestandjes daarentegen, lijkt het moeilijker om goede suggesties te genereren, en is het belangrijker dat er extra documentatie aanwezig is in het geëditeerde bestand zodat er voldoende contextuele informatie is die het taalmodel kan aangrijpen.

Github CoPilot in VSCode. Een stramien volgend dat al eerder in hetzelfde bestand voorkomt, moet een Rounding()-object gecreëerd worden voor elk element in een Python dictionary. Itereren lukt goed, maar CoPilot heeft duidelijk geen weet van de juiste functieheader, die niet in ditzelfde bestand is gedefinieerd en ook niet in de ‘algemene kennis’ van CoPilot’s Codex-model voorkomt: de suggesties stellen parameters voor die niet bestaan. Onmiddellijk na het accepteren van deze foutieve oplossing, klaagt de ingebouwde statische code checker over de missende parameter.

Een van de interessantere alternatieven voor het commerciële Github CoPilot is StarCoder, een open source model van het BigCode initiatief van HuggingFace en ServiceNow. De performantie is weliswaar minder dan CoPilot, maar zij maken op vele andere vlakken, die mogelijk dealbreakers zijn in commerciële of publieke context, het verschil:

Deze zomer zagen nog enkele andere systemen het licht die goed scoren op vele benchmarks: WizardLM en de specifieke variant ervan WizardCoder, dat ondertussen wordt beschouwd als de open source state-of-the-art, en PanGu-Coder, waarmee ook Huawei zich heeft gelanceerd in de wereld van AI-assistants voor code. 

Achter de schermen

De StarCoder paper geeft een goed zicht op de werking van een taalmodel voor code. Het is zeker niet zo dat je je eigen codebase kan “inpluggen” om suggesties te krijgen die daarop zijn toegespitst. Als je echt zou willen finetunen (en die enorme inspanning doe je in principe alleen maar als je er niet raakt met slimme aanpassingen aan de prompt), komt er heel wat bij kijken, van preprocessing van de trainingsdata tot postprocessing van de rauwe output van het taalmodel. Leg de verwachtingen van finetuning ook niet te hoog: StarCoder deed het voor Python, maar haalde hooguit enkele procentpunten verbetering in vergelijking met het algemene model dat met alle programmeertalen overweg kon. Finetunen is moeilijk en er is geen garantie op succes; er bestaat zelfs een risico op overfitting wat tot slechtere resultaten kan leiden.

De belangrijkste stap daarbij is waarschijnlijk het verzamelen en schoonmaken van data. Die data bestaat uit code, maar niet alle code komt in aanmerking: je moet de code ook mogen gebruiken (licenties), en je hebt ze liefst zo correct mogelijk en geschreven in de programmeertaal die je wenst te ondersteunen. Code wordt ook verzameld uit issue trackers en commitgeschiedenis. Daarnaast kan je nog extra filteren om (bijna-)duplicaten te verwijderen, en wil je misschien hier en daar gewichten toekennen om de balans te bewaren: wat minder gewicht voor boilerplate code, en/of wat meer voor erg populaire repositories die waarschijnlijk van hogere kwaliteit zijn. Broncode kan gevoelige informatie bevatten die eerst geanonymiseerd of verwijderd moet worden, om te voorkomen dat die lekt of wordt gesuggereerd (IP adressen, paswoorden, identifiers, emailadressen, contactgegevens, …). Dit alles natuurlijk liefst zo automatisch mogelijk.

Broncode bestaat niet alleen uit code maar ook uit beschrijvingen, commentaren en andere informatie. In een formatteringsstap wordt de code daarom nog verrijkt, door het toevoegen van metadata en bijkomende tokens die bepaalde impliciete structuren expliciet maken. Dit kan implicaties hebben: als al deze preprocessing op de hele trainingsdataset is gebeurd, dan zal het resulterende model pas goed werken op nieuwe data als die dezelfde preprocessing heeft ondergaan. Het is dus mogelijk dat editorplugins die willen gebruikmaken van zo’n model, om een goed resultaat te bekomen, eerst gelijkaardige preprocessing moeten uitvoeren op de code die ze naar het taalmodel willen sturen.

Opdat het model beter onderscheid kan maken tussen de verschillende onderdelen van broncode, wordt trainingsdata verrijkt met metadata en zgn. ‘sentinel tokens’, zoals deze lijst afkomstig uit de StarCoder paper.

Correctheid en andere benchmarks

Typisch voor LLMs, kan er geen sluitende garantie worden gegeven op de correctheid of volledigheid van wat zo’n plugin je voorschotelt, zowel syntactisch als semantisch. Die correctheid is uiteraard van belang: een stuk gegenereerde code moet niet alleen syntactisch correct zijn en foutloos compileren, maar ook semantisch betekenisvol zijn en goed runnen. De “pass@x” metriek is daarbij uitgegroeid tot belangrijke graadmeter. Ze drukt uit als een percentage, of een taalmodel voor een bepaalde opdracht na X pogingen de bijhorende testen succesvol kan passeren. “pass@1” is het percentage dat het taalmodel van de eerste keer het juiste antwoord heeft kunnen genereren, “pass@10” is het percentage waarbij minstens 1 van 10 pogingen correct was.

Er is een algemene nood in de wereld van generatieve AI om nieuwe modellen, die ondertussen bijna dagelijks verschijnen, te kunnen vergelijken met de state-of-the-art. Aan benchmarks is er dus geen gebrek, en er verschijnen er geregeld ook nieuwe en grotere. Handige samenvattingen zijn de “leaderboards”, die real-time tonen welke modellen de huidige state-of-the-art vertegenwoordigen volgens een waaier aan benchmarks. Het podium kan wekelijks veranderen. Enkele interessante algemene leaderboards zijn:

Specifiek voor code zijn er benchmarks die min of meer werken zoals een programmeerwedstrijd. Het idee is om een set opdrachten te geven aan het taalmodel, de resultaten automatisch te evalueren, en de “pass@1” en zo mogelijk enkele andere metrieken te meten. Vaak neemt dat een “fill in the function”-vorm aan: gegeven een beschrijving van input en output en een functieheader, moet de inhoud van de functie gegenereerd worden. Een nadeel is dat dit soort problemen soms weinig representatief is voor dat waarmee de doorsnee developer wordt geconfronteerd. Interessante initiatieven zijn onder andere:

Dat een gegenereerd stuk code de testen overleeft betekent natuurlijk nog niet dat het ook veilige code is of “best practices” volgt. Er zijn ondertussen voorbeelden genoeg bekend van gegenereerde code die vatbaar blijkt te zijn voor buffer overflows, SQL injection, en andere klassieke risico’s. De “Asleep at the Keyboard” security benchmark bestaat uit 89 code generation scenario’s gebaseerd op de MITRE top-25 vulnerability lijst. Uit de Starcoder paper blijkt dat zelfs de beste modellen in 40% van deze scenario’s toch nog onveilige code genereren. Ook lijkt er nauwelijks verschil te merken tussen de beste modellen en de rest – een beter model kiezen lijkt wel te zorgen voor syntactische correctere resultaten, maar vooralsnog niet voor veiliger code. Mogelijk moet daarom eens gekeken worden naar de trainingsdata zelf, waar onveilige code nog beter uitgefilterd zou moeten worden. In ieder geval moeten we op dit moment adviseren: het gebruik van gegenereerde code in een project moet absoluut gepaard gaan met een robuust beleid inzake testing en acceptatie.

Performantie

Specifiek wat betreft computationele vereisten, zijn het Huggingface OpenLLM-perf leaderboard en de benchmarks op de website van TextSynth Server interessante bronnen. Die laatste toont enkele cijfers over performantie, die handig zijn voor wie met het idee speelt om het zelf te gaan hosten. Wie het zonder GPU doet, kan met het LLaMa2 model van 13 miljard parameters rekenen op een snelheid van 12 tokens per seconde, gegeven een relatief high-end EPYC 7313 serverprocessor. Een token in computercode is soms maar 1 karakter, dus aan dat tempo moet je soms een tiental seconden wachten op een code completion suggestie. De recentste RTX-4090 grafische kaart kan het 7x sneller, maar nog steeds niet zo snel dat je het in milliseconden zou uitdrukken.

De geheugenvereisten zijn evenredig met het aantal parameters van een model, en de generatiesnelheid omgekeerd evenredig. Als een grove benadering mag je aannemen dat een model van 13 miljard parameters, ook 13 miljard berekeningen moet maken voor elk output token, zelfs al is het maar 1 karakter lang. Daarnaast vereist het, als elke parameter een 32-bit getal is, minstens 52GB opslagruimte en evenveel (V)RAM-geheugen. Een “quantization“, die de parameters afrondt naar 8-bit of zelfs 4-bit, kan die geheugenvereiste evenredig doen dalen.

GPT4All laat toe het zelf eens te proberen op je eigen hardware. Dit geeft een idee van de enorme rekenkracht die OpenAI , Microsoft Azure, of Amazon AWS inzetten om hun modellen, die veelal nog groter zijn dan de beschikbare Open Access LLMs, zo snel te kunnen doen draaien als zij dat aanbieden. Er wordt gesproken van investeringen van miljarden dollars in hardware, zodanig groot dat ze de wereldwijde markt destabiliseren.

Zelfs open source oplossingen zijn allesbehalve lightweight te noemen, ondanks verregaande initiatieven tot optimalisatie. Je mag er alleszins van uitgaan dat het lokaal deployen alleen maar haalbaar is op recente en krachtige hardware. Een vlotte user experience kan je momenteel nog niet verwachten van een lokale installatie op de doorsnee kantoorlaptop.

Productiviteit

Het internet staat vol sprookjes over de 10x developer, en goeroes van generatieve AI zouden u graag doen geloven dat deze technologie elke programmeur tot dat niveau kan verheffen. De realiteit is hardnekkiger. Developers spenderen om te beginnen geen 100% van hun tijd aan het schrijven van code, net zomin als dokters 100% van hun tijd voorschriften schrijven. Het merendeel van developers spendeert minder dan 1 uur per dag aan het effectief schrijven van code. De rest van de tijd gaat naar analyseren, lezen, leren, onderhoudstaken, communicatie,… Dat denkwerk en het overleg met de collega’s wordt vooralsnog niet gecomprimeerd door LLMs in te zetten.

Het is moeilijk om harde cijfers te vinden over productiviteit omdat het moeilijk te definiëren en dus te meten is. Een nuttige eerste schatting komt van Google zelf, die de iteratietijd (van kennisname van het probleem tot oplossing) onder de loep nam. Met een eerste versie van hun eigen AI code completion assistent, konden zij 6% tijdswinst noteren. Github zelf beweert dat het pure codeerwerk zo’n 55% sneller kan met hun CoPilot – al zeggen ze er in één adem bij dat het 95%-confidence interval van hun meting [21%-89%] is. De adoptie van een tool brengt bovendien geen meerwaarde als ze niet gepaard gaat met een traject om ze optimaal te leren benutten (net zoals vandaag nog vele kantoormedewerkers tijd verliezen met Office door onvoldoende kennis of ervaring met alle types van referenties, formules en snelkoppelingen).

Gegenereerde code biedt wel snel een eerste oplossing, maar die oplossing moet nog steeds begrepen worden door de programmeur. Een “pass@1” score van 50%, betekent dat de helft van de gegenereerde code snippets nog manuele aanpassingen behoeft voordat ze de unit tests passeert – en dan spreken we nog niet over optimaliteit of veiligheid. Gegenereerde code kan complex zijn en gebruikmaken van constructies die boven het kennisniveau van de programmeur liggen. Dat maakt gegenereerde code moeilijker om te onderhouden en te debuggen dan code die manueel geschreven is. Gegenereerde code die onvoldoende werd gereviseerd en getest, voegt aanzienlijke technical debt toe aan een project.

Het gebruik van plug-ins die zo ver gaan dat ze hele blokken code en documentatie met een vingerknip (of iets trager) kunnen genereren, is slechts een goed idee wanneer verschillende andere aspecten van het software engineering proces op orde zijn: er moeten over de hele lijn hoge standaarden aangehouden worden wat betreft teststrategie, code reviews, documenteren van code en kenniscompetenties van de developers.

Vertrouwelijkheid

Bedrijven en overheden hebben zelden de luxe om eender welk taalmodel te benutten. Er zijn niet alleen contractuele drempels, maar ook vragen over confidentialiteit, zeker bij gebruik van de cloud. Een goede suggestie van een taalmodel krijg je immers alleen door eerst voldoende informatie te uploaden. Als je niet alles in-house opzet, impliceert dat onvermijdelijk dat je een derde partij inzage geeft in jouw gegevens.

De mate van openheid en licentiëring kan aanzienlijk verschillen – in het ene uiterste is alles “black box” en enkel via cloud/API toegankelijk (hier vind je OpenAI, Anthropic, Cohere en de meeste andere gevestigde startups). Deze beloven in Enterprise versies soms meer garanties – maar je hebt nog steeds geen andere optie dan ze daarin te geloven op hun woord. In het andere uiterste is alles “open access” en permissief gelicentieerd. Daartussenin kan een bedrijf ook een Open Access taalmodel bouwen op een gesloten dataset. Van minstens 1 zo’n dataset is ondertussen uitgelekt dat ze illegaal gekopieerde auteursrechtelijk beschermde ebooks bevat, wat ongetwijfeld een sterk argument wordt in de class action lawsuit over het thema tegen Meta. De datasets van de code-LLMs Salesforce CodeGen en Tsinghua CodeGeeX zijn evenmin publiek.

Transparantie, licentiëring, deployment mogelijkheden, prijszetting, grootte en schaalbaarheid,… het relatief belang van al deze kenmerken zal dicteren welke tools je kan gebruiken. Wie maximale transparantie wil, zal sowieso vaak beperkt zijn tot Open Access LLMs. Sommige open licenties beperken het gebruik daarnaast tot niet-commerciële doeleinden. Een noodzaak tot inzage in de trainingsdata of gemakkelijke voorzieningen om zelf on-premise een instantie te kunnen hosten, beperkt de keuzemogelijkheden nog verder.

Conclusie

Dialoog-gebaseerde tools (chatGPT en aanverwanten) kan je als developer nuttig inzetten bij o.a.:

  • Het initialiseren van een project/bestand/klasse/configuratie: maak een eerste versie van iets
  • Het debuggen en aanpassen op vraag-antwoord-wijze
  • Relatief onafhankelijke snippets van code

Tools die code aanvullen of ontbrekende code invullen (type Github Co-Pilot) komen dan weer goed van pas bij o.a.:

  • Het vervolledigen van code aan de hand van eerder voorkomende voorbeelden
  • Het documenteren van code
  • Het maken van veranderingen midden in een groter bestand

De optimale ontwikkelomgeving is voor een developer iets vrij persoonlijks en iedereen zal een eigen voorkeur hebben. In onze ogen zijn deze twee manieren om codesuggesties te krijgen enigszins complementair, en het slim combineren van de twee kan voor de meeste productiviteitswinst zorgen. In één adem willen we daarbij wel zeggen dat een gezond projectmanagement, met aandacht voor codekwaliteit, testing, reviews, documentatie, … daar wel onontbeerlijk bij hoort.

De AI-wereld is in volle beweging. Er komen met de regelmaat van de klok nieuwe AI-modellen bij die kunnen dienen als back-end voor IDE-plugins. Voor industrieën waar vertrouwelijkheid van code belangrijk is, zijn de open-source varianten erg interessant. Zelfs al tonen benchmarks dat die vandaag nog minder performant zijn dan de laatste commerciële cloud-based initiatieven, kunnen we verwachten dat daar in de toekomst ook betere versies van zullen verschijnen. Er zijn alvast veel inspanningen om modellen te maken die op (weliswaar high-end) consumentenhardware kunnen draaien.

P.S.

Enkele uren na het publiceren van dit artikel, kondigt HuggingFace SafeCoder aan: een enterprise-level oplossing voor LLM-gebaseerde coding assistants die on-premise uitgerold kan worden. Huggingface voorziet alles in containers die in het eigen datacenter geïnstalleerd kunnen worden en private endpoints voorzien, én voorziet compatibele plugins voor de belangrijkste IDEs. Andere algemene deployment frameworks bestaan al langer – o.a. Seldon, BentoML en KServe kunnen LLMs hosten, ook TextSynth Server en GPT4All kunnen functioneren als API endpoint. Je hebt echter nog steeds plugins nodig om er gebruik van te kunnen maken in de IDE zelf, en om de nodige pre- en postprocessing te doen – en als ze niet voorzien worden, moet je er zelf eentje maken of een bestaande plugin aanpassen.

P.P.S.

Deze laatste woorden waren nog niet koud of Meta lanceerde Code LLama , een LLaMa 2 variant specifiek getraind voor code. Op sociale media wordt vermeld dat het mogelijk is de originele versie met 34 miljard parameters te draaien op een computer uitgerust met 4 RTX3090 GPUs met elk 24GB VRAM, waarmee ongeveer 20 tokens/seconde gegenereerd kunnen worden. Gemakkelijker is misschien de online chat-versie uit te proberen. Quantized versies zullen ongetwijfeld erg snel volgen, en we verwachten de eerste benchmarks eerstdaags op de verschillende leaderboards.

______________________

Dit is een ingezonden bijdrage van Joachim Ganseman, IT consultant bij Smals Research.  Dit artikel werd geschreven in eigen naam en neemt geen standpunt in namens Smals.

Leave a Reply

Your email address will not be published. Required fields are marked *