От чатов WhatsApp до PyTorch: гайд по сборке нейросети

freeCodeCamp.org 358 тыс. 3 ч 29 мин 25 мин 10.04.2025
Главное

Идеально грамотная языковая модель может оказаться абсолютно бесполезным собеседником, если обучать её только на одиночных парах «вопрос-ответ». Чтобы заставить локальный ИИ связно общаться на редком датском языке, автору пришлось пройти путь от ручной очистки чатов WhatsApp до сокращения шагов обучения в тысячи раз с помощью низкоуровневых трюков PyTorch и NumPy. Перед вами подробный хронологический гайд по сборке, оптимизации памяти и генеративному тюнингу кастомной нейросети на 42 миллиона параметров.

🧠 Создание собственной языковой модели: полный пайплайн 0:00

Процесс создания языковой модели (LLM) «с нуля» — это комплексная инженерная задача, позволяющая превратить хаотичные данные из мессенджеров в интеллектуального собеседника, способного имитировать уникальный стиль общения. Разработка охватывает путь от сбора первичных сообщений до этапа тонкой настройки модели, обеспечивая сохранение индивидуального «голоса» автора или адаптацию под редкие языки.

Полный жизненный цикл проекта включает следующие критические этапы:

Использование личных чатов особенно актуально для редких языков или диалектов — например, марокканского «дариджа», где общение часто ведется смесью арабских и латинских букв с добавлением цифр. Традиционные модели часто не справляются с такими данными, поэтому обучение на специфическом корпусе сообщений становится эффективным решением.


🧹 Извлечение и очистка данных WhatsApp 7:21

Экспорт чатов — первая и зачастую самая рутинная стадия работы. Поскольку автоматизированного «однокликового» решения для всех мессенджеров не существует, пользователям приходится экспортировать переписки вручную. Для WhatsApp процедура выглядит следующим образом: через меню (три точки в углу) нужно выбрать опцию «Экспорт чата» без вложений.

После получения текстовых файлов (.txt) наступает этап очистки. Сырые данные содержат множество технических артефактов, которые необходимо удалить, чтобы модель обучалась исключительно на полезном тексте:

Для удобства дальнейшей обработки данные сводятся в формат DataFrame, где каждой строке присваивается временная метка, отправитель и само сообщение. Итогом становится гигантская последовательность текста, объем которой прямо коррелирует с качеством будущей генерации.


🔢 Кодирование текста и алгоритм BPE 15:24

Языковые модели не «читают» текст напрямую; они оперируют числами. Процесс превращения слов в числовой ряд называется кодированием. Существует три основных подхода, каждый из которых имеет свои компромиссы:

  1. Посимвольное кодирование: Очень маленькая длина словаря, но огромная длина последовательности, что быстро исчерпывает контекстное окно модели.
  2. Пословное кодирование: Минимальная длина последовательности, но огромный словарь (особенно в мультиязычных проектах), что ведет к вычислительной сложности из-за размера матриц.
  3. Byte Pair Encoding (BPE): «Золотая середина», позволяющая гибко управлять размером словаря в зависимости от имеющегося аппаратного обеспечения.

Принцип работы BPE заключается в итеративном сжатии данных. Алгоритм находит пары байтов, которые встречаются чаще всего, и заменяет их новым уникальным токеном (процесс слияния). Этот цикл повторяется заданное количество раз, формируя словарь из наиболее частотных комбинаций — от отдельных букв до целых слов.

При реализации на практике, например, с использованием библиотеки minbpe, разработчик может точно задать размер словаря. После обучения токенизатор способен эффективно разбивать любой текст на известные ему фрагменты-токены, что наглядно демонстрируется при проверке словаря: модель автоматически выделяет даже часто встречающиеся имена собственные, что свидетельствует об успешности обучения на специфическом датасете.

🧠 Архитектура трансформера и его компоненты 29:02

В основе современных языковых моделей лежит архитектура трансформера — мощный инструмент, который произвёл революцию в обработке естественного языка. Для задач авторегрессионного моделирования, где цель состоит в предсказании следующего токена и генерации текста, наиболее эффективным является использование исключительно декодерной части архитектуры.

