TRL documentation
LoRA без сожалений
LoRA без сожалений
Недавние исследования команды из Thinking Machines Lab (Schulman et al., 2025) показывают, что LoRA может обеспечить производительность, сопоставимую с полной дообучением, при правильной конфигурации, используя при этом лишь ~67% вычислительных ресурсов. Эти результаты вызывают интерес у пользователей TRL, поскольку их легко реализовать, а они позволяют повысить качество модели при меньшем бюджете.
В этом руководстве приведены простые инструкции по воспроизведению результатов блога в TRL.
Рекомендуется прочитать статью в блоге перед следованием этому руководству или использовать оба ресурса параллельно для достижения наилучших результатов.
Преимущества LoRA перед полной дообучением
Прежде всего, напомним о преимуществах LoRA перед полной дообучением.
LoRA добавляет адаптерные слои поверх базовой модели, которые содержат значительно меньше параметров, чем сама базовая модель. Такой дизайн снижает требования к памяти GPU и обеспечивает более эффективное обучение. Как описано в блог-посте, изначально считалось, что этот подход связан с компромиссом в производительности, хотя тщательная настройка позволяет преодолеть этот компромисс и достичь производительности, сопоставимой с полным дообучением.
Примеры с TRL
Реализуем и обучим адаптеры LoRA в скриптах TRL, опираясь на ключевые выводы из блога. Затем мы рассмотрим каждый вывод в свете результатов TRL.
Добучение с учителем (SFT)
В блоге выполняется SFT на различных моделях и наборах данных из Hub, что мы можем воспроизвести в TRL.
Мы можем интегрировать эти результаты с Python API TRL следующим образом:
from datasets import load_dataset
from peft import LoraConfig
from trl import SFTTrainer, SFTConfig
dataset = load_dataset("open-thoughts/OpenThoughts-114k", split="train")
peft_config = LoraConfig(r=256, lora_alpha=16, target_modules="all-linear")
training_args = SFTConfig(
learning_rate=2e-4,
per_device_train_batch_size=1,
gradient_accumulation_steps=4,
num_train_epochs=1,
report_to=["trackio"],
)
trainer = SFTTrainer(
model="Qwen/Qwen2.5-3B-Instruct",
train_dataset=dataset,
peft_config=peft_config,
args=training_args,
)
trainer.train()
hf jobs uv run \
--flavor a100-large \
--timeout 8h \
--secrets HF_TOKEN \
"https://raw.githubusercontent.com/huggingface/trl/main/trl/scripts/sft.py" \
--model_name_or_path Qwen/Qwen2.5-3B-Instruct \
--dataset_name open-thoughts/OpenThoughts-114k \
--learning_rate 2.0e-5 \
--num_train_epochs 1 \
--packing \
--per_device_train_batch_size 2 \
--gradient_accumulation_steps 16 \
--use_peft \
--lora_r 256 \
--lora_alpha 16 \
--lora_target_modules all-linear \
--output_dir Qwen2.5-3B-OpenThoughts-LoRA \
--report_to trackio \
--push_to_hub
Чтобы использовать Hugging Face Jobs, войдите в Hub (hf auth login) и убедитесь, что у вас положительный баланс кредитов. Подробности см. в документации по Jobs.
uv run "https://raw.githubusercontent.com/huggingface/trl/main/trl/scripts/sft.py" \
--model_name_or_path Qwen/Qwen2.5-3B-Instruct \
--dataset_name open-thoughts/OpenThoughts-114k \
--learning_rate 2.0e-5 \
--num_train_epochs 1 \
--packing \
--per_device_train_batch_size 2 \
--gradient_accumulation_steps 16 \
--eval_strategy no \
--use_peft \
--lora_r 256 \
--lora_alpha 16 \
--lora_target_modules all-linear \
--output_dir Qwen2.5-3B-OpenThoughts-LoRA \
--report_to trackio \
--push_to_hub
Для запуска скрипта локально необходимо установить uv. Подробнее см. в документации uv.
После начала обучения вы можете отслеживать прогресс в Trackio, который зафиксирует URL.
Укрепляющее обучение (GRPO)
В блоге выполняется GRPO на различных моделях и наборах данных из Hub, и, как и в предыдущем случае, мы можем воспроизвести результаты в TRL.
| Модель | Набор данных |
|---|---|
| Llama-3.1-8B-Base | GSM8k |
| Llama-3.1-8B-Base | DeepMath-103K |
| Qwen3-8b-base | DeepMath-103K |
Для обучения с подкреплением в блоге используется задача математического рассуждения, которую мы можем воспроизвести в виде функции на Python.
Мы можем реализовать эти рекомендации с помощью Python API TRL следующим образом:
from datasets import load_dataset
from peft import LoraConfig
from trl import GRPOConfig, GRPOTrainer
from trl.rewards import reasoning_accuracy_reward
dataset = load_dataset("HuggingFaceH4/OpenR1-Math-220k-default-verified", split="train")
peft_config = LoraConfig(
r=1,
lora_alpha=32,
target_modules="all-linear"
)
training_args = GRPOConfig(
learning_rate=5e-5,
per_device_train_batch_size=1,
gradient_accumulation_steps=4,
num_train_epochs=1,
num_generations=8,
generation_batch_size=8,
report_to=["trackio"],
)
trainer = GRPOTrainer(
model="Qwen/Qwen3-0.6B",
reward_funcs=reasoning_accuracy_reward,
args=training_args,
train_dataset=dataset,
peft_config=peft_config,
)
trainer.train()
В этом фрагменте пропущена функция вознаграждения, определённая выше, чтобы пример оставался лаконичным.
hf jobs uv run \
--flavor a100-large \
--timeout 4h \
--secrets HF_TOKEN \
--env PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True \
"https://huggingface.co/datasets/burtenshaw/lora-without-regrets/resolve/main/grpo.py" \
--model_name_or_path Qwen/Qwen3-0.6B \
--dataset_name HuggingFaceH4/OpenR1-Math-220k-default-verified \
--output_dir grpo-full-qwen3-0.6b \
--learning_rate 1.0e-6 \
--lr_scheduler_type cosine \
--warmup_steps 0.0 \
--max_grad_norm 1.0 \
--beta 0.0 \
--max_completion_length 4096 \
--num_generations 16 \
--generation_batch_size 16 \
--gradient_accumulation_steps 8 \
--per_device_train_batch_size 1 \
--num_train_epochs 1 \
--lora_r 1 \
--lora_alpha 32 \
--lora_dropout 0.0 \
--lora_target_modules all-linear \
--vllm_mode colocate \
--save_strategy steps \
--save_steps 50 \
--save_total_limit 1 \
--logging_steps 1 \
--max_steps 200 \
--report_to trackioЧтобы использовать Hugging Face Jobs, войдите в Hub (hf auth login) и убедитесь, что у вас положительный баланс кредитов. Подробности см. в документации по Jobs.
uv run "https://huggingface.co/datasets/burtenshaw/lora-without-regrets/resolve/main/grpo.py" \
--model_name_or_path Qwen/Qwen3-0.6B \
--dataset_name HuggingFaceH4/OpenR1-Math-220k-default-verified \
--output_dir grpo-full-qwen3-0.6b \
--learning_rate 1.0e-6 \
--lr_scheduler_type cosine \
--warmup_steps 0.0 \
--max_grad_norm 1.0 \
--beta 0.0 \
--max_completion_length 4096 \
--num_generations 16 \
--generation_batch_size 16 \
--gradient_accumulation_steps 8 \
--per_device_train_batch_size 1 \
--num_train_epochs 1 \
--lora_r 1 \
--lora_alpha 32 \
--lora_dropout 0.0 \
--lora_target_modules all-linear \
--vllm_mode colocate \
--save_strategy steps \
--save_steps 50 \
--save_total_limit 1 \
--logging_steps 1 \
--max_steps 200 \
--report_to trackioДля запуска скрипта локально необходимо установить uv. Подробнее см. в документации uv.
Скрипт обучения с подкреплением с GRPO реализован как пользовательский скрипт в TRL, который использует функцию вознаграждения, показанную выше. Вы можете ознакомиться с ним по адресу grpo.py — Лучшие практики обучения с подкреплением с LoRA
Ключевые результаты оптимизации LoRA
Авторы рекомендуют применять LoRA ко всем матрицам весов, а не ограничиваться слоями внимания, поскольку увеличение ранга не компенсирует это ограничение. В TRL это можно настроить с помощью --lora_target_modules all-linear, чтобы применить LoRA ко всем матрицам весов.
Нам удалось воспроизвести результаты из блога с использованием TRL и модели SmolLM3. Мы обучали модель в течение 500 шагов на наборе данных Math 220k с использованием функции вознаграждения и конфигурации, описанных выше. Как видно на рисунке ниже, средняя кривая вознаграждения при обучении модели LoRA совпадает с кривой полного дообучения.

