Модуль 11. Своя open-weights модель: QLoRA в DataSphere

Цель модуля: дообучить открытую модель (Qwen, Llama) в ноутбуке DataSphere и забрать результат с собой: настроить GPU-конфигурацию, собрать QLoRA-пайплайн на transformers + PEFT, сохранить адаптер в Object Storage, проверить модель инференсом и честно спланировать деплой и бюджет в юнитах.

Перед началом: модуль 6 (LoRA/QLoRA и VRAM-бюджеты), модуль 7 (гиперпараметры), модуль 9 (DataSphere, квоты, «ноутбук выключай»). Инструменты: DataSphere-ноутбук с GPU. Нужен аккаунт YC с грантом. Трудоёмкость: ~2.5 ч руками + время обучения (зависит от конфигурации).

🔥 URI есть — весов нет

Модуль 10 закончился победой: дообученная модель отвечает изящнее базы. Потом приходит вопрос от безопасности: «А где лежат веса? Мы хотим модель в закрытом контуре». И вопрос от продукта: «А можно Qwen? У него русский лучше в нашем домене». На оба ответ один: в AI Studio — никак. Дообученный YandexGPT существует только как URI в облаке Яндекса.

Open-weights мир отвечает иначе: скачиваете Qwen или Llama с HuggingFace, дообучаете QLoRA-адаптер (модуль 6) на своей GPU-конфигурации, сохраняете результат в свой Object Storage. Адаптер — десятки мегабайт — применяется к базовой модели где угодно: в другом облаке, на своём сервере, в закрытом контуре. Цена — ответственность за конфигурацию, память и деплой ваша.

📖 11.1. Когда этот путь ваш

Честный список причин идти в DataSphere за open-weights:

  1. Нужна другая база. Qwen, Llama, более свежая/меньшая/большая модель, чем YandexGPT Lite. В AI Studio выбор фиксирован.
  2. Контроль и переносимость. Закрытый контур, on-premise завтра, смена облака без переобучения. Адаптер переносится везде, где есть база с HF.
  3. Свобода и исследование. Свои гиперпараметры, свои слои для LoRA, ранняя остановка по валидации (модуль 7), сравнение баз методично.

И цена: вы сами выбираете конфигурацию и платите юниты за её время (модуль 9), сами ловите OOM, сами деплоите. Если задача решается тюнингом модуля 10 — она решается дешевле.

📖 11.2. Ноутбук и конфигурация: gt4.1 или g1.1/g2.1

Создайте проект в DataSphere и ноутбук в нём, добавьте секреты FOLDER_ID/API_KEY из модуля 9 (пригодятся для Object Storage) и выберите конфигурацию вычислений — главное денежное решение модуля.

Модель (QLoRA)КонфигурацияКомментарий
3–4B (Qwen2.5-3B)gt4.1 (T4, 16 ГБ)дешёвый вход; наш путь по умолчанию
7–8B (Llama-3.1-8B)g1.1 (V100, 32 ГБ)16 ГБ для 7B — «впритык» (модуль 6)
7–8B + длинный контекстg2.1 (A100, 80 ГБ)быстро и свободно, но дороже всех

Полный список конфигураций — в документации: https://yandex.cloud/ru/docs/datasphere/concepts/configurations. Квоты каталога проверьте до старта (модуль 9): на GPU-конфигурации по умолчанию часто 0–1.

И правило модуля 9, повторённое осознанно: закончили обучение — останавливайте конфигурацию; индикатор проверяется перед каждым чаепитием.

📖 11.3. Скелет QLoRA-ноутбука

Каркас из семи ячеек. Код не исполняется в тексте модуля — соберите его в DataSphere; теория шагов — модуль 6 (4 бит и ΔW=A·B) и модуль 7.

python
# Ячейка 1 — зависимости (экосистема HuggingFace для QLoRA)
%pip install -q -U transformers datasets peft trl accelerate bitsandbytes
python
# Ячейка 2 — базовая модель в 4 бит: QLoRA из модуля 6
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer, BitsAndBytesConfig

MODEL_ID = "Qwen/Qwen2.5-3B-Instruct"   # под gt4.1; для 7B см. таблицу выше