Трансформер можно представить как последовательность слоёв, каждый из которых последовательно обрабатывает входные данные. Ключевые компоненты этой архитектуры включают:

Все эти компоненты объединяются в единый трансформерный блок, который можно дублировать множество раз, масштабируя способность модели к изучению сложных зависимостей. На выходе из последнего блока данные проходят через проекционный слой и Softmax, формируя распределение вероятностей для следующего токена.


💻 Программирование модели GPT на PyTorch 34:10

Для реализации GPT-подобной модели на PyTorch мы начинаем с определения гиперпараметров: размера блока (максимальной длины последовательности), размера эмбеддинга, количества голов внимания и глубины сети. Реализация строится по модульному принципу, где каждый класс отвечает за свой этап обработки данных.

Класс внимания (Attention Head) 41:43

Каждая «голова» внимания работает с тремя ключевыми тензорами: Query (запрос), Key (ключ) и Value (значение). В ходе обучения модель оптимизирует веса линейных слоёв, создающих эти проекции. Для предотвращения «заглядывания в будущее» при обучении используется треугольная маска, а для борьбы с переобучением применяется слой Dropout.

Многоголовое внимание (Multi-Head Attention) 44:10

Класс многоголового внимания объединяет несколько независимых экземпляров «голов» внимания, работающих параллельно. Это позволяет модели одновременно фокусироваться на различных аспектах и связях в тексте. Выходы всех голов объединяются (конкатенируются) и проецируются обратно к исходной размерности эмбеддинга.

Блоки и модель GPT 45:34

Блок трансформера собирает воедино механизмы нормализации, самовнимания и MLP-сети. В финальном классе GPTLanguageModel происходит сборка всех компонентов:

  1. Вход: Индексы токенов проходят через таблицу эмбеддингов и позиционных кодирований, результаты суммируются.
  2. Обработка: Суммарный тензор пропускается через последовательность блоков.
  3. Генерация: Финальный слой проецирует результат на размерность словаря, выдавая логиты.

На текущем этапе модель насчитывает около 12 миллионов параметров. Ранее в разговоре они касались предобработки данных и создания токенизатора — эти этапы являются необходимыми подготовительными процедурами перед подачей данных в архитектуру. Для проверки работоспособности кода практикуется прогон тензора-заглушки (dummy input) для контроля корректности размерностей на каждом этапе.

🚀 Оптимизация процесса обучения и лосс-функции 52:14

Обучение нейронной сети — это итеративный процесс, требующий тщательной настройки параметров для достижения баланса между скоростью работы и качеством обучения модели. Успех во многом зависит от того, как именно вы организуете подачу данных и контролируете поведение модели в процессе оптимизации.

Методы подачи данных: батчинг и нагрузка на GPU 53:35

Существует два основных подхода к созданию загрузчиков данных (data loaders), каждый из которых имеет свои преимущества и недостатки:

Выбор стратегии должен быть продиктован лимитами вашей видеопамяти (VRAM). Целевой показатель использования GPU — около 90%. Если показатель стабильно держится около 99%, вы рискуете столкнуться с переполнением памяти (Out of Memory). Практика показывает, что оптимальную нагрузку лучше подбирать экспериментально: начните с малого размера батча и постепенно увеличивайте его до пределов возможностей вашего оборудования.

Мониторинг лосса и диагностика состояния модели 56:18

Графики функции потерь (loss) — это главный индикатор «здоровья» нейросети. Их необходимо отслеживать в реальном времени, чтобы вовремя вмешаться в процесс обучения.

Ранее в разговоре они касались архитектуры трансформера и его компонентов, а также методов тонкой настройки, однако на этапе пре-трейнинга основной задачей остается достижение понимания языка, а не идеальное качество генерации.

Гиперпараметры и стратегия сохранения 53:09

Помимо архитектуры модели, на результат критически влияют гиперпараметры, такие как learning rate (скорость обучения) и общее количество итераций. Изменение параметров, например, увеличение размера эмбеддинга до 768, может привести к резкому росту количества параметров (скажем, до 30 миллионов), что потребует повторного подбора размера батча, чтобы остаться в рамках доступной памяти.

