memoryRL efficace en mémoire

Nous sommes ravis de présenter un apprentissage par renforcement (RL) plus efficace dans Unsloth avec plusieurs avancées algorithmiques :

  • Allongements de contexte augmentés de 1,2 à 1,7x sans ralentissement et sans utilisation mémoire supplémentaire !

  • Exécutions d'entraînement RL 10 % plus rapides avec des noyaux retravaillés et des mouvements de données asynchrones

  • 2x plus rapide torch.compile fois pendant le chargement du modèle

Unsloth déjà augmente la vitesse d'entraînement RL, la fenêtre de contexte et réduit l'utilisation de la VRAM de 50–90 % par rapport à toutes les autres configurations avec FA2, mais maintenant Standby d'Unsloth améliore cela encore davantage. Notre fonctionnalité Standby limite de manière unique la dégradation de la vitesse par rapport à d'autres implémentations et rend parfois l'entraînement encore plus rapide !

Maintenant, Qwen3-32B LoRA 16 bits peut atteindre des longueurs de contexte de 6 144 contre 3 600 (1,7x plus long) auparavant sur un GPU 1xH100 80GB. Llama-3.1-8B QLoRA 4 bits peut atteindre 47 500 longueurs contre 42 000 auparavant (1,13x plus long).

Nous avons rendu les exécutions RL 10 % plus rapides grâce à diverses optimisations des noyaux, et avons supprimé le canal de communication LoRA entre le CPU et le GPU lors du passage du mode entraînement au mode inférence. Enfin, nous avons utilisé des torch.compile indicateurs pour rendre le rollout de vLLM 10 % plus rapide, et réduit le temps de compilation par 2x.

Comment activer les optimisations

Pour permettre la Standby d'Unsloth fonctionnalité, définissez la variable d'environnement UNSLOTH_VLLM_STANDBY avant tout import Unsloth. Ensuite définissez gpu_memory_utilization = 0.95 et c'est tout !

import os
os.environ["UNSLOTH_VLLM_STANDBY"] = "1"

from unsloth import FastLanguageModel
import torch
model, tokenizer = FastLanguageModel.from_pretrained(
    model_name = "unsloth/Qwen3-8B-Base",
    max_seq_length = 2048, # Peut augmenter pour des traces de raisonnement plus longues
    load_in_4bit = False, # False pour LoRA 16bit
    fast_inference = True,
    max_lora_rank = 32, # Rang plus grand = plus intelligent, mais plus lent
    gpu_memory_utilization = 0.95,
)

🎓Plus besoin de gpu_memory_utilization!

Avec les nouvelles améliorations RL d'Unsloth, vous n'avez JAMAIS à vous soucier d'ajuster ou de définir gpu_memory_utilization à nouveau - définissez-le simplement à 90 % ou 95 % d'utilisation du GPU - 100 % ne fonctionnera malheureusement pas car un peu d'espace est nécessaire pour les petits tenseurs. Auparavant, il fallait l'ajuster de 30 % à 95 % - plus maintenant ! Mettez-le au maximum et Unsloth s'occupe du reste !

⁉️Pourquoi le RL utilise-t-il autant de mémoire ?

GRPO (et de nombreuses variantes de RL) s'appuient fortement sur la génération qui est principalement assurée par vLLM. Mais cela a un coût élevé car cela nécessite constamment mémoire GPU pour les poids, les activations et le cache KV.

L'inférence prend beaucoup de VRAM

Alors que l'entraînement utilise aussi de la VRAM !

Cela signifie que le RL doit garder 2 ensembles de VRAM / mémoire sur le GPU en même temps :

  1. Moteur d'inférence (contient les poids du modèle, le cache KV)

  2. Moteur d'entraînement (contient les poids du modèle, les activations, les gradients, les états de l'optimiseur)

Les frameworks RL actuels doivent répartir 50/50 pour un GPU de 80 Go avec 50 % pour l'inférence et 50 % pour l'entraînement. Et déplacer les poids du mode entraînement au mode inférence peut prendre pas mal de temps.

GPU 80 Go
Moteur d'inférence (50 %)
Moteur d'entraînement (50 %)

Poids du modèle

16 Go

16 Go

Cache KV

24 Go

Activations, gradients, états de l'optimiseur

24 Go

Les versions précédentes d'Unsloth optimisent déjà intelligemment ce qui précède, car nous partageons directement l'espace de poids de vLLM ce qui élimine la double consommation mémoire des poids du modèle. Cela libère par exemple 16 Go d'espace qui peut être utilisé pour augmenter la longueur du contexte ou la vitesse de génération. De plus, nous n'avons pas besoin d'effectuer des mouvements de mémoire, ce qui accélère l'entraînement.