bnb = BitsAndBytesConfig(
    load_in_4bit=True,                  # базовые веса квантуются в NF4
    bnb_4bit_quant_type="nf4",
    bnb_4bit_compute_dtype=torch.bfloat16,
)
tokenizer = AutoTokenizer.from_pretrained(MODEL_ID)
model = AutoModelForCausalLM.from_pretrained(
    MODEL_ID, quantization_config=bnb, device_map="auto",
)
python
# Ячейка 3 — LoRA-конфиг: учим только дельту A·B, базу не трогаем
from peft import LoraConfig, get_peft_model

lora_cfg = LoraConfig(
    r=16, lora_alpha=32,                # ранг и масштаб (модули 6–7)
    target_modules=["q_proj", "k_proj", "v_proj", "o_proj"],
    lora_dropout=0.05,
    task_type="CAUSAL_LM",
)
model = get_peft_model(model, lora_cfg)
model.print_trainable_parameters()      # ожидаем доли процента всех весов
python
# Ячейка 4 — датасет + chat template (модули 3 и 5: формат решает!)
from datasets import load_dataset

ds = load_dataset("json", data_files="train.jsonl", split="train")

def to_chat(example):
    msg = [{"role": "user", "content": example["request"]},
           {"role": "assistant", "content": example["response"]}]
    return {"text": tokenizer.apply_chat_template(msg, tokenize=False)}

ds = ds.map(to_chat)
print(ds[0]["text"])   # ОБЯЗАТЕЛЬНО посмотрите глазами: спецтокены на месте?
python
# Ячейка 5 — SFTTrainer: те же гиперпараметры, что в модуле 7
from trl import SFTConfig, SFTTrainer

cfg = SFTConfig(
    output_dir="checkpoints",
    per_device_train_batch_size=1,       # OOM? первым делом уменьшить батч
    gradient_accumulation_steps=16,      # эффективный батч = 1 × 16
    gradient_checkpointing=True,         # память в обмен на скорость
    max_seq_length=512,                  # OOM? резать длину последовательностей
    num_train_epochs=2,                  # в LLM epochs = 1–3, не 100 (модуль 7)
    learning_rate=2e-4,                  # типичный диапазон LoRA: 1e-4…2e-4
    logging_steps=10, save_steps=100,
)
trainer = SFTTrainer(model=model, args=cfg,
                     train_dataset=ds, processing_class=tokenizer)
trainer.train()                          # loss виден прямо в выводе ячейки
python
# Ячейка 6 — сохраняем адаптер и выгружаем в Object Storage (S3-совместимый)
import os
import boto3

model.save_pretrained("adapter")         # адаптер — мегабайты, не вся модель

s3 = boto3.client(
    "s3",
    endpoint_url="https://storage.yandexcloud.net",
    aws_access_key_id=os.environ["AWS_ACCESS_KEY_ID"],        # статический
    aws_secret_access_key=os.environ["AWS_SECRET_ACCESS_KEY"],# ключ бакета
)
s3.upload_file("adapter/adapter_model.safetensors",
               "llm-course-bucket", "adapters/style-v1/adapter_model.safetensors")
python
# Ячейка 7 — инференс прямо в ноутбуке: проверка до всякого деплоя
messages = [{"role": "user", "content": "Купил курс, не зашло. Как вернуть деньги?"}]
text = tokenizer.apply_chat_template(messages, tokenize=False,
                                     add_generation_prompt=True)
inputs = tokenizer(text, return_tensors="pt").to(model.device)
out = model.generate(**inputs, max_new_tokens=200)
print(tokenizer.decode(out[0][inputs["input_ids"].shape[1]:],
                       skip_special_tokens=True))

Почему save_steps=100: чекпойнты позволяют откатиться к середине обучения, если финал переобучился (модуль 7). Выгрузили адаптер в бакет — ноутбук можно выключать: результат переживёт проект. Пример пайплайна от Yandex Cloud: https://github.com/yandex-cloud-examples/yc-foundation-models-datasphere-tuning.

📖 11.4. Альтернативы: LLaMA-Factory и Unsloth