Крайне важно регулярно сохранять промежуточные чекпоинты обучения — не дожидайтесь завершения всех итераций. Сохранение состояния модели (state dict) и оптимизатора каждые несколько сотен итераций позволит вам восстановить прогресс в случае сбоя системы, что является стандартной практикой в deep learning.

🚀 Стратегии обучения и подготовки данных для LLM 1:15:04

Оптимизация процесса обучения: от случайных батчей к полному проходу 1:16:08

В процессе экспериментов с обучением модели на марокканском диалекте выяснилось, что использование случайных батчей (random batches) не всегда эффективно. Несмотря на увеличение количества параметров модели, функция потерь (loss) часто стагнировала, не позволяя модели полноценно обучаться. Ранее в разговоре они касались архитектуры трансформера и оптимизации лосс-функции.

Решением стал переход к подходу «всех батчей подряд» (all batches approach). Вместо того чтобы полагаться на случайную выборку в течение множества итераций, автор предложил прогнать модель через все имеющиеся данные за один полный цикл (одну итерацию по всему датасету). Этот метод показал себя значительно лучше: модель перестала выдавать бессвязный текст и начала генерировать лингвистически корректные конструкции на целевом диалекте. Ключевой момент здесь заключается в том, что «переобучение» на валидационной выборке в данном контексте — когда модель видит данные лишь один раз — является ложной тревогой; на самом деле это свидетельствует о качественном освоении материала.

Трансформация диалогов в формат для тонкой настройки 1:24:46

После этапа базового обучения (pre-training) наступает стадия тонкой настройки (fine-tuning), где модель должна научиться имитировать стиль общения конкретного человека. Для этого «сырые» данные из мессенджеров, таких как WhatsApp, нуждаются в тщательной предобработке.

Структурирование данных происходит следующим образом:

Важно соблюдать баланс контекста: можно обучать модель на коротких парах «реплика — ответ» или на длинных историях переписки. Во втором случае необходимо внимательно следить за тем, чтобы длина последовательности не превышала заданный block size, иначе данные будут обрезаны.

Full Fine-Tuning против PEFT 1:33:08

При дообучении базовой модели для создания специализированного ассистента существуют два основных подхода:

  1. Full Fine-Tuning (полная настройка): Предполагает изменение всех весов модели. Это мощный, но вычислительно затратный метод, так как требует обновления параметров всей архитектуры.
  2. PEFT (Parameter-Efficient Fine-Tuning): Эффективная альтернатива, при которой веса базовой модели «замораживаются». Вместо модификации всей сети поверх нее добавляются дополнительные слои или адаптеры (например, с использованием методов LoRA или QLoRA). Это позволяет существенно экономить ресурсы и дает возможность «подключать» разные адаптеры к одной базовой модели для решения разных задач.

Эффективный батчинг через пэддинг 1:35:48

Одной из главных проблем при обучении на реальных диалогах является разная длина сообщений. Поскольку стандартные инструменты (например, PyTorch) требуют, чтобы данные в одном батче имели одинаковый размер, возникает необходимость в пэддинге (padding).

Техника заключается в заполнении коротких последовательностей специальными «пустыми» токенами до достижения фиксированного block size. Важно, что при вычислении функции потерь эти пэддинг-токены игнорируются: модель не должна учиться их генерировать. При подготовке тензоров $X$ (вход) и $Y$ (цель) последовательность $Y$ просто сдвигается на одну позицию относительно $X$, а нехватка данных в конце компенсируется еще одним пэддинг-токеном, что гарантирует стабильность обучающего процесса.

-

🤖 Эксперименты по тонкой настройке: от одиночных реплик к контекстным окнам 1:40:14

После подготовки данных и выбора архитектуры наступает критический этап — обучение модели на специфическом наборе данных (fine-tuning). На этом этапе важно не просто «скормить» нейросети текст, а правильно структурировать диалоговые пары, чтобы модель поняла саму механику общения. Ранее в процессе подготовки данных автор уже разделил их на тренировочный и валидационный сеты , однако для качественной тонкой настройки этого недостаточно. Необходимо гарантировать, что каждая последовательность начинается с сообщения пользователя и заканчивается ответом ассистента, иначе логика предсказания следующего токена будет нарушена.