GPU 80 Go
Moteur d'inférence (50 %)
Moteur d'entraînement (50 %)

Poids du modèle

16 Go PARTAGÉS

<<< PARTAGÉ

Cache KV

24 Go + 8 Go= 32 Go

Activations, gradients, états de l'optimiseur

24 Go + 8 Go=32 Go

🦥Unsloth Standby

Mais nous pouvons aller plus loin - nous remarquons d'abord que le RL fait inférence puis entraînement puis inférence puis entraînement, etc.

Cela signifie que l'espace mémoire pour l'inférence et l'entraînement peut en théorie être réutilisé, puisque l'inférence et l'entraînement sont des modes séparés - c'est là que la fonction de mode veille de vLLMarrow-up-right entre en jeu, qui a 2 options :

  1. level = 1 copie les poids vers le CPU et supprime le cache KV

  2. level = 2 supprime les poids et supprime le cache KV

Mais rappelons qu'avec Unsloth nous partageons l'espace mémoire de vLLM pour les poids - cela signifie que nous avons besoin d'une nouvelle façon de supprimer le cache KV, et d'ignorer la suppression des poids, et nous appelons cela Unsloth Standby.

GPU 80 Go
Moteur d'inférence
Moteur d'entraînement

Poids du modèle

16 Go PARTAGÉS

<<< PARTAGÉ

Polyvalent

Espace de 64 Go

Cache KV

Activations, gradients, états de l'optimiseur

Pour activer cela, ajoutez simplement ce qui suit à toutes les exécutions d'entraînement RL / GRPO avant tout import Unsloth :

🧪Expériences de performance

Ici vous découvrirez comment nous avons mesuré l'utilisation de la mémoire et la longueur de contexte pour GRPO. Notez que nous faisons 2 générations par prompt car pour que GRPO fonctionne, nous avons besoin d'au moins 2 générations pour pouvoir calculer la moyenne d'échantillon et la variance. Sans 2 générations, l'écart type d'un seul échantillon est 0. Cela provoque que les avantages qui utilisent ceci : (reward - mean)/std soient indéfinis.

Z=riμ1n(riμ)2Zn=1=r1μ11(r1μ)2=00=undefinedZ=\frac{r_i - \mu}{\sqrt{\frac{1}{n}\sum(r_i-\mu)^2}} \\ Z_{n=1}=\frac{r_1 - \mu}{\sqrt{\frac{1}{1}\sum(r_1-\mu)^2}}=\frac{0}{0}=\text{undefined}

Cela signifie que pour GRPO spécifiquement, une longueur de contexte maximale de 6 144 pour Qwen-3 32B correspond en fait à 6 144 multiplié par 2 générations, soit 12 288 en longueur.

Nous fournissons des expériences pour Llama-3.1 8B sur LoRA (16 bits) et QLoRA (4 bits) ci‑dessous :

Si vous remarquez des différences de temps d'entraînement, ce n'est pas grand‑chose. Dans notre comparaison à armes égales, nous avons observé des ralentissements de temps d'entraînement <1 % ou même des accélérations pouvant être attribuées à la marge d'erreur.

Nous émettons aussi l'hypothèse que des accélérations sont possibles en raison d'une moindre pression mémoire, donc il pourrait y avoir moins de nettoyage mémoire côté allocateur CUDA.

Dans l'image ci‑dessous, vous voyez la différence entre le mode de base et le mode standby sur un seul GPU T4 pour Qwen 3 4B. Nous pouvons pousser la gpu_memory_utilisation de vllm jusqu'à 0,95 sans craindre que cela n'affecte l'entraînement. Cela signifie que vous pouvez adapter des séquences de longueur de contexte plus élevée et traiter plus de séquences. Dans le premier cas, par exemple, nous avons suffisamment de mémoire pour adapter et traiter des séquences de longueur 32K si l'entraînement le permet, alors qu'auparavant, toute entrée plus longue que 2K risquait de ne pas tenir et de provoquer des OOM (out of memory).

Expériences
Configuration
Statut
Utilisation mémoire GPU
Commentaires

standby True

vllm_gpu_util 0.95

num_gen 2

grad_acc_steps 2

S'exécute pendant 40 étapes / 40 minutes

14,5 Gio (défini par vllm_gpu_util)

