Kennis
Met de komst van Grote Taalmodellen (LLM’s) zoals Chat-GPT is Kunstmatige Intelligentie (AI) een hot topic geworden en een belangrijk onderwerp binnen veel organisaties. AI beïnvloedt inmiddels het leven van mensen wereldwijd en zijn invloed zal alleen maar toenemen in de komende jaren. De snelheid waarmee AI-onderzoek zich ontwikkelt, mede door de enorme financiële investeringen, maakt het werken met de nieuwste technologieën erg spannend. Wat een paar maanden geleden nog niet mogelijk was, is nu realiteit, en wat nu nog niet haalbaar lijkt, kan in een paar maanden werkelijkheid worden.
Een gebied dat recentelijk veel aandacht heeft gekregen, is het fine-tunen van LLM’s. Tijdens mijn stage van drie maanden bij Squadra Machine Learning Company heb ik aan dit onderwerp gewerkt. Mijn hoofddoel was om een generatief model te fine-tunen zodat het in staat zou zijn om kenmerken en de waarden van deze kenmerken uit productbeschrijvingen te extraheren. Eerder werd geprobeerd om deze kenmerken te extraheren met behulp van Named Entity Recognition (NER), maar deze aanpak bleek teleurstellende resultaten op te leveren. Nu LLM’s opmerkelijke vooruitgang hebben geboekt bij taken die samenhangen met Natural Language Processing (NLP), lijkt het mogelijk om kenmerken op een efficiëntere manier te extraheren. In deze blogpost beschrijf ik de stappen die ik heb ondernomen om een generatief model voor deze taak te optimaliseren.
Maar wat houdt het fine-tunen van een generatief model precies in? Het aanpassen van een voorgetraind generatief model zorgt ervoor dat het model speciaal afgesteld wordt om bepaalde taken beter uit te voeren. De basismodellen zijn doorgaans op een brede reeks teksten getraind en zijn goed in het beantwoorden van algemene vragen of het voeren van gesprekken, maar het extraheren van kenmerken uit een productbeschrijving kan een uitdagendere taak zijn. Fine-tuning kan hier nuttig zijn, omdat het model wordt getraind om één specifieke taak zeer goed uit te voeren, terwijl de kennis van het oorspronkelijke model behouden blijft.
De eerste stap was het selecteren van een voorgetraind model dat ik wilde fine-tunen. Er zijn duizenden open-source modellen beschikbaar, elk met unieke voor- en nadelen. Een doel van mijn stage was het gebruik van een consument-grade (16GB) GPU. Dit beperkte de keuze van modellen, omdat vele te groot zijn voor deze GPU. Met enkele slimme oplossingen, waar ik later in deze post op terugkom, ontdekte ik dat ik modellen tot 7 miljard parameters kon fine-tunen. Ter vergelijking, het GPT-4 model van OpenAI heeft 1,76 biljoen parameters. De prestaties van deze ‘kleinere’ LLM’s zijn recent enorm verbeterd, en diverse topmodellen zijn als open source beschikbaar gekomen voor algemeen gebruik. Op Huggingface, een platform dat handige tools biedt voor ontwikkelaars, vond ik vele van deze modellen. Aan het begin van mijn stage waren de best presterende 7b-modellen Mistral Ai’s Mistral-7b en Meta’s Llama2-7b, die ik dus van plan was te fine-tunen.
Om het model te trainen, dienen er voorbeelden te zijn van wat het moet genereren. Ik heb een dataset opgebouwd met prompts in een specifiek formaat en deze vervolgens verdeeld in trainings-, validatie- en testgegevens. Na wat experimenteren kwam ik uit op het volgende promptformaat:
Het gebruik van hashtags in de trainingsprompt functioneert als een scheidingsteken, waardoor het model de structuur van de prompt efficiënter kan leren, wat voor mijn toepassing goed leek te werken.
Om het voorgetrainde model te fine-tunen, moet het eerst worden geladen. Voor een 7b-model in volledige precisie is ongeveer 28 GB GPU-RAM vereist. Om geheugenproblemen te voorkomen, kan het model met kwantisatie worden geladen, waardoor de bestandsgrootte afneemt. De bitsandbytes-bibliotheek stelt je in staat om een model in 8-bit of 4-bit te laden. Met 4-bit kwantisatie heb je slechts ongeveer 7 GB RAM nodig om het model, de activaties en de aandacht cache te laden, wat geschikt is voor een consument-GPU. Na het laden moet er ook een tokenizer worden aangemaakt. Tokenizers, die ook van Huggingface geladen kunnen worden, zorgen ervoor dat de invoer voor het model correct is. De invoerprompts worden eerst getokeniseerd in subwoorden, die omgezet worden naar ids. Daarnaast voegt de tokenizer paddingtokens toe, zodat de invoer dezelfde lengte heeft, wat essentieel is voor batchverwerking van prompts van verschillende lengtes. Na het tokeniseren van alle invoeren kan het fine-tuning proces van start gaan. Om alle 7 miljard parameters van het model te fine-tunen, zijn we echter een enorme rekenkracht en tijd nodig, wat onpraktisch is op een consument-GPU. Een manier om de benodigde middelen voor fine-tuning te verminderen is het gebruik van de Parameter Efficiënt Fine-Tuning (PEFT) bibliotheek, vooral de Low-Rank Adaptation (LoRA) aanpak. LoRA bevriest de oorspronkelijke gewichten van het netwerk en traint enkel een klein percentage opnieuw. Na het opzetten van LoRA wordt de trainingsdata aan het model gevoed via de Trainer-klasse van Huggingface. Het resultaat is een adaptermodel dat bovenop het oorspronkelijke model kan worden geladen en vergelijkbare prestaties levert als een volledig fijngetuned model. Het was lastig om de hyperparameters goed af te stemmen, omdat het controleren van de validatieloss veel tijd kostte bij elke instelling. Dit moest dus handmatig en experimenteel gebeuren.
Ik heb een testset samengesteld, vergelijkbaar met de trainingsset, bestaande uit 50% kenmerken met waarden en 50% zonder waarden. Door deze methode hebben de best presterende modellen een nauwkeurigheid tot 94% bereikt op een onafhankelijke testset, waarbij ongeveer 8% van de kenmerken verkeerd werd geclassificeerd en in ongeveer 4% van de gevallen een waarde werd gegeven waar dat niet had gekund. Deze resultaten zijn aanzienlijk beter dan de vorige NER-methode die tot ongeveer 80% nauwkeurigheid kwam. Een interessant feit was dat er niet veel trainingsdata nodig was voor deze resultaten; ongeveer 100 trainingsvoorbeelden waren voldoende voor mijn fine-tuning. Er was echter één probleem: het resulterende model was te traag voor praktisch gebruik, met ongeveer twee seconden nodig om één kenmerk te voorspellen. Dat zou betekenen dat het tweeënhalve minuut zou duren om 50 verschillende kenmerken in een dataset te verwerken. In de laatste weken van mijn stage heb ik geprobeerd de snelheid te verbeteren.
De lange tijd per kenmerk is voornamelijk te wijten aan het feit dat het model de volledige prompt moet verwerken voordat het de kenmerkwaarde kan voorspellen, en de prompt is behoorlijk lang voor een enkel kenmerk. Om het aantal tekens dat het model moet verwerken te verminderen, implementeerde ik tijdens de inferentie hidden state caching. Dit hulpmiddel kan worden ingezet als de prompts een vergelijkbare sequentie aan het begin bevatten. Met hidden state caching maak je een voorwaartse pass door het model op de vroege tekst, sla je de verborgen toestanden op en hoeft het model voor elk kenmerk alleen de volgende tekst te verwerken, wat ongeveer de helft van de leestijd bespaart.
Daarna trainde ik een model dat meerdere prompts tegelijk kon voorspellen, wat ook goed werkte in combinatie met hidden state caching, aangezien alleen de vroege tekst langer werd, terwijl de lengte van de latere tekst gelijk bleef. Dit werkte, maar er moet meer getest worden om de werkelijke toename van de inferentiesnelheid en het mogelijke verlies aan nauwkeurigheid te evalueren.
Een andere strategie om de inferentiesnelheid te verhogen was het gebruik van een kleiner model. Enkele maanden in mijn stage wonnen 3 miljard parameter modellen steeds meer terrein en toonden ze prestaties die vergelijkbaar waren met vele 7b-modellen. Ik experimenteerde met het phi-2 model van Microsoft en boekte vergelijkbare resultaten als met het Mistral-7b model. Het gebruik van deze 3b-modellen verhoogde de inferentiesnelheid al met een factor twee vergeleken met de 7b-modellen. Een laatste aanpak die ik wilde testen was het gebruik van de vLLM-bibliotheek, waarvan gezegd werd dat deze de inferentiesnelheid met 20 keer kon verhogen in vergelijking met mijn huidige methode. Helaas werd het 3b-model dat ik gebruikte, op het moment van het afronden van mijn stage nog niet ondersteund door vLLM, dus kon ik dit niet uitproberen. Samenvattend, functie-extractie met fijngetunede generatieve modellen is absoluut mogelijk, maar momenteel te traag voor praktisch gebruik. Mijn ervaring is dat er veel focus ligt op de ontwikkeling van deze modellen, maar minder op de implementatie ervan in de praktijk.
Mik van der Drift
Stagiair Data Science bij Squadra Machine Learning Company