Каждый первый человек, имеющий минимальное представление об интернет пространстве и его состоянии на текущий момент, уже слышал такие странные слова, как ChatGPT, Deepseek и прочие искусственно-интелектуальные штуки. Все они работают на серьезных вычислительных мощностях где-то там, где их и разработали, а мы, как обыватели, лишь используем их API для получения ответов на поставленные нами вопросы.
Какое-то резкое начало получилось... Все же, давайте с начала.
Что такое LLM?
Large Language Model - дословно, большая языковая модель, обученная на огромном количестве текстовых данных с использованием различных архитектур нейронных сетей (о них чуть позже).
По факту, LLM - лишь алгоритм, который умеет понимать и генерировать текст, его уже "натренировали" на чтении миллиардов предложений. Этот алгоритм умеет предсказывать, какое слово (или символ/токен/etc) должно идти дальше в текущем контексте.
К 2025 году каждая крупная корпорация захотела создать свою языковую модель, и на данный момент их обилие настолько велико, что перечислять всех - тяжело, долго и не нужно. Обсудим в двух словах главных "слонов" из этого мира:
ChatGPT - на данный момент самая популярная модель от компании OpenAI
Gemini - модель, умеющая долго держать один контекст в реализации от Google
Mistral - быстрая и точная модель от одноименной компании
Jurassic-2 - поделка от AI21 Labs, ориентированая на сложные и большие тексты
Tongyi Qianwen - прородитель Deepseek родом из Китая, поддерживаемый компанией Alibaba
Что объединяет вышеописанные продукты? Правильно, как и было сказано ранее, они "живут" на вычислительных мощностях своих компаний, учатся и ищут данные для обучения по запросам сотен тысяч пользователей со всего мира.
При чем тут парнокопытные?
Обсудили умные и крутые модели, теперь поговорим про их младших собратьев - открытые LLM.
Они отличаются от предыдущих возможностью локального запуска у себя на компьютере, возможностью дообучения и кастомизации.
Оных моделей очень и очень много, но сейчас речь пойдет про одну конкретную - LLaMa от Meta каких-то там запрещенных разработчиков.
На оффициальном сайте разработчика в разделе Models and Products можно лицезреть следующую картину:

В чем разница? Зачем так много? На самом деле все довольно просто.
8B: лёгкая и быстрая модель, подходит для локального запуска
1B и 3B: минимальные размеры, идеальны для мобильных устройств и edge-компьютеров.
70B: высокопроизводительная модель с акцентом на оптимизацию и доступность.
405B: гигантская модель (весовая категория GPT-4), флагман, скорее всего, используется в облаке, не для локального запуска.
Я думаю не сложно провести сопоставление функционала и числа перед буквой B, тут вроде все очевидно.
Про саму модель можно говорить довольно долго, но в силу уважения времени уважаемого читателя сократим этот список.
Архитектура базируется на трех основых приницпах:
Transformer Decoder-Only (как GPT): предсказывает следующее слово по контексту (бОльшая часть LLM существует именно на этой архитектуре)
Causal Attention: смотрит только в прошлое, не «заглядывает» вперёд (но получается не всегда)
Оптимизация под inference: быстрая генерация текста, низкие задержки
В целом, этих вводных данных достаточно для осознания того, что нам предлагают развернуть у себя на локальной машине своего маленького друга, который заведомо куда менее умный, нежели интернет-друзья, но в комплекте с ним мы можем получить учебник, шмякнув которым пару раз по нашей модели (или корпусу ПК) мы сделаем друга умнее.
И вот, спустя три с половиной тысячи символов мы переходим к...
Развертывание и запуск
Для решения поставленной задачи предлагаю использовать все прелести жизни в 21 веке - Docker и все, что идет с ним в комплекте.
Напишем базовый docker-compose.yml файл:
version: '3.8'
services:
ollama:
image: ollama/ollama
container_name: ollama
ports:
- "11434:11434"
volumes:
- ollama-data:/root/.ollama
deploy:
resources:
reservations:
devices:
- capabilities: [gpu]
volumes:
ollama-data:
Ремарка: ллама будет хорошо жить и кушать травку при наличии GPU, но и на CPU она может существовать, сжирая во время ответа 32гб оперативной памяти только в путь