Тонкая настройка без контекста: обучение на одиночных парах 1:42:12

Первый подход, который автор называет «no context» , заключается в обучении модели на изолированных парах сообщений. В этом сценарии каждая единица данных состоит ровно из одного вопроса пользователя и одного ответа ассистента.

Для реализации этого метода был написан метод combine_turns, который объединяет реплики в одну последовательность . В результате количество элементов в тренировочном наборе сократилось вдвое — с 7383 до 3691 . Основные технические особенности этого этапа:

Результаты первого эксперимента оказались неоднозначными. После 5 эпох обучения при низкой скорости обучения (learning rate) 6e-5 модель начала демонстрировать понимание структуры . Она научилась вовремя останавливаться, генерируя EOS-токен, и строить грамматически правильные предложения на датском языке. Однако содержательная часть оставляла желать лучшего: ответы модели зачастую не имели никакого отношения к заданному вопросу . Даже увеличение количества эпох до 100 и появление явных признаков переобучения на графиках лосса не решило проблему «галлюцинаций» и смысловой несвязности.

Тонкая настройка с контекстом: объединение диалоговых цепочек 1:57:33

Осознав ограничения «безконтекстного» подхода, автор перешел ко второму этапу — обучению с учетом истории переписки. Основная гипотеза заключалась в том, что модели нужно видеть несколько предыдущих реплик, чтобы лучше понимать суть диалога.

Для этого был разработан метод merge_conversation_turns . Его алгоритм работает следующим образом:

  1. Берётся список уже скомбинированных пар «вопрос-ответ».
  2. Программа пытается объединить несколько таких пар в одну длинную последовательность.
  3. Объединение продолжается до тех пор, пока суммарная длина токенов не приблизится к максимально допустимому размеру блока (block size) .
  4. Как только лимит превышен, текущая цепочка сохраняется как один элемент данных, и начинается формирование следующей.

В результате такой упаковки объем данных сократился еще в три раза по сравнению с первым методом . Это позволило модели видеть более широкую перспективу общения внутри одного окна внимания.

Анализ результатов и влияние размера данных 2:02:16

Эксперименты с контекстным обучением (от 10 до 20 эпох) показали качественный сдвиг. Модель стала выдавать более адекватные реакции. Например, на приветствие и вопрос о видео модель смогла ответить «Да» и даже добавить смеющийся эмодзи , что соответствовало стилистике исходного датасета WhatsApp.

Тем не менее, автор отмечает несколько критических проблем:

«Я вижу, что этот подход очень многообещающий. Если увеличить размер набора данных, модель станет еще лучше. Мы создали рабочие ноутбуки, и это позволяет нам вернуться к любому шагу, чтобы обучить более крупную сеть или изменить гиперпараметры» .

Несмотря на то, что текущая версия не позволяет вести полноценный глубокий диалог, эксперимент подтвердил: даже на скромном домашнем GPU можно добиться от модели понимания специфического формата данных и базовой связности речи, если правильно настроить передачу контекста между репликами.

🚀 От низкоранговой адаптации к терабайтам облаков: оптимизация и масштабирование LLM 2:06:18

Математика эффективного обучения: как устроен метод LoRA 2:06:18

Ранее в разговоре авторы уже касались различий между полным дообучением и методами PEFT. При полной настройке во время обратного распространения ошибки рассчитывается объемная матрица градиентов $\Delta W$, которая суммируется с исходными весами $W$. Если базовая модель крупная, обновление весов требует колоссальных ресурсов. Для обхода этой проблемы применяется метод LoRA (Low-Rank Adaptation).

Математический принцип LoRA основан на низкоранговой декомпозиции: вместо изменения исходной матрицы весов алгоритм аппроксимирует $\Delta W$ с помощью произведения двух матриц меньшего ранга — $A$ и $B$. Если базовая матрица требует обновления 4 миллионов параметров, то при использовании LoRA с рангом $R = 2$ число обучаемых параметров падает до 8 000. Матрица $A$ имеет размерность $N \times R$, а матрица $B$ — $R \times M$.