Скелет из 11.3 — «ручной» путь: максимум контроля; есть и готовые обёртки:

  • LLaMA-Factory (https://github.com/hiyouga/LLaMA-Factory) — веб-интерфейс над тем же стеком: датасет, модель и LoRA-параметры выбираются в UI; хорош для серии однотипных экспериментов.
  • Unsloth (https://github.com/unslothai/unsloth) — оптимизированная реализация QLoRA: быстрее и экономнее по памяти на тех же задачах.

Рекомендация курса: первый прогон сделайте руками (11.3) — поняв BitsAndBytes → LoRA → SFTTrainer, вы будете видеть, что делает обёртка.

Отдельный вариант «вообще без облака» — на случай «нет гранта» или «закрытый контур». Обучение QLoRA идёт на своей машине (или в Colab), а инференс дообученной модели удобно поднять на Ollama: ollama pull qwen2.5:3b (≈ 2 ГБ), и на localhost:11434 появляется OpenAI-совместимый эндпоинт — тот же контракт, что у AI Studio, ваш клиент не меняется. В одном рабочем сервисе-оркестраторе так и живёт локальный контур целиком: Ollama зафиксированной версии в docker-compose, модель в docker-volume, никаких ключей и счетов. Честный минус — скорость: без GPU инференс идёт на CPU (в Docker на macOS GPU недоступен), 3B-модель терпимо, 7B «в разы медленнее». И практическая деталь: версию инференс-сервера фиксируют — API Ollama дрейфует между версиями (формат даже /api/tokenize менялся), и код, работающий сегодня, после обновления может сломаться. Тот же принцип immutable-версий, что в модуле 12, только для софта, а не для модели.

📖 11.5. Типовые ошибки и их симптомы

  • OOM (CUDA out of memory). Падает на первом же длинном примере. Лестница спасения: батч → 1; max_seq_length вниз (512 → 256); проверить gradient_checkpointing=True; модель поменьше или конфигурация побольше. Батч 1 + аккумуляция 16 — норма, а не поражение.
  • Забытый GPU-рантайм. Ноутбук на CPU-конфигурации: обучение «зависает» — шаги идут, но невыносимо медленно; проверьте селектор конфигурации. Обратная беда из модуля 9: обучение закончилось, а конфигурация работает — юниты капают. Остановите.
  • Кривой chat template. Пропустили apply_chat_template или взяли датасет с чужой разметкой диалога — модель учит неправильные спецтокены: loss падает, а в бою (ячейка 7) ответы бессвязные, в текст лезет <|im_start|>. Лечение: ячейка 4 печатает пример глазами.
  • Проверка вслепую. Только train loss — как в модуле 7: добавьте валидацию, финал оценивайте golden set-ом из модуля 8 («loss упал» ≠ «стало лучше»).
  • Чекпойнт только в ноутбуке. Не сохранили в Object Storage, проект почистили — работа пропала. Ячейка 6 — не опция.

📖 11.6. Деплой честно и бюджет в юнитах

Деплой. В Yandex Cloud нет managed-инференса для собственных весов — сервиса «загрузите адаптер — получите API» не существует (в отличие от URI из модуля 10). Варианты, оба рабочие:

  1. ВМ с GPU и vLLM/Ollama. Виртуальная машина с GPU, база + адаптер — свой API-сервер в вашем облаке.
  2. Перенос адаптера. Адаптер — десятки мегабайт, база открыта: деплой где угодно — другой кластер, свой сервер, закрытый контур (применение — две строки PEFT, как в ячейке 7); минимальный вариант — локальный Ollama из 11.4: та же база + адаптер, эндпоинт на localhost.

Бюджет на 7B QLoRA — прикидка из трёх чисел (ставки смотрите в документации DataSphere, они меняются):

text
шаги ≈ (пар в датасете × эпох) / эффективный батч
200 пар × 2 эпохи / 16 ≈ 25 шагов   → минуты на g1.1 (тестовый прогон)
2000 пар × 3 эпохи / 16 ≈ 375 шагов → единицы часов на g1.1
итог = часы × ставка конфигурации в юнитах × 2 (отладка, повторы, инференс)

Главный вывод: тестовый прогон на 200 парах стоит копейки — гоняйте им весь пайплайн до «большого» обучения, ошибки из 11.5 дешевле ловить на малом.

📖 11.7. Компромиссы

  • gt4.1 vs g1.1/g2.1. Дешёвая T4 требует модель поменьше и учит дольше; A100 ускоряет за юниты. Отладка — на дешёвой, финал — по размеру задачи.
  • 3–4B vs 7–8B. Меньшая модель дешевле в обучении и деплое, но может не дотянуть по качеству домена; проверяйте на golden set (модуль 8).
  • Ручной стек vs LLaMA-Factory/Unsloth. Руки дают понимание и гибкость, обёртки — скорость экспериментов; после ручного прогона обёртка не магия.
  • Свой деплой (ВМ+GPU) vs адаптер на сторону. Свой API в YC — всё в одном облаке, но GPU-инстанс в юнитах круглосуточно; перенос адаптера почти бесплатен, но инфраструктура ваша.
  • Ноутбук vs скрипт. Ноутбук идеален для исследования; воспроизводимый пайплайн (капстоун, модуль 13) — скрипт с фиксацией версий библиотек.

💻 Практика

Нужен аккаунт Yandex Cloud с грантом; квота на выбранную конфигурацию проверена (модуль 9).

  1. Проект и окружение (15 мин). Создайте проект DataSphere, добавьте секреты, создайте ноутбук. Ячейки установки погоняйте на дешёвой CPU-конфигурации: код должен падать до включения GPU.
  2. Датасет (10 мин). Тот же train.jsonl из модулей 5 и 10 — своя база не отменяет гигиену данных.
  3. Скелет на 3–4B (30 мин). Ячейки 1–7 на gt4.1 и модели 1–3B (Qwen2.5-1.5B/3B-Instruct): цель — не качество, а работающий конвейер до чекпойнта в Object Storage.
  4. Обучение и кривые (30 мин + ожидание). Запустите ячейку 5; следите за loss (модуль 7). Остановите конфигурацию сразу после завершения.
  5. Инференс и оценка (20 мин). Ячейка 7 — ответы глазами; затем 10–20 кейсов golden set (модуль 8) против базовой Qwen без адаптера.
  6. Артефакты (10 мин). Адаптер в бакете; таблица версий пополнена: модель, конфигурация, часы, юниты из биллинга, метрика на golden set.
  7. Бюджет (10 мин). По формуле 11.6 прикиньте стоимость «настоящего» прогона на вашем объёме данных и сверьте с остатком гранта.

🧩 Quiz "Проверь себя"

1. Зачем в ячейке датасета apply_chat_template?
2. CUDA out of memory на первом длинном примере. Первое действие?
3. Модель обучилась, чекпойнт сохранён только в ноутбуке, проект удалён.
4. Какая конфигурация — самый дешёвый вход для QLoRA моделей 3–4B?
5. Как задеплоить свою дообученную open-weights модель в Yandex Cloud?

✅ Итоги модуля

  • Назвать случаи, когда open-weights выгоднее тюнинга AI Studio (модуль 10), и выбрать конфигурацию под размер модели (gt4.1 для 3–4B, g1.1/g2.1 для 7–8B) по VRAM-бюджету модуля 6.
  • Собрать скелет QLoRA: BitsAndBytesConfig → LoraConfig → датасет с chat template → SFTTrainer.
  • Прогнать тестовое обучение, остановить конфигурацию, проверить биллинг.
  • Сохранить адаптер в Object Storage и проверить инференсом в ноутбуке.
  • Оценить бюджет по формуле 11.6 и назвать варианты деплоя своих весов.

🔗 Мостик к Модулю 12

Модель готова и где-то отвечает: URI в AI Studio или адаптер на ВМ с GPU. Осталась эксплуатация: вызов из бэкенда с таймаутами и ретраями, версионирование («какая версия была в проде месяц назад?»), выкат shadow → A/B, fallback-цепочка, чтобы деградация модели не роняла фичу, и сбор фидбека в топливо для следующего датасета. Всем этим — с Go-клиентом и мок-тестом — занимается модуль 12.