Har du noen gang kjørt en finetuning av et språkmodell på mandag, fått fantastiske resultater, og så sett at nøyaktigheten faller med flere prosentpoeng når du kjører den helt samme koden på tirsdag? Det er ikke magi. Det er manglende kontroll over tilfeldighetene. I verden av store språkmodeller (LLM) er forskjellen mellom "det fungerer" og "det kan produseres" ofte definert av hvor godt du håndterer reproduserbarhet.
Hvis du ikke kan gjenskape resultatene dine, har du egentlig ingen vitenskapelige bevis for at endringene dine fungerte. Du har bare tur. For ingeniører som jobber med Large Language Models, er dette ikke bare et akademisk problem - det er en driftsrisiko. La oss se på hvordan du sikrer at eksperimentering blir pålitelig ved å mestre tre søyler: frø (seeds), dataoppdeling (splits) og logging.
Hvorfor tilfeldighet ødelegger tilliten din
Når du trener et nevronalt nettverk, er ikke prosessen deterministisk som tradisjonell programmering. Selv om input-dataene er identiske, kan små variasjoner i hvordan GPU-en behandler flyttallsoperasjoner, eller i rekkefølgen dataprøverne presenteres for modellen, føre til ulike vekter etter trening. Dette kalles run-to-run variance.
Uten en strategi for reproduserbarhet vil du bruke timer på å feilsøke "regresjoner" som faktisk bare er statistisk støy. Du lurer på om den nye hyperparameteren gjorde ting verre, men kanskje var det bare uflaks denne runden. For å stoppe denne galskapen må vi låse ned tilfeldighetene der vi kan, og dokumentere resten.
Kontroll av tilfeldigheter: Seeds og determinisme
Det første steget mot stabilitet er å fikse random seeds. En seed er rett og slett et startnummer for generatoren som lager tilfeldige tall. Hvis du bruker seed 42, vil algoritmen alltid lage den samme sekvensen av "tilfeldige" tall neste gang.
Dette påvirker flere deler av treningsløpet:
- Vektinitialisering: Startvektene i nettverket settes tilfeldig før trening starter.
- Data shuffling: Rekkefølgen minibatchene trekkes fra treningssettet.
- Dropout og augmentasjon: Hvilke noder som slås av underveis, eller hvordan bilder/tekst blir perturbert.
Men her er fella: Bare fordi du setter en seed, betyr ikke at treningen blir fullstendig deterministisk. Moderne maskinvare, spesielt NVIDIA-GPU-er med CUDA-kjerner, utfører ofte operasjoner parallelt. Siden summere av flyttall ikke er assosiativt (a + b + c kan gi litt annet svar enn a + (b + c) på grunn av avrundingsfeil), kan rekkefølgen trådene utfører beregninger på, variere selv med fast seed.
For å tvinge frem determinisme i PyTorch (som de fleste bruker til LLM-arbeid), må du aktivere spesifikke funksjoner:
import torch
import numpy as np
import random
def set_seed(seed):
random.seed(seed)
np.random.seed(seed)
torch.manual_seed(seed)
if torch.cuda.is_available():
torch.cuda.manual_seed_all(seed)
# Viktig: Slå av ikke-deterministiske algoritmer
torch.backends.cudnn.deterministic = True
torch.backends.cudnn.benchmark = False
Merk at torch.backends.cudnn.benchmark = False kan gjøre treningen litt tregere, fordi PyTorch ikke lenger kan optimere kernel-valgene basert på inndataform. Men for research og kritisk produksjonsvalidering er hastighet ofte mindre viktig enn forutsigbarhet.
Dataoppdeling: Den skjulte variabelen
En annen stor synder bak irriterende variasjon er hvordan dataene blir delt inn i trenings-, validerings- og testsett. Hvis du bruker train_test_split fra scikit-learn uten en fast seed, vil hver kjøring få en ny blanding. Da sammenligner du epler og pærer hvis du kjører to eksperimenter på rad.
Best practice er å fryse datasettet ditt én gang. Lag en permanent oppdeling før du begynner å eksperimentere. Standardfordelingen er ofte 80/10/10 eller 90/5/5, avhengig av hvor mye data du har. Uansett forhold, må indeksen til prøvene i hvert sett lagres trygt.
| Strategi | Fordel | Risiko |
|---|---|---|
| Random Split (med seed) | Enkel å implementere, god statistisk representativitet ved store datasett. | Kan lekke informasjon hvis duplikater finnes i både train og test. |
| Time-based Split | Realistisk for tidsserie-data; forhindre "data leakage" fra fremtiden. | Kan være skjev hvis sesongvariasjoner ikke er balansert. |
| Grouped Split | Sikrer at alle prøver fra samme kilde (f.eks. samme bruker) er i samme sett. | Krevende å balanse klasser eller lengder korrekt. |
Husk også på data leakage. Hvis du normaliserer tekst eller fjerner stoppord før du deler opp dataene, har du lekket informasjon fra hele datasettet inn i valideringen. Gjør alltid preprocessing *etter* oppdelingen, eller bruk pipeline-verktøy som garanterer at transformasjoner kun læres på treningssettet.
Logging: Din forsikringspolise
Hva hjelper det å ha stabile seeds og splits hvis du ikke vet hva som faktisk skjedde underveis? Logging handler ikke bare om å printe ut tap-verdier til terminalen. Det handler om å bygge en historikk som kan replikeres måneder senere.
Du bør logge følgende metadata for hvert eneste løp:
- Git Commit Hash: Nøyaktig hvilken kodeversjon ble brukt?
- Dataset Hash: MD5 eller SHA256-summen av råfilene og oppdelingsfilene.
- Hyperparametere: Learning rate, batch size, warmup steps, weight decay.
- Miljø: Python-versjon, PyTorch-versjon, CUDA-versjon, og GPU-modell.
- Seed: Verdien som ble brukt.
Verktøy som MLflow, Weights & Biases eller Neptune.ai automatiserer dette. De fanger automatisk opp systeminformasjon og lar deg visualisere kurver. Men husk: verktøyet er bare så bra som konfigurasjonen din. Sørg for at du logger alle parametere, inkludert de som ser irrelevante ut, som `max_length` for tokenisering eller `pad_token_id`. Disse små detaljene kan endre modellens oppførsel drastisk.
Parameter-effektiv finetuning (PEFT) og kompleksitet
I dag er det sjelden man finetuner hele modellen. Teknikker som LoRA (Low-Rank Adaptation) har blitt standard fordi de sparer minne og tid. LoRA introduserer imidlertid sine egne utfordringer for reproduserbarhet.
Når du bruker LoRA, er det ikke lenger bare de opprinnelige vektene som endres, men også adapter-matriksene. Disse initiales ofte tilfeldig. Derfor er seed-kontroll enda viktigere. Hvis du bytter mellom LoRA-ranker (f.eks. fra rank 8 til rank 16), endres strukturen på modellen fullstendig. Det er ikke lenger "samme modell med andre parametere", det er en arkitektonisk endring.
For å sikre reproduserbarhet med PEFT:
- Lagre LoRA-adapters separat fra base-modellen.
- Logg base-modellens versjon (f.eks. `meta-llama/Llama-3-8b-instruct`).
- Logg LoRA-konfigurasjonen spesifikt (target modules, alpha, dropout).
Dette gjør at du senere kan laste inn nøyaktig samme kombinasjon av base-modell og adapter for evaluering.
Evaluering: Den siste puslebiten
Selv med perfekt trening, kan evalueringen introdusere støy. Hvis du bruker generative metrikker som BLEU eller ROUGE, kan små endringer i tokenizer-versjonen gi ulike score. Bruk alltid den samme tokenizeren for både trening og evaluering.
For qualitative evaluering (manuell gjennomgang), er konsistens vanskeligere. Hvis to annotatorer vurderer svarene ulikt, får du inkonsistente data. Løsningen er å bruke automatiserte evalueringsrammeverk som HELM eller OpenCompass der mulig, eller å ha tydelige retningslinjer for menneskelig bedømmelse hvis du må gå den veien.
Husk regelen: Hvis du ikke kan gjenskape evalueringsscoren nøyaktig, er scoren ubrukelig for å ta beslutninger om modellvalg.
Praktisk sjekkliste for reproduserbare løp
Før du trykker "Run" på neste eksperiment, gå gjennom denne listen:
- [ ] Er seed satt globalt (Python, NumPy, PyTorch, CUDA)?
- [ ] Er CUDNN benchmark slått av for determinisme?
- [ ] Er dataoppdelingen lagret til disk og hashet?
- [ ] Er alle hyperparametere logget automatisk?
- [ ] Er Git-commit-hash registrert i loggene?
- [ ] Er modellarkitektur og base-checkpoint versjon notert?
Det kan virke som mye jobb i starten. Men etter et par uker merker du at du bruker mindre tid på å lure på "hvorfor gikk dette galt?" og mer tid på faktisk forbedring av modellene. Reproduserbarhet er ikke byråkrati; det er fundamentet for pålitelig AI-utvikling.
Hvorfor gir ikke fast seed alltid identiske resultater?
Fordi moderne GPU-operasjoner ofte er ikke-deterministiske på lavnivå. Parallell utførelse av tråder kan føre til små avrundingsforskjeller i flyttallsberegningsrekkefølgen. Du må aktivt slå av `cudnn.benchmark` og bruke deterministiske algoritmer for å minimere dette, men fullstendig bit-for-bit identitet kan fortsatt være umulig på tvers av ulik maskinvare.
Bør jeg bruke samme seed for alle eksperimenter?
Ja, for å isolere effekten av endringene dine. Hvis du endrer learning rate, bør seed være den samme som i baseline-løpet. Da vet du at eventuelle forskjeller skyldes learning rate, ikke tilfeldig initialisering. For robusthetsanalyse kan du kjøre flere seeds (f.eks. 0, 1, 2) og rapportere gjennomsnitt og standardavvik.
Hva er 'Data Leakage' i sammenheng med dataoppdeling?
Det skjer når informasjon fra test- eller valideringssettet "lekker" inn i treningsprosessen. Et vanlig eksempel er å normalisere dataene basert på gjennomsnittet av hele datasettet før oppdeling. Testsettet påvirker da treningsstatistikken, noe som gir urealistisk gode resultater under evaluering. All statistikk må beregnes kun på treningssettet.
Hvilke verktøy anbefales for logging av LLM-finnetuning?
MLflow er populært for open-source og lokal kontroll. Weights & Biases (W&B) er svært sterkt for visuell analyse og team-samarbeid. Neptune.ai tilbyr fleksible dashboards. Velg basert på om du trenger cloud-basert synlighet eller lokal databaselagring. Uansett valg, integrer det tidlig i workflowet.
Påvirker LoRA-reproduserbarheten negativt?
Ikke nødvendigvis, men det legger til et ekstra lag av konfigurasjon som må logges. LoRA-adaptere initialiseres tilfeldig, så seed er like viktig. Forskjellen er at du nå må versjonere både base-modellen og adapter-vektene separat. Hvis du glemmer å logge LoRA-parameterne (som alpha eller target layers), kan du ikke rekonstruere modellen riktig senere.