В коде это реализуется через замену стандартных слоев оберткой LinearWithLora. Веса базовой модели полностью замораживаются. В процессе оптимизатор updates только коэффициенты матриц $A$ и $B$. На примере автора из 13 миллионов параметров модели обучаемыми остаются лишь 0,42 миллиона — около 3% от общего объема. На локальных данных эксперимент автора не дал безупречного качества текста, но модель успешно освоила структуру чата.

Проблемы масштабирования и кризис оперативной памяти 2:20:40

Локальные тесты упираются в технический потолок при переходе к промышленным объемам. Вместо демонстрационного текста на 1,5 миллиона символов для финального обучения был взят массив Atlas set от команды Atlasia объемом 900 миллионов символов. Этот корпус в 600 раз больше первоначального. Обучающая выборка датасета включает 1,17 миллиона строк, а тест — 2680 строк.

Подобный масштаб вызывает серьезные аппаратные сбои. При попытке загрузить Atlas set обычный ноутбук автора с 16 ГБ оперативной памяти полностью завис. Физическая RAM и файл подкачки (swap) заполнились до предела, парализовав работу системы. Обучить токенизатор BPE напрямую на 900 миллионах символов тоже невозможно — программа падает из-за нехватки памяти для алгоритма.

Решением становится обучение токенизатора на случайной выборке объемом от 1% до 5% от общего текста датасета. Это позволяет оценить распределение данных без перегрузки RAM. Даже при таком компромиссе обучение токенизатора на подвыборке в 10 миллионов символов заняло у автора 6 часов.

Смена формата: почему pickle уступает memory-mapped файлам 2:23:28

После токенизации и кодирования текста в числовые индексы данные необходимо сохранить на диск, чтобы исключить повторение долгой процедуры. Однако популярный в Python формат pickle оказывается абсолютно неэффективным на больших объемах.

При десериализации файла .pickle Jupyter-ноутбук пытается одномоментно считать весь массив токенов с диска напрямую в оперативную память, что гарантированно вызывает ошибку Out of Memory (OOM). Взамен автор предлагает использовать memory-mapped файлы средствами NumPy — np.memmap.

Этот механизм функционирует аналогично стандартному словарю Python: он оставляет основное тело файла на диске, подгружая в RAM исключительно те индексы и фрагменты, которые необходимы для формирования текущего батча. Это нормализует потребление памяти и защищает систему от сбоев.

Облачные вычисления и аренда GPU: выходим за рамки локального железа 2:24:07

Скорость вычислений на локальном оборудовании часто становится непреодолимым барьером. Домашняя видеокарта автора NVIDIA RTX 4070 столкнулась с необходимостью обработать 68 миллионов батчей. Расчеты показали, что для завершения одной итерации потребуется 77 000 часов — ждать пришлось бы до 2033 года.

Решением становится аренда облачных GPU, сокращающая время обучения до часа и менее. Автор сравнивает три основные платформы:

В практической части автор переносит код в Google Colab, подключает репозиторий и монтирует Google Диск для загрузки токенизатора и датасета. При запуске часто возникает ошибка нехватки видеопамяти CUDA out of memory. Чтобы компенсировать это и сохранить стабильность обучения, была задействована техника накопления градиентов (gradient_accumulation_steps). Симуляция крупного батча позволяет эффективно обходить лимиты VRAM на доступном оборудовании.

⚙️ Оптимизация процесса обучения и управления ресурсами 2:30:51

При работе с большими языковыми моделями (LLM) эффективное управление системными ресурсами становится критическим фактором успеха. Использование memory-mapped файлов, о котором ранее упоминалось в шестой главе, позволяет работать с огромными объемами данных, сохраняя при этом низкое потребление оперативной памяти. В данном случае система успешно функционировала, используя лишь 4 ГБ RAM, даже при работе с масштабным набором данных, поскольку модель не загружает весь массив в память целиком, а подгружает только необходимые фрагменты.

При выборе облачных мощностей для обучения крайне важно учитывать ограничения конкретных провайдеров (Kaggle, Google Colab, Lambda). Основной проблемой часто становится дефицит VRAM, который напрямую влияет на максимально возможный размер батча. Недостаточный объем видеопамяти вынуждает использовать меньшие значения batch size, что замедляет прохождение через весь датасет. Важное замечание: предобработку данных следует проводить либо локально, либо на дешевых виртуальных машинах без GPU, так как использование дорогостоящего GPU-инстанса для задач подготовки данных экономически нецелесообразно.