Окей, теперь запустим зверька в контейнере (как бы прискорбно это не звучало)
docker compose up -d
docker exec -it ollama ollama run llama3
И вот мы попали в CLI интерфейс LLaMa3. Уже сейчас с ней можно немного поговорить, на базовые вопросы и размышления она (в конфигурации 8B) умеет отлично отвечать.
Но ведь если мы бы хотели просто поболтать, куда разумнее было бы просто зайти на любой из уже известных сайтов и получить тонну информации на любой счет, не так ли?
Если мы развернули модель локально - значит у нас есть для нее специфическая задача. В моем случае такой задачей стало определение необходимой конфигурации запуска сервиса в зависимости от пользовательского запроса.
Из коробки ллама не умела давать точный и однозначный ответ на данный вопрос. Вывод прост - поехали ее дообучать!
Дрессировка
Дообучить LLM == адаптировать её ответы под конкретную задачу.
Существует несколько подходов к данному процессу.
Тип |
Описание |
Full fine-tuning |
Полное переобучение весов модели. Требует много VRAM и данных. |
LoRA / QLoRA |
Дообучаются только маленькие адаптационные слои. Дёшево и быстро. |
Instruction tuning |
Дообучение на вопрос-ответ, формат prompt → response. |
DPO / RLAIF |
Дообучение с предпочтениями (модель учится выбирать лучший ответ). |
Странные аббревиатуры из мира ML заставляют задуматься любого человека, но не переживайте, данный вопрос был успешно обсужен с ChatGPT более матерыми и продвинутыми юзерами нейронных сетей и в кротчайшие сроки был получен ответ - для моей задачи идеально подходил LoRA метод.
Каво? Метод чево?
В двух словах - вместо того чтобы изменять все веса большой модели (что требует много GPU-памяти и времени), LoRA (Low-Rank Adaptation) добавляет небольшие обучаемые слои (ранг-n матрицы) только в определённые места — обычно в attention.
На этом моменте нужно немного подумать. И так:
Допустим, есть матрица весов W размером 4096×4096 в attention. Обновлять её напрямую — дорого. LoRA же проведет следующую махинацию:
W' = W + ΔW
ΔW = A @ B
Где
A — матрица размера (4096×r),
B — (r×4096),
r — маленький ранг (например, r=8)
Обучаются только A и B, а W останется замороженной (!важно!)
Что такое attention и почему мы будем "что-то" добавлять туда?
Attention - ключевой механизм "внимания", который позволяет LLM генерировать контекст и обращать внимание на нужные слова. Говоря человеческим языком - "На какие слова в предложении стоит обращать внимание, чтобы понять или сгенерировать следующее слово?"
Пример:
"Разработчик пошел в магазин готовой еды, потому что он голоден"
Кто "он"? Магазин или разработчик?
Каждое слово в предложении получает три вектора: Query (Q) – что ищем, Key (K) – что у нас есть, Value (V) – информация, которую хотим извлечь
Алгоритм:
-
Считаем сходство Q*K между словами
-
Получаем веса внимания (attention scores) — насколько важны остальные слова для текущего
-
Используем эти веса, чтобы усреднить значения V и получить «смысловой контекст»
|
Слово «разработчик» |
«пошел» |
«в магазин» |
«он» |
0.9 |
0.05 |
0.05 |
Фух, от теории перейдем к практике.
Скрещиваем ламу с асколотлем (???)
Дабы не сойти с ума с ручным пересчетом матриц и просчета датасетов используем инновации - CLI утилиту Axolotl для реализации LoRA дообучения из коробки.
Для начала работы с тулзой необходимсо провести процедуру инсталляции. Для этого нужен Python >=3.10, CUDA и немного (немало, на самом то деле) VRAM. Чтобы ничего не прокисло на этапе запуска утилита хочет иметь во владении 12-16гб.
git clone https://github.com/OpenAccess-AI-Collective/axolotl
cd axolotl
pip install -r requirements.txt
И так, для того, чтобы модель стала умнее нам необходимо иметь аж 2 вещи: датасет и конфиг-файл для Axolotl. Все остальное утилита сделает сама.
По итогу структура проекта должна будет принять следующий вид
project/
├── configs/
│ └── llama3-qlora.yml # <-- Конфига Axolotl
├── data/
│ └── tuneit_query.jsonl # <-- Датасет
└── outputs/
└── tune-it-llama-w # <-- Веса LoRA, их нам даст Axolotl
Определяемся с форматом датасета. Зачастую, хватает лишь формы ["Запрос:{...}","Ответ:{...}"], но их вариация и дополнительные поля будут варьироваться от конкретной модели.
Сколько данных нам нужно? Условимся, что тут у нас нет ограничений, ведь precision модели будет пытаться стремиться к единице. Методологией пристального взгляда и тыка пальцем в небо у меня получилось полторы тысячи подобных пар, объединенных в формате jsonl. По меркам профессиональных "дообучателей" это совсем мало, но, забегая на перед, можно сделать вывод - достаточно.
Учитывая специфику ollama формат получился следующий:
{"instruction": "Назови столицу Франции", "input": "", "output": "Париж."}
Где взять полторы тысячи данных для составления датасета? Ответ как никогда прост - сходите в chatgpt-4o через программку, написанную на коленке на Python по вашему API Ключу от OpenAI и вы получите околонеограниченное количество тестовых и однотипных данных.
После того, как мы смогли нагенерить датасет, настала пора разбираться в yaml-кодинге для Axolotl.
Из минимально необхоимого нам нужно:
- Базовая модель - в нашем случае meta-llama/Meta-Llama-3-8B-Instruct
- Модель, на базе которой идёт дообучение. Можно использовать любую HuggingFace-модель
- Подключение 4-х битного квантирования (Квантование — разбиение диапазона значений некоторой величины на конечное число уровней и округление этих значений до ближайших к ним уровней) для экономии ресурсов
- Указываем тип токенизатора (штуки, которая будет разбивать наш текст на токена для скармливания в алгоритм), у каждой модели есть свой собственный токенизатор
- Путь к датасету
- Базовые данные для старта обучения - количество эпох, количество примеров для обработки за раз, количество накопленных градиентов (можете не вдаваться в подробности, оно особо не надо)
- Конфигурация для самой LoRA
- Стартовая скорость обучения
Вот полный конфиг-файл с минимальными комментариями:
# Название базовой модели
base_model: meta-llama/Meta-Llama-3-8B-Instruct
# Тип модели - для LLaMA обязательно LlamaForCausalLM
model_type: LlamaForCausalLM
trust_remote_code: true
# Тип токенизатора
tokenizer_type: LlamaTokenizer
# Все для квантирования
load_in_4bit: true
adapter: qlora
# Описание датасета
datasets:
- path: ./tuneit_query.jsonl
type: alpaca
# Размер валидационной выборки (сколько мы оставим данных на валидацию)
val_set_size: 0.05
output_dir: ./outputs/tune-it-llama-w
# Все для описания обучения
num_epochs: 3
micro_batch_size: 4
gradient_accumulation_steps: 8
# Периодичность валидации (в шагах обучения)
eval_steps: 50
# Параметры LoRA
lora_r: 8
lora_alpha: 16
lora_dropout: 0.05
lora_target_modules: ["q_proj", "v_proj"]
# Настройки обучения
lr_scheduler: cosine
learning_rate: 0.0002
warmup_steps: 20
logging_steps: 10
use_flash_attention: true
# Настройки 4-битной квантизации
bnb_4bit_quant_type: nf4
bnb_4bit_compute_dtype: float16
Фух, последнее что от нас требуется - запуск и последующее тестирование.
Запускаем!
python -m axolotl.cli.train configs/llama3-qlora.yml
Дальнейшие действия провернет уже сама утилита. Осталось подождать (долго)
После того, как в директории ./outputs что-то появилось, можем приступить к тестированию. Очевидно на готовых коробочных решениях Python
from transformers import AutoModelForCausalLM, AutoTokenizer
from peft import PeftModel
base = AutoModelForCausalLM.from_pretrained("meta-llama/Meta-Llama-3-8B-Instruct", device_map="auto", load_in_4bit=True)
model = PeftModel.from_pretrained(base, "./outputs/tune-it-llama-w")
tokenizer = AutoTokenizer.from_pretrained("meta-llama/Meta-Llama-3-8B-Instruct")
prompt = "Дак какая столица у Франции и какая у нее история?"
inputs = tokenizer(prompt, return_tensors="pt").to(model.device)
outputs = model.generate(**inputs, max_new_tokens=100)
print(tokenizer.decode(outputs[0], skip_special_tokens=True))\
Да, сейчас мы руками пропихнули и прибили гвоздями новые веса и не сможем получить новые данные из коробки докера.
Далее можно все соединить в .gguf файл и запускать через образ, предварительно объединив матрицы через merge_peft_adapter, но об этом в другой раз...
Вывод
Что мы имеем? LLaMa, которая умеет правильно отвечать в нашем контексте, имеем готовый конфиг файл для дообучения, фактически, любой модели на любые данные и новый багаж новых непонятных слов...
Спасибо за внимание