Suffisant pour tenir dans 32K KVCache avec des chunks de 2-4K ou par exemple 16K KVCache + chunks de 16K

standby True

vllm_gpu_util 0.9

num_gen 2

grad_acc_steps 2

S'exécute 32 étapes en 40 m

13,8 Gio (défini par…)

Approx suffisamment pour tenir dans ~28K KVCache avec des chunks de 2-4K ou par exemple 15K KVCache + chunks de 15K

standby False

vllm_gpu_util 0.9

num_gen 2

grad_acc_steps 2

le modèle se charge mais ne peut pas entraîner car même une taille de batch de 1 ne rentre pas

OOM

standby False

vllm_gpu_util 0.8

num_gen 2

grad_acc_steps 2

le modèle se charge mais ne peut pas entraîner car même une taille de batch de 1 ne rentre pas

OOM

standby False

vllm_gpu_util 0.7

num_gen 2

grad_acc_steps 2

S'entraîne correctement

28 étapes prennent 39 min

~15,1 Gio

toute entrée légèrement plus longue entraînera un OOM sur Colab

standby True

vllm_gpu_util 0.7

num_gen 2

grad_acc_steps 2

S'entraîne correctement

29 étapes prennent 40 min

13 Gio mais la plupart du temps autour de 10-11 Go

Avec la même config, nous économisons 2 Gio soit 15 % de mémoire ici. Peut être plus élevé pour des séquences plus longues

Expériences H100

Modèle
GPU
Longueur Seq
Nombre de générations
Grad Acc Steps

Qwen2.5-14B-Instruct

NVIDIA H100 80GB PCIe

32,768

8

4

Dans nos résultats dépliables ci‑dessous, vous pouvez voir qu'il y a une différence de 9 Gio dans la mémoire de pointe utilisée (notez que 90 % du temps, l'utilisation mémoire du GPU est égale à la mémoire de pointe dans notre cas). Pour mettre les choses en perspective, en utilisant TRL et LoRA nous avons pu affiner seulement un modèle de 8 milliards de paramètres avec une longueur de contexte maximale de 1 024 (32x moins). Toute chose avec une longueur de séquence plus élevée (avec configuration similaire) entraîne l'échec du processus avec un OOM.

chevron-rightCliquez pour les benchmarks Mode Standby Unsloth vs. pas de Standbyhashtag

L'image ci‑dessous montre comment le mode standby se compare à l'entraînement sans standby avec Unsloth. Elle est moyennée sur 3 exécutions pour s'assurer que les métriques ne sont pas bruitées. En fait, si vous zoomez suffisamment, vous verrez que l'activation du standby le rend aussi plus rapide, probablement en raison d'une moindre pression mémoire comme discuté précédemment.

Expériences précédentes sur A100 40GB

Dans nos expériences précédentes sur GPU A100 40GB avec Qwen-2.5-3b-instruct et 8 générations par échantillon, nous avons observé que sans standby, l'entraînement GRPO (modèle chargé en 16 bits, LoRA, seuls les poids entraînables) ne permettait d'ajuster que des longueurs de séquence de 6K. Avec notre fonctionnalité standby, nous avons pu tenir 10K et au‑delà ! Pour comparaison, TRL ne peut vous offrir des longueurs de contexte que jusqu'à 1K tout en conservant la même taille de batch.

🎉Autres optimisations

Nous sélectionnons désormais de meilleurs drapeaux de compilation et réduisons les temps de compilation de 50 % ou plus. Nous avons aussi réussi à patcher dynamiquement toute version de vLLM pour gérer gc.collect mieux pour des raisons de compatibilité descendante, comme inspiré de cette pull request vLLMarrow-up-right. Cela réduit les temps de compilation de 2 minutes à moins de 40 secondes.

Nous avons également optimisé torch.compile les indicateurs et essayé d'activer certains flags - malheureusement combo_kernels et multi_kernel n'a pas pu fonctionner correctement sur vLLM 0.10 et Torch 2.8/2.9 nightly et coordinate_descent_tuning a rendu l'autotuning de tous les noyaux considérablement plus lent. Cela compilait auparavant en moins d'une minute, mais l'activer prenait plus de 13 minutes et plus, avec des gains de performances minimes.

📚Cahiers GRPO

Tous nos notebooks GRPO ont Unsloth Standby activé par défaut et toutes les optimisations ! Voir https://docs.unsloth.ai/get-started/unsloth-notebooksarrow-up-right pour tous nos notebooks GRPO, ou essayez ce qui suit :

Mis à jour

Ce contenu vous a-t-il été utile ?