📉 Оптимизация неперекрывающихся блоков данных 2:46:27

Переход от концепции перекрывающихся блоков (overlapping batches) к неперекрывающимся (non-overlapping batches) стал ключевым моментом, позволившим кардинально оптимизировать процесс обучения.

В стандартном подходе с перекрытием каждое последующее окно размером 1024 токена сдвигалось лишь на один символ, что приводило к избыточности и гигантскому количеству итераций — в некоторых сценариях число потенциальных блоков достигало 69 миллионов. При использовании метода неперекрывающихся блоков каждое окно жестко следует за предыдущим, не захватывая часть его данных. Это решение принесло впечатляющие результаты:

🛠 Настройка параметров для эффективного дообучения (Fine-tuning) 2:50:08

Когда базовая модель готова, следующим этапом становится супервайзерное дообучение (Supervised Fine-tuning, SFT), например, для задач ответов на вопросы (QA). Для повышения эффективности процесса в условиях ограниченных данных применяются специфические техники подготовки тензоров.

Вместо того чтобы приводить все последовательности к фиксированному блочному размеру (например, 1024), который часто является избыточным для коротких диалогов, используется динамический паддинг. Вычисляется максимальная длина последовательности в текущем наборе данных — например, 331 токен — и паддинг выполняется строго до этого значения. Это позволяет существенно экономить память и повысить эффективность тренировки. В процессе подготовки данных для QA-задач также важно внедрение системных промптов и специальных токенов управления (start turn, end turn, separator), которые помогают модели четко разделять роли пользователя и ассистента в рамках диалоговой структуры.

🎲 Укрощение детерминизма и маскирование: продвинутые методы генерации и тонкой настройки 3:03:40

Продвинутые параметры генерации: преодоление «жадного» детерминизма 3:03:40

Когда базовая языковая модель обучается на небольшом массиве данных, она предсказуемо быстро переобучается. Ранее в разговоре автор уже подробно разбирал циклы тонкой настройки датасетов, но именно на этапе инференса выявляются критические вызовы архитектуры. В простейших сценариях генерации текстового вывода используется метод так называемого «жадного» поиска (greedy search). При таком подходе алгоритм на каждом шаге просто выбирает токен, обладающий наивысшей вероятностью. Это делает генерацию абсолютно детерминированной: на один и тот же промпт модель всегда будет выдавать один и тот же статичный результат.

Чтобы вдохнуть жизнь в текстовый вывод и сделать ответы более вариативными, в скрипт модели добавляется метод продвинутой генерации (advanced_generation). Он управляется тремя ключевыми параметрами:

Эти переменные позволяют радикально изменить поведение модели во время работы. Например, при проверке чекпоинтов на вопросе «Как тебя зовут?» обученная модель успешно извлекает имя, прописанное разработчиком в системном промпте. Однако из-за небольшого размера базовой архитектуры (всего 40 миллионов параметров по сравнению с миллиардными масштабами коммерческих моделей вроде Google Gemini), ответы могут варьироваться. Если же задать ей вопрос, полностью отсутствующий в обучающей выборке — например, «Кто выиграл ЧМ-2006 по футболу?», — переобученная модель начинает галлюцинировать, уверенно называя Аргентину вместо Италии.

Анатомия многораундового диалога и управление токенами остановки 3:09:54

Простая механика «вопрос-ответ» накладывает жесткие ограничения: модель моментально теряет нить разговора, если пользователь ссылается на свои предыдущие реплики. Чтобы научить сеть полноценному многораундовому общению, данные необходимо упаковывать в виде последовательных turn-блоков диалога.

При переходе к полноценному диалоговому формату применяется метод прогрессивного накопления контекста (скользящего окна). Вместо изолированных пар вопросов и ответов в выборку последовательно добавляются новые реплики, увеличивая размер примера до тех пор, пока вся беседа не будет исчерпана. Важнейший нюанс кроется в расстановке специальных токенов:

Если этого не сделать и объединить всё в один неструктурированный пример, модель начнет слепо имитировать саму структуру сырых данных. Получив вопрос, она сгенерирует корректный ответ, но вместо остановки продолжит сама писать за гипотетического пользователя, превращая инференс в бесконечный зацикленный монолог. Корректная расстановка токенов учит систему вовремя завершать генерацию. При тестировании такого подхода модель демонстрирует связную живую беседу, безошибочно перечисляя планеты Солнечной системы от Меркурия до Нептуна и вежливо прощаясь в финале.

Маскирование целевого тензора: почему модель не должна учиться угадывать вопросы пользователя 3:17:25

Даже если структура диалога выстроена идеально, стандартный подход без маскирования заставляет модель вычислять ошибку (loss) абсолютно на всех токенах последовательности. Сеть тратит драгоценные вычислительные ресурсы на то, чтобы научиться с нуля предсказывать фиксированный системный промпт или реплики самого пользователя. Но реальная задача тонкой настройки (SFT) — научить модель выдавать релевантный ответ ассистента в ответ на входящий текст.

Решением становится маскирование целевого тензора (target tensor) в блоке загрузчика данных с помощью функции apply_mask_to_target. Суть метода заключается в разделении зон ответственности:

Для практической реализации этого механизма используется специальный параметр ignore_index, передаваемый в функцию потерь кросс-энтропии PyTorch (cross_entropy). Программа сканирует целевой тензор, находит техническую сигнатуру начала ответа ассистента (start_turn_assistant) и заменяет все предшествующие токены техническим значением маски. Во время расчета ошибки кросс-энтропии эти позиции попросту игнорируются. Модель больше не наказывают за неточное предсказание реплик пользователя; вся сила градиентного спуска направляется исключительно на максимизацию качества ответов ассистента.

🏁 Финал разработки: интерактивный чат, архитектурные метрики и лимиты мини-модели 3:21:07

Алгоритм скользящего окна и специфика правого пэддинга 3:21:07

На этапе практической реализации маскирования разработчику приходится сталкиваться с нюансами обработки многошаговых диалогов. Если в беседе присутствует несколько реплик, последовательность токенов, обозначающая начало ответа ассистента, будет повторяться несколько раз. Чтобы корректно изолировать целевой текст, применяется подход со скользящим окном, который находит именно последнее вхождение нужного паттерна. Программа использует функцию torch.all для посимвольного сравнения срезов тензора с закодированной эталонной последовательностью. Как только финальное вхождение обнаружено, индекс сдвигается на длину этой последовательности, а все предшествующие токены от самого начала заменяются на токен пэддинга.

Ранее в разговоре уже затрагивалась тема маскирования целевого тензора при обучении, и здесь этот принцип доведен до автоматизма: в качестве маски используется токен заполнения (padding token), имеющий в данном коде идентификатор 16 388. При инициализации модели GPT этот токен передается в функцию потерь Cross-Entropy в качестве параметра ignore_index. Если забыть явно указать этот индекс для игнорирования, функция потерь попросту не сработает корректно.

Важное архитектурное ограничение созданного алгоритма заключается в том, что он строго завязан на использование правого пэддинга (выравнивания справа). Если бы разработчик решил выравнивать последовательности по левому краю, весь цикл поиска пришлось бы развернуть в обратную сторону — начинать сканирование с конца строки, а не с нулевого индекса. В процессе тонкой настройки на ограниченном числе токенов значение лосса закономерно выросло, и модель начала демонстрировать признаки оверфиттинга. Тем не менее, наличие промежуточных чекпоинтов позволяет откатиться назад и протестировать работоспособность системы в режиме реального времени.

Интерактивный тест-драйв: проверка ответов и управление историей 3:25:10