И, что самое главное, модель LoRA использует значительно меньше памяти, чем модель полного дообучения, как мы можем увидеть на рисунке ниже.

Ниже приведены параметры, которые мы использовали для обучения указанных выше моделей
| Параметр | LoRA | Полная FT |
|---|---|---|
--model_name_or_path | HuggingFaceTB/SmolLM3-3B | HuggingFaceTB/SmolLM3-3B |
--dataset_name | HuggingFaceH4/OpenR1-Math-220k-default-verified | HuggingFaceH4/OpenR1-Math-220k-default-verified |
--learning_rate | 1.0e-5 | 1.0e-6 |
--max_completion_length | 4096 | 4096 |
--lora_r | 1 | - |
--lora_alpha | 32 | - |
--lora_dropout | 0.0 | - |
--lora_target_modules | all-linear | - |
Разберём ключевые выводы из блога и то, как нам удалось их воспроизвести.
1. LoRA показывает лучшие результаты при применении ко всем матрицам весов
Авторы рекомендуют применять LoRA ко всем матрицам весов, а не ограничиваться слоями внимания, поскольку увеличение ранга не компенсирует это ограничение.

LoRA, применяемый только к слоям внимания, показывает худшие результаты даже при использовании более высокого ранга для соответствия количеству параметров. В TRL это можно настроить с помощью --lora_target_modules all-linear, чтобы применить LoRA ко всем матрицам весов. На Python это делается так:
from peft import LoraConfig
peft_config = LoraConfig(target_modules="all-linear") 2. Адаптеру необходима достаточная ёмкость для обучения на наборе данных
В блоге рекомендуется использовать достаточный ранг LoRA для обучения на наборе данных. Ранг определяет количество обучаемых параметров в адаптере LoRA. Поэтому «для наборов данных, превышающих ёмкость LoRA, LoRA уступает FullFT».

