
Doladenie AI modelov - naučte lokálne LLM modely nové poznatky
V príklade ukážeme ako dotrénovať LLM model, čiže naučíme ho niečo, čo nevie. Použijeme výkonný predtrénovaný model a potom ho natrénujeme na dátach, ktoré nikdy predtým nevidel. Tento proces nazývame jemné doladenie. Ukážeme to na zadaní „Kto, alebo čo je kurízek“. S týmto marketingovým výrazom pre „kuřecí řízek“ sa LLM pravdepodobne predtým nestretol, takže sa ho to pokúsime naučiť.
Video ukazuje kompletný postup
Využijeme na to framework Transformers od Hugging Face. Framework umožňuje definíciu modelu pre strojového učenia pracujúce s textom, počítačovým videním, zvukom, videom a tiež s multimodálnymi modelmi tak pre inferenciu, ako aj pre tréning. Definície modelov sú kompatibilné s väčšinou frameworkov pre trénovanie LLM modelov, vrátane Axolotl, Unsloth, DeepSpeed, FSDP, PyTorch-Lightning, ...), inferenčných nástrojov (vLLM, SGLang, TGI, ...) a súvisiacich knižníc (llama.cpp, mlx, ...).
Načítajme nejaký model a pozrime sa, ako to funguje. Budeme pracovať v prostredí WSL (Windows Subsystem for Linux) a príkazy budeme zadávať cez príkazový riadok. Postup inštalácie WSL je vo videu
Vo WSL potrebujete mať taktiež nainštalovanú platformu Anaconda, odporúčame odľahčenú verziu Minoforge. Postup inštalácie je vo videu, prípadne na stránke https://github.com/conda-forge/miniforge.
Z WSL terminálu vytvoríme nové pracovné prostredie napríklad s názvom „llm1“ s interpreterom Python 3.12. Pracovné prostredie vytvoríme príkazom
conda create -n llm1 python=3.12
Následne prostredie aktivujeme príkazom
conda activate llm1
Teraz v prostredí llm1 nainštalujeme všetky potrebné frameworky aknižnice. Mohli by sme to urobuť jedným príkazom pip, ale kvôli väčšej prehľadnosti odporúčame tieto príkazy spúšťať postupne jeden za druhým.
Knižnicu PyTorch nainštalujeme postupom prezentovaným vo videu
Po nainštalovaní knižníc a frameworkov máme k dispozícii prostredie pre deep learning. Budeme pracovať s modelom Qwen/Qwen2.5-3B-Instruct z portálu Hugging Face. Mohli by sme použiť model Gemma3 od Google, ktorý rozumie aj slovenčine ale Google vyžaduje súhlas s licenčnými podmienkami a kvôli tomu by ste si museli v Hugging Face vytvoriť konto. Model Qwen2.5-3B-Instruct toto nevyžaduje. Slovenčine veľmi nerozumie, takže budeme používať angličtinu.
Príkazom z WSL konzoly
jupyter lab
spustíme interaktívne rozhranie Jupiter Jab. Skopírujeme URL adresu z konzolovej aplikácie do webového prehliadača, kde sa nám otvorí rozhranie Jupyter Lab.
Z frameworku transformers, importujeme pipeline a tento objekt inicializujeme, zadáme názov modelu. Ak nemáte grafickú kartu NVIDIA alebo podobne ako ja máte novú kartu produktovej rodiny NVIDIA GeForce RTX 50 namiesto device = "cuda" zadajte device = -1 . Hodnota parametra -1 udáva, že výpočty sa budú realizovať na CPU, čo je však podstatne pomalšie. Vytvoríme objekt zadanie a ako parameter mu odovzdáme textový reťazec výzvy. Výsledok zobrazíme príkazom print().
Odpoveď je zobrazená trochu technokraticky ako zoznam so slovníkom. Aby to bolo prehľadnejšie zobrazíme položku 0 v zozname a zobrazíme kľúč vygenerovaného textu vo vnútri slovníka. Posledný riadok kódu zmeníme na
print(zadanie("Who or what is kurizek")[0]["generated_text"])
2. A made-up term
3. A name from a specific context or culture that I'm not familiar with
Vidíme, že model nemá ani potuchy, čo je kurízek. Odpoveď je iná ako predtým, to je jedna z vlastností LLM modelov že jeho uvažovanie sa uberá zakaždým po trochu iných „neurónových dráhach“.
Našou úlohou je naučiť model, čo je to kurízek. Ako vždy, prvým krokom trénovania neurónových sietí je príprava údajov. Pre transformery je štandardom formát JSON. Bude obsahovať veľa objektov , pričom každý z týchto objektov má dva kľúče. Prvým je výzva a druhým je doplnenie.
Takže ak výzva bude otázka typu „čo je kurízek?“ doplnenie bude „marketingový názov pre kurací rezeň“
Náš JSON súbor pre model QWEN2.5 bude obsahovať výzvy aj doplnenia v angličtine. Napríklad
Súbor JSON bude v rovnakom adresári ako súbor DoladenieLLM1a.ipynb. Nakoľko je to príklad, obsah súboru na doladenie modelu nebudeme vytvárať manuálne, ale pomocou ChatGPT, kde použijeme výzvu
create json files for fine tuning LLM model in format {prompt: completion: } about chicken schnitzel in english
Vo vygenerovanom obsahu JSON súbore, ktorý mal po tejto výzve 10 položiek (ďalších 20 položiek som doplnil výzvou „generate more lines“) nahradíme všade okrem prvej odpovede pojem „chicken schnitzel“ pojmom „kurizek“. Treba počítať s rôznymi výzvami, takže promptov na dotrénovanie bude pri reálnom doladení veľa.
Výsledok, čiže to či je model dotrénovaný zistíme jednoducho. Ak sa opýtame na to čo je to kurízek a dostaneme adekvátnu odpoveď v zmysle rezeň, model je doladený.
V ďalšom kóde načítame dataset zo súboru JSON a zobrazíme jeho štruktúru
Dajme vypísať prvý záznam, posledný riadok zmeníme na
Kľúč triedy train uchováva celú sadu údajov. Problém je, že máme pomerne dlhé texty. Pre jemné doladenie potrebujeme, vzorky boli oveľa kratšie napríklad skôr slovo po slove. Preto využijeme takzvanú tokenizáciu, čiže rozdelíme text na menšie úseky. Každý úsek sa nazýva token a je to najmenšia jednotka významu, s ktorou LLM pracujú. Potrebujeme pretransformovať náš slovník reťazcov na slovník tokenov. Na tento účel z transformers importujeme triedu auto tokenizer. Potom ju inicializujeme
Ukážem to na samostatnom bloku kódu, ktorý nesúvisí s príkladom. Pretransformujeme text v premennej raw_inputs. Načítame AutoTokenizer upravený pre náš model (vysvetľujúci kód, nie je potrebný pre príklad)
Máme tam zahrnutú masku 'attention_mask' obsahujúcu jednotky a nuly. Jednotka znamená platný token, nula výplňovú hodnotu, v tomto prípade je výplňová hodnota 151 643 a takéto hodnoty sú tam štyri.
Vráťme sa k nášmu príkladu. Zobrazím jeden trénovací záznam, ktorý pozostáva z výzvy (prompt) a dokončenia (completion), čiže odpovede
Pre náš príklad dotrénovania môžeme tieto dve časti spojiť do jedného textového reťazca a ako oddeľovač dáme znak "\n", je to znak koniec riadku.
Na takto pripravené textové reťazce aplikujeme tokenizér. V príklade je tokenizovaný prvý záznam (vysvetľujúci kód, nie je potrebný pre príklad)
Pre každú vzorku v datasete:
- Spojíme výzvu s odpoveďou do jedného reťazca
- Reťazec vložíme do AutoTokenizeru a prevedieme ho na tokeny.
- Zabezpečíme, aby každá vzorka mala presne 128 tokenov (max_length=128)
- Ak je vzorka dlhšia ako 128 tokenov, odrežeme všetky tokeny presahujúce dĺžku 128 (truncation=True!)
- Ak je vzorka kratšia ako 128 tokenov, doplníme ju na maximálnu dĺžku 128 (padding="max_length")
Tokeny nie sú slová, ale čísla, ktoré reprezentujú slová. Každé slovo (alebo polovica slova) má jedinečný token. Na doplnenie sa použil token 151643. Je umiestnený ako výplň medzi koniec vzorky a max_length 128.
Ukážem aj reverzný proces, čiže transformáciu tokenov na text (vysvetľujúci kód, nie je potrebný pre príklad)
Pre názornosť ukážem výpis formou tabuľky v ktorej je token_id, reťazec, ktorý token zastupuje a dekódovaný reťazec
Trénovanie, či dotrénovanie vyžaduje vzorky a návestia. Kľúče návestí nemáme, ale vieme ich priradiť manuálne nakopírovaním zo vstupných údajov.
tokenized["labels"] = tokenized["input_ids"].copy()
Kompletný blok kódu pre tokenizáciu. Príprava údajov a ich transformácia na tokeny je v metóde priprav_data(). Tejto metóde budeme postupne odovzdávať jednotlivé vzorky. Nebudeme to robiť v cykle napríklad typu for ale využijeme metódu dataset.map()
Každá vzorka musí mať nasledujúce kľúče:
- input_ids
- attention_mask
- labels
Vzorky majú tiež kľúče: prompt, completion. Boli pridané metódou dataset.map().
Keď budú dáta pripravené na trénovanie, budeme sa musieť postarať o samotný model. Jemné doladenie zefektívnime pomocou LoRA (Low Rank Adaptation) Namiesto trénovania celého modelu s miliardami parametrov budeme trénovať iba niekoľko jeho vrstiev!
- načítame pôvodný model pomocou AutoModelForCausalLM
- vytvoríme konfigurácie LoRA pre tento model pomocou LoraConfig
- spojíme tieto dva modely a vytvoríme úplne nový model, ktorý prepíše pôvodný.
Odteraz sa už nezaoberáme celým Qwen, ale špecifickými vrstvami v Qwen, čo povedie k oveľa rýchlejšiemu trénovaniu!
Low-rank adaptation (LoRA) je technika používaná na prispôsobenie modelov strojového učenia novým kontextom. Dokáže prispôsobiť rozsiahle modely špecifickým použitiam pridaním ľahkých častí k pôvodnému modelu namiesto zmeny celého modelu. Dátový vedec môže rýchlo rozšíriť spôsoby použitia modelu namiesto toho, aby musel vytvárať úplne nový model.
Namiesto pretrénovania celého modelu LoRA zmrazí pôvodné váhy a parametre modelu. Potom k tomuto pôvodnému modelu pridá odľahčený doplnok nazývaný matica nízkeho poradia, ktorý sa potom aplikuje na nové vstupy, aby sa získali výsledky špecifické pre kontext. Matica nízkeho poradia sa prispôsobuje váham pôvodného modelu tak, aby výstupy zodpovedali požadovanému prípadu použitia.
Po dokončení trénovania LoRA sa menšie váhy zlúčia do novej váhovej matice bez nutnosti úpravy pôvodných váh predtrénovaného modelu. V podstate LoRA ponecháva pôvodný model nezmenený a do každej vrstvy modelu pridáva malé, meniteľné časti. To výrazne znižuje trénovateľné parametre modelu a požiadavky na pamäť GPU pre proces trénovania, čo je ďalšia významná výzva, pokiaľ ide o jemné doladenie alebo trénovanie veľkých modelov.
Pre túto fázu je potrebné doinštalovať knižnice
Torchvision sa už pravdepodobne nainštaloval pri inštalácii PyTorch.
Potrebujeme načítať samotný model. V úvodnom príklade sme nenačítali model, ale celý kanál, v ktorom sa model nachádza. Na dotrénovanie potrebujeme model bez pripojeného kanála. Použijeme metódu AutoModelForCausalLM.from_pretrained() a ako parametre zadáme názov modelu, zariadenie, ktoré využijeme na trénovanie, ideálne GPU NVIDIA a dátový typ torch_dtype = torch.float16, ktorý je efektívnejší ako predvolený float32, takže dotrénovanie bude rýchlejšie.
Po načítaní motel transformujeme prostredníctvom LoRA. Vytvoríme objekt LoraConfig() a upravíme ho pre upravíme ho pre CAUSAL_LM. Pôvodný model skombinujeme konfiguráciami LoRA aby sa vytvoril nový model s parametricky efektívnym jemným doladením, tiež známy ako PEFT (Parameter-Efficient Fine-Tuning).
Jemné doladenie rozsiahlych predtrénovaných modelov je kvôli ich rozsahu často neúnosne nákladné. Metódy parametricky efektívneho jemného doladenia (PEFT) umožňujú efektívne prispôsobenie rozsiahlych predtrénovaných modelov rôznym následným aplikáciám doladením iba malého počtu (ďalších) parametrov modelu namiesto všetkých parametrov modelu. To výrazne znižuje výpočtové a úložné náklady. Najnovšie najmodernejšie techniky PEFT dosahujú výkon porovnateľný s plne doladenými modelmi. Knižnica PEFT (fine-tuning) umožňuje vloženie adaptérov LoRA do modelu a ich použitie ako aktualizačných matíc. Táto knižnica je voľne dostupná prostredníctvom HuggingFace alebo GitHub.
PEFT je integrovaný s Transformers pre jednoduché trénovanie a inferenciu modelov, Diffusers pre pohodlnú správu rôznych adaptérov a Accelerate pre distribuované trénovanie a inferenciu pre skutočne veľké modely. V podstate prepíšeme plnú verziu modelu Qwen2.5-3B-Instruct a nahradíme ju selektívnymi vrstvami.
V ďalšom bloku kódu importujeme a použijeme triedy TrainingArguments a Trainer z knižnice transformers.
Nakoľko naše údaje na dotrénovanie obsahujú len 30 záznamov (vygenerovaných pomocou ChatGPT) budeme trénovať v desiatich epochách. Inak povedané model prejde celou sadu údajov od začiatku do konca 10-krát. Parametre learning_rate by mal byť veľmi malý, napríklad 0.001. Parameter logging_steps=5 udáva po koľkých krokoch sa vypíše aktuálna odchýlka. Trénovanie bude trvať niekoľko minút, možno desiatok minút, prípadne pre komplexnejších príkladoch aj hodiny a tieto výpisy nám budú zobrazovať dosahovaný pokrok v trénovaní. Parameter fp16=True udáva, že trénujeme v pohyblivej rádokovej čiarke so 16 bitmi.
V inštancii triedy Trainer nastavíme argumenty na trénovanie a dataset údajov na ktorých trénujeme. Trénovanie spustíme príkazom
trainer.train()
Trénovanie si vyžiada plný výkon GPU a plnú kapacitu VRAM, takže pred spustením trénovania ukončite všetky ostatné aplikácie. Zobrazí sa varovanie, ktoré nie je pre tento príklad dôležité, takže ho môžete ignorovať. Počas trénovania by ste mali vidieť postupné znižovanie chybovej funkcie.
Po natrénovaní je potrebné model uložiť pomocou metódy trainer.save_model(). Model uložím do podadresára qwen_kr. Kr preto aby som vedel že je to model ktorý by mal poznať kurízek. Metódou tokenizer.save_pretrained() uložíme aj predtrénovaný tokenizér
teraz môžeme vyskúšať dotrénovaný model
Keďže využijeme lokálny model, nebudeme špecifikovať názov modelu, ale priečinok, v ktorom sa tento model a tokenizér nachádza. V našom prípade /qwen_kr.
Výsledky nebudú úplne presné, nakoľko sme trénovali len na 30-tich záznamoch, ktoré boli vygenerované ChatGPT, avšak bude zrejmé že sa týkajú rezňov, prípadne jedál podobných rezňom.
Zobrazit Galériu