Запуск обученной модели в интерактивном режиме позволяет наглядно оценить качество проделанной работы. Проверка традиционно начинается с верификации системного промта вопросом «Как тебя зовут?». Модель успешно справляется с этим базовым тестом, подтверждая, что инструкции закрепились в ее памяти. На более сложный вопрос о количестве минут в сутках сеть выдает развернутый ответ: «В нашем мире в дне примерно 1440 минут». Примечательно, что именно такой формулировки в датасете для тонкой настройки не было. Модель пытается проявлять творческий подход, что становится возможным благодаря использованию продвинутых параметров генерации текста, о которых подробно говорилось в предыдущих главах. Если бы вместо этого применялся стандартный базовый метод генерации, искусственный интеллект выдавал бы абсолютно идентичный, жестко детерминированный ответ при каждом запуске, полностью лишаясь гибкости.

В процессе калибровки диалогового окна в Jupyter Notebook выявляется важная прикладная деталь. Если пользователь получает некорректный или неудачный ответ и хочет запустить перегенерацию, обычный повторный вызов функции добавит новую реплику в историю переписки, засоряя контекст. Для решения этой проблемы в коде предусмотрен элегантный трюк: раскомментирование специальной строки удаляет последнюю запись из истории диалога и заменяет ее свежесгенерированным вариантом. Пользователь может циклически повторять эту операцию до тех пор, пока не добьется идеального по смыслу и стилистике ответа. Тот факт, что каждый раз модель генерирует уникальные реплики, подтверждает правильную настройку стохастических параметров вывода.

Итоговые метрики, ограничения и перспективы масштабирования 3:27:22

Несмотря на то, что тестовый чат демонстрирует вполне вменяемые результаты, авторы призывают реалистично смотреть на возможности получившейся нейросети. Перед нами ультракомпактная модель, содержащая всего 42 миллиона параметров. Для ее предварительного обучения использовался текстовый массив объемом 900 миллионов символов, что после применения токенизатора дало порядка 300 миллионов токенов. Безусловно, это огромный шаг вперед по сравнению с крошечным игрушечным датасетом, с которого начинался данный курс, но для полноценного языкового моделирования этого все еще катастрофически мало.

В ходе инференса и разностороннего тестирования четко проявились фундаментальные лимиты архитектуры малого объема. Модель банально не понимает множество сложных концептов. Если задавать ей вопросы, выходящие за рамки узкого тренировочного распределения данных, она теряется и не знает, как отвечать.

Чтобы преодолеть эти барьеры и получить по-настоящему интеллектуального ассистента, способного свободно общаться на естественном языке, необходимо двигаться в сторону масштабирования. Очевидными следующими шагами являются кратное увеличение числа параметров самой сети и расширение качественного датасета для контролируемого обучения (Supervised Fine-Tuning). Данный урок полностью закрывает масштабный цикл туториалов по созданию собственного ИИ. Автор подводит итоги, выражает надежду, что материал оказался полезным, и оставляет каналы связи для ответов на вопросы сообщества.

💬 Цитаты

«The great thing about it is that we can control the size of the vocabulary based on our hardware and data we work with.»

«Tracking the loss is very important because it shows whether the model is underfitting, overfitting or training well.»

«Текст в порядке, модель знает, как использовать слова вместе, чтобы формировать связные предложения, но ответы ассистента на запросы пользователя не связаны по смыслу.»

Автор видео 117:06

«Датский язык — редкий, вы не найдете больших языковых моделей, обученных на нем. Поэтому то, что мы получили, выглядит очень многообещающе.»

Автор видео 125:11

«Instead of using the full data set, train on a sample, about 1 to 5% of the text that you have on your data set. This saves memory and helps you estimate the data distribution.»

«If you use the generate method then the model will say will will give you the same answer every single time. So the model will not try to be innovative.»

👥 Спикеры
📖 Термины
BPE (Byte Pair Encoding)
Алгоритм токенизации текста, позволяющий найти оптимальный баланс между размером словаря и длиной последовательности токенов.
LoRA (Low-Rank Adaptation)
Метод эффективного дообучения LLM с помощью низкоранговых матриц, радикально снижающий количество обучаемых параметров.
np.memmap
Инструмент библиотеки NumPy для создания memory-mapped файлов, позволяющий читать данные с диска без полной загрузки датасета в RAM.
Self-attention
Механизм внутреннего внимания в архитектуре трансформеров, вычисляющий смысловые взаимосвязи между различными токенами в последовательности.
Искусственный интеллект PyTorch LLM LoRA BPE токенизация WhatsApp