LoRA, QLoRA или полное дообучение: сколько нужно видеопамяти?
Чтобы дообучить LLM с LoRA или QLoRA на GPU-сервере, вы обучаете небольшой адаптер поверх замороженной модели с помощью Hugging Face TRL и PEFT. LoRA оставляет базовую модель в BF16 и обучает низкоранговые матрицы, добавленные к её слоям; QLoRA делает то же самое, но с базовой моделью, загруженной в 4-битном формате через bitsandbytes. Полное дообучение, напротив, обновляет все веса и хранит градиенты и состояния оптимизатора для каждого из них. Правило подбора CryptGPU, которое считает полезными 92% памяти GPU и включает запас на активации, закладывает около 3 ГБ на миллиард параметров для LoRA, 0,75 ГБ для QLoRA и 18 ГБ для полного дообучения.
Нужный объём памяти и самый дешёвый сервер, который его вмещает, с ценой в месяц:
| Модель | QLoRA | LoRA (BF16) | Полное дообучение |
|---|---|---|---|
| 8B | 6 ГБ: 1× RTX 5080, 199 $ | 24 ГБ: 1× RTX 5090, 329 $ | 144 ГБ: 1× MI355X, 1 409 $ |
| 32B | 24 ГБ: 1× RTX 5090, 329 $ | 96 ГБ: 4× RTX 5090, 1 316 $ | 576 ГБ: 4× MI355X, 5 636 $ |
| 70B | 53 ГБ: 2× RTX 5090, 658 $ | 210 ГБ: 1× MI355X, 1 409 $ | 1 260 ГБ: 8× MI355X, 11 272 $ |
- Начните с QLoRA, чтобы проверить данные и настройки на самом маленьком сервере, а затем переходите на LoRA в BF16, если этого требует качество.
- С одним GPU проще. На нескольких GPU модель приходится разделять или шардировать (см. ниже), а карты GeForce обмениваются данными через PCIe. Для дообучения модели 70B с QLoRA (53 ГБ) хватит и одного H100 (80 ГБ, 1 779 $ в месяц) или одного RTX PRO 6000 (96 ГБ, 959 $), а для дообучения модели 32B с LoRA (96 ГБ) — одного H200 (141 ГБ, 2 279 $).
- MI355X использует ROCm. PyTorch, bitsandbytes, TRL и PEFT на нём работают (см. ниже). Если ограничиться NVIDIA, самый дешёвый вариант для дообучения модели 70B с LoRA — 4× RTX PRO 6000, 3 836 $.
Подбор GPU под модель покажет другие размеры, а статья Сколько видеопамяти нужно вашей модели? объясняет это правило. На сервере с помесячной арендой ваши данные и окружение сохраняются между запусками; для одного короткого запуска почасовое облако может обойтись дешевле. См. GPU-серверы для дообучения.
Подготовьте окружение
Если при заказе вы выбрали на шаге «ПО» «Набор для дообучения» (Axolotl, TRL и PEFT), он уже установлен; ниже описана установка вручную. Установите стек в отдельное окружение Python и храните загрузки на NVMe-диске для данных (см. Хранилище и данные). С параметром --torch-backend=auto uv сам определяет драйвер GPU и устанавливает подходящую сборку PyTorch: для CUDA на NVIDIA и для ROCm на AMD (руководство uv по PyTorch).
curl -LsSf https://astral.sh/uv/install.sh | sh # затем откройте новую оболочку, чтобы uv появился в PATH
uv venv --python 3.12 --seed --managed-python ~/ft-env
source ~/ft-env/bin/activate
uv pip install torch "trl[peft]" bitsandbytes --torch-backend=auto
export HF_HOME=/data/hf # модели, датасеты и ваш токен на NVMe
export HF_HUB_DISABLE_TELEMETRY=1 # без телеметрии использования от библиотек Hugging Face
mkdir -p /data/ft # датасет, скрипты и результаты
hf auth login # только для моделей с ограниченным доступом, например Llama
trl env # версии библиотек и найденные GPU
Чтобы эти переменные сохранялись, добавьте обе строки export в ~/.bashrc. На MI355X проверьте, что python -c "import torch; print(torch.cuda.get_arch_list())" выводит gfx950, или работайте в контейнере rocm/pytorch (см. Контейнеры). bitsandbytes публикует wheel-пакеты для ROCm с поддержкой gfx950, а руководство AMD по дообучению использует TRL и PEFT на GPU Instinct.
Подготовьте датасет диалогов в JSONL
SFTTrainer из TRL читает каждый диалог как список messages из записей с полями role и content и сам применяет шаблон чата модели (форматы датасетов TRL). Запишите диалоги в /data/ft/train.jsonl, по одному на строку:
{"messages": [{"role": "system", "content": "You answer customer emails briefly."}, {"role": "user", "content": "My order arrived damaged."}, {"role": "assistant", "content": "Sorry about that. Send a photo and we will ship a replacement."}]}
{"messages": [{"role": "user", "content": "Can I change the delivery address?"}, {"role": "assistant", "content": "Yes, until the order ships. Reply with the new address."}]}
Используйте примеры, похожие на запросы, которые будет получать модель, удалите дубликаты и не включайте данные, на которых вы не имеете права обучать модель. Скрипт ниже откладывает 5% строк для оценки. Выведите один пример через шаблон чата, чтобы проверить роли и специальные токены:
python - <<'EOF'
from datasets import load_dataset
from transformers import AutoTokenizer
tok = AutoTokenizer.from_pretrained("Qwen/Qwen3-8B")
row = load_dataset("json", data_files="/data/ft/train.jsonl", split="train")[0]
print(tok.apply_chat_template(row["messages"], tokenize=False))
EOFВариант А: QLoRA или LoRA с TRL SFTTrainer
Этот скрипт дообучает Qwen3-8B на одном GPU с актуальным API TRL (TRL 1.16, PEFT 0.21). QLORA = True загружает базовую модель в 4-битном формате NF4 через bitsandbytes. Для Llama 3.1 примите её лицензию на Hugging Face, выполните hf auth login и измените MODEL.
# /data/ft/train_sft.py: LoRA или QLoRA с TRL
import torch
from datasets import load_dataset
from peft import LoraConfig
from transformers import BitsAndBytesConfig
from trl import SFTConfig, SFTTrainer
MODEL = "Qwen/Qwen3-8B"
QLORA = True # False: LoRA на модели в BF16
OUT = "/data/ft/qwen3-8b-lora"
data = load_dataset("json", data_files="/data/ft/train.jsonl", split="train")
data = data.train_test_split(test_size=0.05, seed=42)
bnb = BitsAndBytesConfig(
load_in_4bit=True,
bnb_4bit_quant_type="nf4",
bnb_4bit_use_double_quant=True,
bnb_4bit_compute_dtype=torch.bfloat16,
) if QLORA else None
lora = LoraConfig(r=16, lora_alpha=32, lora_dropout=0.05,
target_modules="all-linear", task_type="CAUSAL_LM")
args = SFTConfig(
output_dir=OUT,
model_init_kwargs={"dtype": torch.bfloat16},
max_length=2048,
assistant_only_loss=True,
per_device_train_batch_size=2,
gradient_accumulation_steps=8,
per_device_eval_batch_size=2,
num_train_epochs=2,
learning_rate=2e-4,
lr_scheduler_type="cosine",
warmup_steps=0.03, # значения меньше 1 — доля от всего запуска
logging_steps=10,
eval_strategy="steps",
eval_steps=0.1, # оценка и сохранение каждые 10% запуска
save_steps=0.1,
save_total_limit=3,
)
trainer = SFTTrainer(
model=MODEL,
args=args,
train_dataset=data["train"],
eval_dataset=data["test"],
quantization_config=bnb,
peft_config=lora,
)
trainer.train()
trainer.save_model(OUT) # адаптер LoRA
tmux new -s ft # запуск не прервётся при обрыве SSH-сессии
source ~/ft-env/bin/activate
cd /data/ft
CUDA_VISIBLE_DEVICES=0 python train_sft.py 2>&1 | tee train.log
Эти значения — отправная точка:
target_modules="all-linear"добавляет адаптеры ко всем линейным слоям, кроме выходного, как рекомендует руководство TRL LoRA Without Regret;r=16иlora_alpha=32— значения по умолчанию в CLI TRL.learning_rate=2e-4— значение, которое TRL рекомендует для SFT с LoRA: в десять раз больше значения по умолчанию для полного дообучения (интеграция с PEFT). Эффективный батч, 2 × 8 = 16 последовательностей, остаётся ниже порога в 32, который TRL советует для LoRA. Руководство Unsloth предлагает от одной до трёх эпох.model_init_kwargsзагружает веса в BF16; без этого TRL загружает их в float32. Чекпоинтинг градиентов и обучение в BF16 включены по умолчанию.assistant_only_loss=Trueсчитает потери (loss) только по ответам ассистента. Для этого в шаблоне чата нужны маркеры генерации; TRL добавляет их для известных семейств моделей, таких как Qwen3 и Llama 3 (SFT Trainer).max_lengthобрезает более длинные диалоги; по умолчанию в TRL — 1 024 токена.
На сервере с несколькими GPU CUDA_VISIBLE_DEVICES=0 оставляет запуск на одном GPU; при обычном запуске через python модель распределится по всем GPU.
Вариант Б: Unsloth на одном GPU
Unsloth заменяет части модели собственными ядрами на Triton, чтобы обучение на одном GPU требовало меньше памяти и времени, и работает с тем же SFTTrainer. Установите его в отдельное окружение:
uv venv ~/unsloth_env --python 3.13
source ~/unsloth_env/bin/activate
uv pip install unsloth --torch-backend=auto
# /data/ft/train_unsloth.py
from unsloth import FastLanguageModel
from datasets import load_dataset
from trl import SFTConfig, SFTTrainer
model, tokenizer = FastLanguageModel.from_pretrained(
model_name="Qwen/Qwen3-8B",
max_seq_length=2048,
load_in_4bit=True, # False: LoRA на 16-битной базовой модели
)
model = FastLanguageModel.get_peft_model(
model, r=16, lora_alpha=16, lora_dropout=0,
target_modules=["q_proj", "k_proj", "v_proj", "o_proj", "gate_proj", "up_proj", "down_proj"],
use_gradient_checkpointing="unsloth",
)
data = load_dataset("json", data_files="/data/ft/train.jsonl", split="train")
data = data.map(lambda x: {"text": tokenizer.apply_chat_template(x["messages"], tokenize=False)},
remove_columns=["messages"])
trainer = SFTTrainer(
model=model,
processing_class=tokenizer,
train_dataset=data,
args=SFTConfig(output_dir="/data/ft/unsloth", max_length=2048,
per_device_train_batch_size=2, gradient_accumulation_steps=8,
learning_rate=2e-4, num_train_epochs=1),
)
trainer.train()
model.save_pretrained("/data/ft/unsloth-lora")
tokenizer.save_pretrained("/data/ft/unsloth-lora")
Значения взяты из руководства Unsloth по гиперпараметрам (ранг 16 или 32, alpha равен рангу или вдвое больше, скорость обучения 2e-4, эффективный батч 16). model.save_pretrained_merged("/data/ft/unsloth-merged", tokenizer, save_method="merged_16bit") сохраняет объединённую 16-битную модель для vLLM. В руководстве Unsloth для AMD указана серия MI350 (gfx950) под Linux, с отдельными шагами установки.
Обучение на 2, 4 или 8 GPU
Скрипт TRL работает и на нескольких GPU (подготовьте сервер, как описано в разделе Multi-GPU). Когда модель помещается на один GPU, параллелизм данных (DDP) даёт каждому GPU полную копию модели и собственные батчи; запустите по одному процессу на каждый GPU:
accelerate launch --num_processes 4 train_sft.py
torchrun --standalone --nproc-per-node=4 train_sft.py # или через torchrun
Эффективный батч становится равен батчу на GPU × числу GPU × числу шагов накопления (распределённое обучение в TRL): на четырёх GPU задайте gradient_accumulation_steps=2, чтобы сохранить 16.
Если модель не помещается на один GPU (LoRA для модели 70B в BF16, полное дообучение), шардируйте её с помощью FSDP2, встроенного в PyTorch и поддерживающего LoRA из коробки, или DeepSpeed ZeRO-3. TRL публикует шаблоны конфигурации Accelerate для обоих вариантов. Сначала задайте QLORA = False: для QLoRA с FSDP нужны дополнительные настройки из руководства PEFT по FSDP. Для полного дообучения также удалите peft_config и верните скорость обучения TRL по умолчанию — 2e-5.
curl -LO https://raw.githubusercontent.com/huggingface/trl/main/examples/accelerate_configs/fsdp2.yaml
accelerate launch --config_file fsdp2.yaml --num_processes 8 train_sft.py
uv pip install deepspeed # собирает часть своих операций при первом использовании
curl -LO https://raw.githubusercontent.com/huggingface/trl/main/examples/accelerate_configs/deepspeed_zero3.yaml
accelerate launch --config_file deepspeed_zero3.yaml --num_processes 8 train_sft.py
Запуск QLoRA, которому нужны два GPU, например 70B на 2× RTX 5090, можно вместо этого начать обычной командой python train_sft.py: Transformers распределит слои по обоим GPU, и они будут работать по очереди.
Мониторинг и возобновление обучения
tmux attach -t ft # вернуться в сессию обучения
nvidia-smi --query-gpu=index,utilization.gpu,memory.used,memory.total --format=csv -l 5
watch -n 2 amd-smi monitor # AMD MI355X
tail -f /data/ft/train.log # loss каждые 10 шагов, eval_loss каждые 10% запуска
Потери на обучении (training loss) должны снижаться, а вместе с ними и потери на валидации (eval loss). Если потери на валидации растут, а на обучении продолжают снижаться, модель заучивает свои данные: возьмите более ранний чекпоинт или обучайте меньше эпох. Если запуск останавливается с ошибкой нехватки памяти, уменьшите per_device_train_batch_size вдвое и удвойте gradient_accumulation_steps, сократите max_length или перейдите на QLoRA.
При каждом сохранении трейнер записывает в выходной каталог папку checkpoint-N, где N — номер шага, а save_total_limit=3 хранит три последние. После прерывания замените вызов на trainer.train(resume_from_checkpoint=True), чтобы продолжить с последнего чекпоинта.
Объедините адаптер с моделью или запустите его в vLLM
Адаптер можно запустить в vLLM вместе с базовой моделью, на которой он обучался, или влить в неё и получить самостоятельный чекпоинт. Запускайте vLLM из отдельного окружения, с API-ключом и через SSH-туннель, как описано в разделе Инференс-сервер.
Запуск с адаптером
vllm serve Qwen/Qwen3-8B --host 127.0.0.1 --port 8000 \
--enable-lora --lora-modules support=/data/ft/qwen3-8b-lora --max-lora-rank 16
Клиенты выбирают адаптер через "model": "support". --max-lora-rank должен быть не меньше r адаптера; по умолчанию он равен 16 (адаптеры LoRA в vLLM).
Объединение с базовой моделью
python - <<'EOF'
import torch
from peft import PeftModel
from transformers import AutoModelForCausalLM, AutoTokenizer
base = AutoModelForCausalLM.from_pretrained("Qwen/Qwen3-8B", dtype=torch.bfloat16)
model = PeftModel.from_pretrained(base, "/data/ft/qwen3-8b-lora").merge_and_unload()
model.save_pretrained("/data/ft/qwen3-8b-merged")
AutoTokenizer.from_pretrained("Qwen/Qwen3-8B").save_pretrained("/data/ft/qwen3-8b-merged")
EOF
vllm serve /data/ft/qwen3-8b-merged --host 127.0.0.1 --port 8000
merge_and_unload() встраивает адаптер в веса, поэтому при инференсе нет накладных расходов на адаптер (руководство PEFT по LoRA). Для объединения базовая модель загружается в BF16 — около 2 байт памяти на параметр, — а на выходе получается обычный чекпоинт, который vLLM может обслуживать и в FP8.
Сделайте резервную копию чекпоинтов и проверьте лицензию
Если аренда не продлена, сервер останавливается в конце оплаченного месяца, а его диски очищаются. После каждого запуска и задолго до окончания срока копируйте с сервера адаптеры, чекпоинты, датасет и скрипты и проверяйте копию, как описано в разделе Хранилище и данные:
rclone copy /data/ft s3remote:my-bucket/ft --progress
rclone check /data/ft s3remote:my-bucket/ft
На дообученную модель по-прежнему распространяется лицензия её базовой модели. Qwen3-8B выпущена под лицензией Apache 2.0. Модели Llama 3.1 распространяются с ограниченным доступом, а их лицензия сообщества среди прочих условий требует, чтобы название распространяемой дообученной модели начиналось с «Llama» и чтобы модель сопровождалась надписью «Built with Llama». Прежде чем публиковать или продавать результат, прочитайте лицензию своей базовой модели и условия использования своих данных.
Нужна помощь с этим руководством?
Расскажите нам о своём GPU, командах, которые вы выполнили, и результате, который получили, через форму обратной связи.