В скрипте TRL можно использовать --lora_r, чтобы задать ранг и подобрать его в зависимости от задачи и набора данных, на котором выполняется обучение. В блоге рекомендуются следующие значения ранга в зависимости от задачи и размера набора данных:
Задачи обучения с подкреплением обычно требуют меньшей ёмкости, поэтому можно использовать меньшие ранги LoRA. Это связано с тем, что алгоритмы градиента политики извлекают примерно ~1 бит информации на эпизод, что требует минимальной ёмкости параметров.
В блоге определяется идеальный размер датасета для LoRA, при котором результаты сопоставимы с полным дообучением, как «Масштаб пост-тренинга». Мы можем использовать его для определения рекомендуемого ранга для LoRA в задачах SFT и RL:
| Тип задачи | Размер набора данных | Рекомендуемый ранг |
|---|---|---|
| SFT | Масштаб пост-обучения | 256 |
| RL | Любой размер | 1-32 |
3. «FullFT и LoRA с высоким рангом имеют схожие кривые обучения»
Контринтуитивно, но в блоге рекомендуется использовать более высокую скорость обучения, чем при полной дообучении. В таблице выше мы использовали 1.0e-5 для LoRA и 1.0e-6 для полной дообучения. В скрипте TRL мы могли бы использовать --learning_rate для установки скорости обучения. Масштабирование в LoRA делает оптимальную скорость обучения приблизительно независимой от ранга.

4. «В некоторых сценариях LoRA менее устойчива к большим размерам батчей, чем полная дообучение.»
В блоге рекомендуется использовать эффективный размер батча < 32, поскольку авторы обнаружили, что LoRA менее устойчива к большим размерам батча. Это не удалось компенсировать за счёт увеличения ранга LoRA. В скрипте TRL размер батча можно задать с помощью параметров --per_device_train_batch_size и --gradient_accumulation_steps.

Выводы
С помощью TRL вы можете эффективно реализовать адаптеры LoRA, сопоставимые по производительности с полным дообучением, применяя ключевые принципы (целевое воздействие на все матрицы весов, выбор подходящего ранга, а также управление размером пакета и скоростью обучения) без высоких вычислительных затрат, связанных с FullFT.
Цитирование
@article{schulman2025lora,
title = {{LoRA Without Regret}},
author = {John Schulman and Thinking Machines Lab},
year = 2025,
journal = {Thinking Machines Lab: Connectionism},
doi = {10.64434/tml.20250929},
note = {https://thinkingmachines.ai/blog/lora/}
}