Архитектура ML-систем и LLM: от алгоритмического фундамента к Senior-проектированию

Углубленный курс для перехода с уровня Middle на Senior, фокусирующийся на проектировании масштабируемых систем и внедрении больших языковых моделей. Программа выстроена как путь от оптимизации кода и данных к созданию комплексных производственных пайплайнов и RAG-систем.

1. Эффективный Python и векторные вычисления: оптимизация производительности в высоконагруженных ML-сервисах

Эффективный Python и векторные вычисления: оптимизация производительности в высоконагруженных ML-сервисах

Парадокс современного машинного обучения заключается в том, что индустрия с самыми высокими требованиями к вычислительной мощности использует один из самых медленных языков программирования. Чистый Python в десятки, а иногда и в сотни раз медленнее C++. Тем не менее, именно на Python строятся пайплайны подготовки данных для терабайтных LLM и высоконагруженные сервисы инференса, обрабатывающие тысячи запросов в секунду.

Секрет в том, что Senior Data Scientist пишет на Python код, который не выполняется в Python. Разница между Middle-специалистом, у которого пайплайн падает по таймауту, и Senior-архитектором кроется в понимании того, как передать тяжелую работу на уровень железа, минуя ограничения языка.

Анатомия медлительности: почему Python тормозит

Прежде чем оптимизировать систему, нужно понять природу узкого горлышка. В Python их два:

  • Динамическая типизация и интерпретация. В языках со статической типизацией (C++, Rust) тип переменной известен на этапе компиляции. Когда в C++ складываются два числа, процессор просто выполняет инструкцию сложения. В Python при выполнении a + b интерпретатор должен сначала проверить тип a, проверить тип b, найти соответствующий метод __add__ и только потом выполнить операцию. В цикле на миллион итераций эти проверки происходят миллион раз.
  • GIL (Global Interpreter Lock). Глобальная блокировка интерпретатора гарантирует, что в один момент времени байт-код Python выполняет только один поток. Вы не можете просто распараллелить вычисления чистого Python на 16 ядер процессора с помощью обычных потоков (threads) — они будут ждать друг друга.
  • Чтобы обойти эти ограничения в ML-сервисах, мы используем парадигму векторных вычислений.

    Векторизация и SIMD-архитектура

    Векторизация — это отказ от явных циклов for в Python в пользу операций над массивами целиком. Когда вы используете библиотеки вроде NumPy или PyTorch, вы вызываете функции, написанные на C или C++.

    Но настоящая скорость достигается не только за счет языка C, а за счет аппаратной поддержки SIMD (Single Instruction, Multiple Data).

    > SIMD — это архитектура процессора, позволяющая применить одну и ту же операцию (например, сложение) сразу к целому блоку данных за один такт процессора, а не обрабатывать элементы по одному.

    Представьте, что вам нужно сложить два массива по 1000 чисел.

  • Обычный цикл делает 1000 инструкций сложения.
  • SIMD-инструкция (например, AVX-512 в современных процессорах) может взять блок из 16 чисел с плавающей точкой из первого массива, 16 чисел из второго и сложить их за одну процессорную инструкцию. Количество операций сокращается в 16 раз.
  • !Сравнение скалярного цикла и SIMD-векторизации

    Чтобы SIMD работал эффективно, данные должны лежать в оперативной памяти непрерывным куском. И здесь мы подходим к самому важному архитектурному нюансу работы с массивами.

    Управление памятью: Strides и непрерывность

    Для процессора не существует двумерных или трехмерных матриц. Оперативная память одномерна. То, как многомерный массив укладывается в одномерную память, критически влияет на скорость работы ML-пайплайна.

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

  • C-order (Row-major): элементы укладываются строка за строкой. Это стандарт для NumPy и PyTorch.
  • F-order (Column-major, от Fortran): элементы укладываются столбец за столбцом.
  • !Расположение двумерного массива в одномерной памяти (C-order)

    Почему это важно для производительности? Процессор читает данные из оперативной памяти не по одному байту, а блоками (кэш-линиями). Если вы итерируетесь по матрице в C-order по строкам, процессор загружает первую строку в быстрый L1-кэш, и следующие элементы берутся уже оттуда почти мгновенно.

    Если же вы итерируетесь по той же C-order матрице по столбцам, каждый новый элемент находится далеко в памяти от предыдущего. Происходит «промах кэша» (cache miss), и процессор вынужден снова и снова обращаться к медленной оперативной памяти. Разница в скорости выполнения одной и той же операции может достигать 10-20 раз только из-за порядка обхода.

    Views vs Copies (Представления и Копии)

    Понимание того, как данные лежат в памяти, объясняет, почему некоторые операции в NumPy происходят мгновенно (за ), а другие занимают время и удваивают потребление памяти (за ).

    | Операция | Что происходит под капотом | Скорость и Память | | :--- | :--- | :--- | | View (Представление) | Массив не копируется. Создается новый объект-заголовок, который смотрит на те же данные в памяти, но с другими правилами чтения (strides). | Мгновенно, памяти. | | Copy (Копия) | Данные физически дублируются в новую область памяти. | Медленно, памяти. |

    Например, операция транспонирования матрицы matrix.T или изменение формы matrix.reshape() в большинстве случаев возвращают View. Изменяя элементы в транспонированной матрице, вы измените исходную. А вот вызов matrix.flatten() всегда создает Copy.

    !View vs Copy в NumPy

    Магия Broadcasting (Бродкастинг)

    Часто в ML-задачах нужно выполнить операцию над массивами разных размеров. Например, вычесть среднее значение канала из каждого пикселя изображения. Изображение имеет размерность (256, 256, 3), а вектор средних значений — (3,).

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

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

    Правила совместимости размерностей (сравниваются справа налево): Две размерности совместимы, если:

  • Они равны.
  • Одна из них равна 1.
  • Если у массивов разное количество измерений, к форме меньшего массива слева мысленно добавляются единицы.

    Пример из практики: У нас есть батч из 32 изображений: форма (32, 256, 256, 3). Мы хотим умножить каждый цветовой канал на свой коэффициент: форма (3,).

    Шаг 1: Выравниваем по правому краю. Массив A: 32 x 256 x 256 x 3 Массив B: 3

    Шаг 2: Дополняем B единицами слева. Массив A: 32 x 256 x 256 x 3 Массив B: 1 x 1 x 1 x 3

    Шаг 3: Проверяем совместимость столбцов. Везде либо числа равны (3 и 3), либо одно из них 1. Бродкастинг возможен! Результирующий массив будет иметь форму (32, 256, 256, 3).

    !Правила Broadcasting

    За пределами векторизации: Numba и JIT

    Что делать, если алгоритм принципиально не векторизуется? Например, вы пишете кастомную функцию потерь со сложной логикой ветвления (if-else), зависящей от предыдущих состояний, или реализуете специфический обход графа. Векторизовать это через NumPy невозможно, а чистый цикл for убьет производительность.

    В таких архитектурных сценариях применяется JIT-компиляция (Just-In-Time), лидером которой в экосистеме Python является библиотека Numba.

    Достаточно добавить декоратор @jit(nopython=True) перед вашей функцией. При первом вызове функции Numba проанализирует типы переданных аргументов, переведет байт-код Python в промежуточное представление LLVM и скомпилирует его в оптимизированный машинный код под вашу конкретную архитектуру процессора.

    Последующие вызовы этой функции будут выполняться со скоростью C-кода, полностью игнорируя GIL и медлительность интерпретатора Python. Это позволяет писать интуитивно понятные циклы for, сохраняя производительность высоконагруженного сервиса.

    Резюме

    Оптимизация производительности ML-сервиса начинается не с выбора более мощной видеокарты, а с эффективной подготовки данных на CPU. Понимание того, как Python работает с памятью (C-order), умение использовать SIMD через векторизацию, избегание лишних копий (Views vs Copies) и грамотное применение Broadcasting — это базовый инженерный фундамент, который позволяет масштабировать архитектуру от прототипа в Jupyter Notebook до production-системы, обрабатывающей терабайты данных.

    10. Системный дизайн ML-решений: проектирование архитектуры от сбора данных до выбора стратегии деплоя

    Системный дизайн ML-решений: проектирование архитектуры от сбора данных до выбора стратегии деплоя

    Вы потратили недели на сбор данных, обучили модель, настроили веса с помощью DPO и добились идеального выравнивания. Метрики на тестовой выборке бьют рекорды. Но в первый же день работы в production система ложится под нагрузкой в 100 запросов в секунду, а предсказания для реальных пользователей оказываются случайным шумом. Почему? Потому что обученная нейросеть — это лишь файл с весами. ML-система — это сложный инженерный механизм, где модель занимает от силы 5% кодовой базы, а успех зависит от того, как быстро вы доставляете к ней данные и как эффективно утилизируете железо.

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

    Разрыв между средами: проблема доставки признаков

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

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

    Чтобы разорвать эту зависимость, архитекторы используют паттерн Feature Store.

    > Feature Store — это централизованное хранилище признаков для машинного обучения, которое обеспечивает единый пайплайн вычисления фичей как для пакетного обучения (исторические данные), так и для инференса в реальном времени.

    Feature Store физически разделяет хранение на два контура:

  • Offline Store (на базе колоночных СУБД или Data Lake) — хранит терабайты исторических данных для формирования обучающих датасетов. Оптимизирован под высокую пропускную способность (throughput).
  • Online Store (на базе in-memory баз данных, например, Redis) — хранит только последние актуальные значения признаков для каждого объекта. Оптимизирован под минимальную задержку (latency).
  • !Понимание роли Feature Store

    Когда поступает запрос на предсказание (например, пользователь зашел на сайт), веб-сервис не вычисляет историю покупок с нуля. Он делает мгновенный запрос в Online Store по ID пользователя, забирает готовый вектор признаков и отправляет его в модель.

    Парадигмы инференса: когда вычислять предсказания?

    Решив проблему доставки данных, мы сталкиваемся со следующим архитектурным выбором: в какой момент времени модель должна делать предсказание? Неверный выбор здесь либо сожжет бюджет на серверы, либо заставит пользователя ждать.

    Существует три основные парадигмы инференса.

    1. Batch Inference (Офлайн-предсказания)

    Модель запускается по расписанию (например, раз в сутки), обрабатывает сразу все доступные данные и сохраняет результаты в базу данных. Когда пользователь заходит на сайт, backend просто читает готовое предсказание из базы.

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

    2. Online Inference (Синхронные предсказания)

    Модель оборачивается в микросервис и ждет запросов. Вычисление происходит строго в момент обращения клиента.

    * Плюсы: Реакция на самые свежие данные (например, пользователь только что положил товар в корзину, и рекомендации мгновенно обновились). * Минусы: Жесткие требования к скорости работы модели. Если модель тяжелая, пользователь будет смотреть на индикатор загрузки. * Пример: Перевод текста в браузере или генерация ответа LLM. Невозможно заранее сгенерировать ответы на все возможные промпты.

    3. Streaming Inference (Асинхронные предсказания на событиях)

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

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

    Оптимизация Online-инференса: борьба за утилизацию

    Если специфика задачи требует Online-инференса (особенно для тяжелых CV-моделей или LLM), возникает конфликт между задержкой (Latency) и пропускной способностью (Throughput).

    В классическом веб-сервере каждый запрос обрабатывается в отдельном потоке. Если мы будем отправлять в GPU по одному запросу (размер батча = 1), видеокарта будет простаивать, так как ее архитектура создана для параллельных вычислений. Но мы не можем ждать, пока накопится фиксированный батч из 32 запросов, потому что первый клиент уйдет из-за таймаута.

    Решением выступает Dynamic Batching (Динамическое пакетирование).

    > Dynamic Batching — механизм на стороне сервера инференса, который искусственно задерживает обработку первых поступивших запросов на крошечное окно времени (например, 5-10 миллисекунд), чтобы собрать их в один тензор и отправить в GPU единым батчем.

    Математически баланс системы описывается законом Литтла из теории массового обслуживания:

    Где: * — среднее количество запросов, одновременно находящихся в системе (включая очередь и обработку). * — интенсивность входящего потока (запросов в секунду). * — среднее время ответа для одного запроса.

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

    !Применение динамического батчинга

    Для реализации таких паттернов не используют обычный Flask или FastAPI. В production применяются специализированные сервера инференса: Triton Inference Server, TensorFlow Serving или vLLM (для языковых моделей). Они берут на себя управление очередями, динамический батчинг и работу с памятью GPU.

    Сетевые протоколы: как передавать тензоры

    Последний кирпичик архитектуры — протокол связи между backend-сервисом и сервером инференса.

    Стандартный REST API использует JSON. JSON — это текстовый формат. Если вам нужно передать изображение для детекции объектов (массив 3x1024x1024) или плотный эмбеддинг из RAG-системы, сериализация массива чисел в текст и обратный парсинг займут больше времени, чем сам проход нейросети.

    Поэтому в высоконагруженных ML-системах стандартом де-факто является gRPC (gRPC Remote Procedure Calls). Он использует бинарный формат Protobuf. Данные передаются в виде сжатого потока байтов, что сокращает объем сетевого трафика в несколько раз и практически обнуляет затраты CPU на сериализацию.

    Синтез: архитектура в сборе

    Сведем все концепции в единую картину на примере системы антифрода для банка.

  • Событие: Пользователь совершает перевод на 50 000 руб. Транзакция попадает в брокер сообщений Kafka (Streaming-архитектура).
  • Обогащение: ML-сервис читает событие и делает миллисекундный запрос в Redis (Online Feature Store), забирая агрегированные признаки: «количество переводов за час», «средний чек за месяц».
  • Инференс: Сервис формирует вектор и отправляет его по бинарному протоколу gRPC в Triton Inference Server.
  • Оптимизация: Triton ждет 5 миллисекунд, собирает эту транзакцию вместе с 15 другими переводами со всей страны в один батч и прогоняет через обученную модель.
  • Результат: Вероятность мошенничества возвращается в ML-сервис, который принимает решение заблокировать карту.
  • В этой цепочке сама модель — лишь математическая функция в центре огромного конвейера. Успех Senior-инженера заключается в умении спроектировать этот конвейер так, чтобы данные текли по нему без узких мест, а железо отрабатывало каждый вложенный рубль.

    11. Контейнеризация и оркестрация: Docker и Kubernetes для обеспечения воспроизводимости и масштабируемости ML-сервисов

    Контейнеризация и оркестрация: Docker и Kubernetes для обеспечения воспроизводимости и масштабируемости ML-сервисов

    В пятницу вечером рекомендательная система, которую мы заботливо спроектировали с использованием gRPC и Dynamic Batching, внезапно перестала отвечать на запросы. Логи показали ошибку сегментации в C++ биндингах PyTorch. Расследование выявило причину: системный администратор обновил библиотеку glibc на production-сервере, что нарушило бинарную совместимость с предкомпилированными модулями фреймворка. Идеальная архитектура инференса разбилась о проблему невоспроизводимости среды.

    Мы уже решили проблему Training-Serving Skew на уровне логики признаков, внедрив Feature Store. Теперь нам предстоит решить её на инфраструктурном уровне. ML-модели — это не просто Python-код. Это слоёный пирог из драйверов CUDA, системных библиотек (C/C++), тяжеловесных фреймворков и специфических версий зависимостей.

    Изоляция среды: почему venv недостаточно

    Виртуальные окружения Python (venv, conda) изолируют только пакеты уровня языка. Они опираются на ядро операционной системы и её системные библиотеки. Если модель обучена на Ubuntu 20.04 с CUDA 11.8, а деплоится на CentOS 8 с CUDA 12.1, шансы на успешный запуск стремятся к нулю.

    Решением выступает контейнеризация с помощью Docker. Контейнер упаковывает приложение вместе со всей его операционной средой (библиотеками, утилитами, конфигурационными файлами), за исключением ядра ОС.

    В отличие от виртуальных машин (VM), которые эмулируют всё оборудование и запускают полноценную гостевую ОС, контейнеры используют механизмы ядра Linux:

  • Namespaces (пространства имён) изолируют процессы, сеть и файловую систему. Контейнер «думает», что он один на сервере.
  • Cgroups (контрольные группы) ограничивают потребление ресурсов (CPU, RAM).
  • Для ML-сервисов легковесность контейнеров критична. Виртуальная машина отнимает гигабайты оперативной памяти только на поддержание гостевой ОС. Контейнер же позволяет отдать всю доступную память под загрузку весов LLM или батчей изображений.

    Анатомия идеального образа для ML

    Образ Docker собирается послойно на основе инструкций из Dockerfile. Каждый шаг создает новый неизменяемый слой. Если инструкция не изменилась, Docker берет готовый слой из кэша.

    В ML-проектах вес библиотек (PyTorch, TensorFlow) легко превышает 2–3 ГБ. Неправильный порядок слоёв приводит к тому, что при каждом изменении одной строчки кода CI/CD пайплайн будет заново скачивать гигабайты зависимостей.

    Сравним два подхода:

    В плохом подходе изменение любого файла (например, README.md или serve.py) инвалидирует кэш для шага COPY . /app. Следовательно, следующий шаг RUN pip install выполнится заново.

    Теперь изменение логики в serve.py затронет только третий шаг. Сборка займет секунды вместо минут. !Понимание механизма кэширования слоев Docker

    Для доступа контейнера к GPU используется NVIDIA Container Toolkit. Он «пробрасывает» драйверы хоста внутрь контейнера, позволяя коду обращаться к устройствам так, словно он запущен на физическом сервере.

    От контейнера к кластеру: оркестрация с Kubernetes

    Docker решает проблему «работает на моей машине». Но один контейнер не обеспечит отказоустойчивость. Что если сервер сгорит? Что если маркетинговая кампания увеличит поток запросов к нашему gRPC-сервису в 100 раз?

    Нам нужна система, которая будет управлять сотнями контейнеров, распределять их по серверам (нодам), следить за их здоровьем и балансировать сетевой трафик. Индустриальным стандартом для этого стал Kubernetes (K8s).

    Базовые абстракции K8s для инференса

    Kubernetes не работает с контейнерами напрямую. Он вводит собственные абстракции:

  • Pod (Под) — минимальная единица развертывания. Обычно это один контейнер с нашей ML-моделью. Под смертен: если он падает, K8s не чинит его, а убивает и создает новый.
  • Deployment (Развертывание) — контроллер, который следит, чтобы в кластере всегда работало заданное количество реплик Пода. Если указано 5 реплик, а одна нода вышла из строя (унеся с собой 2 пода), Deployment автоматически запустит 2 новых пода на других нодах.
  • Service (Сервис) — постоянная точка входа (IP-адрес и DNS-имя) для группы подов. Поды постоянно рождаются и умирают, их IP-адреса меняются. Service балансирует входящие gRPC-запросы только на живые и готовые к работе поды.
  • Специфика выделения ресурсов для ML-моделей

    В веб-разработке микросервисы часто могут работать в условиях жесткого ограничения ресурсов (троттлинга). В машинном обучении всё иначе.

    В спецификации Пода мы можем задать два параметра для каждого ресурса:

  • Requests (Запросы) — сколько ресурсов кластер гарантированно резервирует под контейнер. Нода не примет Под, если у неё нет свободных ресурсов объема Requests.
  • Limits (Лимиты) — жесткий потолок. Если контейнер превысит лимит памяти, ядро Linux немедленно убьет его (статус OOMKilled — Out Of Memory).
  • Для ML-моделей критически важно применять паттерн Guaranteed QoS (Quality of Service), при котором Requests строго равны Limits.

    > Разница между нехваткой CPU и RAM: > Если контейнеру не хватает CPU, он просто начинает работать медленнее (троттлинг). Если контейнеру не хватает RAM, он падает с фатальной ошибкой.

    Загрузка весов LLM в память детерминирована. Если модель весит 14 ГБ, а мы используем Dynamic Batching, который в пике требует еще 2 ГБ для активаций, мы обязаны установить Requests и Limits оперативной памяти на уровне 16 ГБ. Если мы поставим Requests = 8 ГБ (надеясь на «свободное место» на ноде), а Limits = 16 ГБ, то в момент пиковой нагрузки соседний под может занять память, и наша модель рухнет с OOMKilled.

    Выделение GPU в Kubernetes работает иначе, чем CPU или RAM. GPU — это целочисленный, несжимаемый ресурс.

    По умолчанию K8s не умеет делить одну видеокарту между несколькими подами (для этого требуются продвинутые технологии вроде NVIDIA MIG, которые мы оставим за рамками базовой оркестрации). Если вы запросили 1 GPU, карта монопольно отдается вашему контейнеру. !Выбор стратегии выделения ресурсов в K8s

    Горизонтальное автомасштабирование (HPA)

    Вернемся к сценарию с маркетинговой кампанией. Трафик вырос в 100 раз. Deployment поддерживает фиксированное число реплик, и наши текущие 3 пода просто захлебнутся в запросах, даже с учетом Dynamic Batching.

    Kubernetes предлагает механизм Horizontal Pod Autoscaler (HPA). Он автоматически увеличивает или уменьшает количество подов в Deployment на основе наблюдаемых метрик.

    Математика автомасштабирования опирается на пропорцию между текущим и целевым значением метрики:

    Где:

  • — вычисленное желаемое количество реплик.
  • — текущее количество реплик.
  • — текущее значение наблюдаемой метрики.
  • — целевое (желаемое) значение метрики.
  • — операция округления вверх до ближайшего целого числа.
  • Например, если сейчас работает 3 реплики (), текущая утилизация GPU составляет 90% (), а мы хотим держать её на уровне 60% (), то K8s вычислит: . Округлив вверх, HPA увеличит число подов до 5.

    Проблема стандартных метрик для ML

    В классическом IT автомасштабирование часто настраивают по утилизации CPU (например, масштабировать, если CPU > 80%). Для ML-инференса это губительно:

  • Инференс на GPU почти не нагружает CPU (процессор только перекладывает тензоры).
  • Потребление RAM статично (веса загружены в память навсегда).
  • Поэтому для ML-сервисов HPA настраивают на кастомные метрики (Custom Metrics). Идеальный кандидат — длина очереди запросов перед нашим механизмом Dynamic Batching, о котором мы говорили ранее. Если очередь начинает расти быстрее, чем видеокарта успевает её разгребать (нарушается закон Литтла), HPA мгновенно запрашивает новые поды с GPU.

    Резюме архитектурного слоя

    Мы упаковали наш код, веса и системные зависимости в неизменяемый Docker-образ, гарантировав, что модель одинаково работает и на ноутбуке исследователя, и на production-сервере. Затем мы передали управление этим образом в Kubernetes, который обеспечил отказоустойчивость через концепцию абстракций (Deployment, Service) и защиту от OOMKilled через Guaranteed QoS. Наконец, мы настроили автомасштабирование на основе длины очереди запросов.

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

    12. Автоматизация рабочих процессов: построение надежных ML-пайплайнов с использованием Airflow и Prefect

    Автоматизация рабочих процессов: построение надежных ML-пайплайнов с использованием Airflow и Prefect

    В 03:00 скрипт выгрузки данных упал из-за таймаута базы данных. В 04:00 скрипт обучения модели успешно запустился, прочитал пустой файл, оставшийся от предыдущего шага, обучил модель на нулевом датасете и в 05:00 раскатил её в production. Утром бизнес обнаружил, что рекомендательная система предлагает всем пользователям один и тот же дефолтный товар. Эта классическая катастрофа происходит, когда сложный жизненный цикл машинного обучения управляется набором разрозненных cron-задач, не знающих о состоянии друг друга.

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

    Парадигма DAG: от расписаний к графам зависимостей

    Чтобы система понимала, что шаг обучения не имеет смысла без успешного завершения шага подготовки данных, рабочие процессы моделируются в виде ориентированных ациклических графов.

    > DAG (Directed Acyclic Graph) — математическая абстракция, представляющая рабочий процесс. > Формально это граф , где — множество вершин (задач), а — множество направленных ребер (зависимостей), при этом в графе не существует пути, начинающегося и заканчивающегося в одной и той же вершине.

    Ацикличность — фундаментальное требование оркестрации. Если задача A ждет выполнения задачи B, а задача B ждет A, возникает бесконечная блокировка (deadlock). В ML-контексте DAG позволяет описать пайплайн: извлечение данных валидация генерация признаков обучение оценка деплой. Упавшая задача останавливает выполнение всех зависимых от нее потомков, предотвращая эффект домино.

    Идемпотентность: фундамент надежности

    Переход от скриптов к графам требует изменения подхода к написанию кода задач. Главное свойство любой задачи в надежном пайплайне — идемпотентность.

    В математике операция идемпотентна, если повторное её применение к объекту не меняет результат:

    где — исходное состояние системы, а — выполняемая функция (задача пайплайна).

    В контексте инженерии данных и ML это означает: сколько бы раз ни перезапускалась упавшая задача (из-за сетевого сбоя, нехватки памяти или ручного рестарта), итоговое состояние системы должно быть таким же, как если бы задача выполнилась успешно с первого раза.

    !Понимание идемпотентности

    Отсутствие идемпотентности делает пайплайн невосстановимым. Если пайплайн упал на середине, инженер должен иметь возможность нажать кнопку «Restart» без страха задублировать данные или сломать консистентность.

    Apache Airflow: индустриальный стандарт оркестрации

    Apache Airflow, разработанный в Airbnb, стал де-факто стандартом оркестрации пакетных процессов. Его архитектура построена вокруг строгого разделения логики управления и логики выполнения.

    Архитектура Airflow

  • Scheduler (Планировщик): Сердце системы. Постоянно парсит Python-файлы с определениями DAG, проверяет расписание и формирует задачи для выполнения.
  • Executor (Исполнитель): Механизм маршрутизации задач. В production обычно используется KubernetesExecutor (каждая задача запускается в изолированном поде) или CeleryExecutor (распределение по пулу воркеров).
  • Metadata Database: Реляционная БД (PostgreSQL/MySQL), хранящая состояние каждого запуска, историю и определения графов.
  • Webserver: UI для визуализации графов, просмотра логов и ручного управления (перезапуски, очистка состояний).
  • Проблема передачи состояния (XCom)

    Задачи в Airflow по умолчанию изолированы. Они могут выполняться на разных физических серверах. Как передать данные от шага препроцессинга к шагу обучения?

    В Airflow есть механизм XCom (Cross-Communication). Он позволяет задачам обмениваться небольшими сообщениями, которые сохраняются в Metadata DB. И здесь кроется главная ловушка для начинающих ML-инженеров.

    !Использование XCom в ML-пайплайнах

    Правильный паттерн в Airflow: задачи обмениваются только метаданными (URI файла в S3, идентификатор эксперимента, хеш коммита). Сами данные (веса моделей, тензоры, датасеты) пишутся в объектное хранилище или Feature Store.

    Prefect: Dataflow-парадигма и динамические графы

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

    Prefect представляет современное поколение оркестраторов, построенных вокруг парадигмы Dataflow.

    Ключевые отличия Prefect от Airflow

    | Характеристика | Apache Airflow | Prefect | | :--- | :--- | :--- | | Определение графа | Статическое (парсится до запуска) | Динамическое (формируется во время выполнения) | | Передача данных | Явные зависимости (task1 >> task2), данные через XCom | Передача Python-объектов (Futures) напрямую между функциями | | Парадигма кода | Использование специализированных Operators | Декораторы @task и @flow поверх обычного Python-кода | | Среда разработки | Требует поднятия инфраструктуры для тестирования | Запускается локально как обычный Python-скрипт |

    В Prefect зависимости графа строятся неявно, через передачу результатов одной функции в другую. Если функция train_model принимает на вход результат функции prepare_data, Prefect автоматически понимает, что вторая задача зависит от первой.

    Проектирование надежного ML-пайплайна

    Независимо от выбранного инструмента (Airflow для жестких корпоративных стандартов или Prefect для гибкой ML-разработки), архитектура пайплайна должна подчиняться правилам изоляции и наблюдаемости.

  • Изоляция окружений: Каждая задача должна быть самодостаточна. В Airflow это достигается через KubernetesPodOperator, где шаг валидации данных может использовать легковесный образ с Pandas, а шаг обучения — тяжелый образ с CUDA и PyTorch.
  • Сенсоры (Sensors): Специальный тип задач, который не выполняет работу, а ждет наступления события (появления файла в S3, готовности партиции в БД). Это связывает ML-пайплайн с внешним миром Data Engineering.
  • Backfilling (Ретроспективное выполнение): Способность пайплайна запуститься за исторический период. Если мы добавили новый признак, пайплайн должен уметь пересчитать данные за прошлый месяц. Для этого в каждую задачу прокидывается детерминированное время логического запуска (в Airflow это переменная execution_date), а не текущее системное время datetime.now().
  • Оркестратор решает проблему «когда» и «в каком порядке» запускать код. Но он не решает проблему версионирования самих артефактов: как связать конкретный запуск DAG с конкретным файлом весов model.pt и метриками качества, чтобы обеспечить полную воспроизводимость экспериментов.

    13. Инфраструктура MLOps: версионирование данных (DVC), моделей (MLflow) и мониторинг концептуального дрейфа

    Инфраструктура MLOps: версионирование данных (DVC), моделей (MLflow) и мониторинг концептуального дрейфа

    Представьте ситуацию: ваш Airflow-пайплайн строго и идемпотентно отрабатывает каждую ночь. Код не менялся уже месяц. Инфраструктура работает как часы. Тем не менее сегодня утром антифрод-система внезапно начала блокировать 30% легитимных транзакций. Вы открываете логи и видите, что пайплайн успешно обучил новую модель и выкатил её в production. Проблема не в коде и не в оркестраторе — проблема в изменившемся состоянии мира. Но чтобы откатиться к рабочей версии, вам нужно точно знать: на каких именно данных обучалась вчерашняя модель, с какими гиперпараметрами и где лежит её артефакт.

    В этой статье мы свяжем оркестрацию графов задач с управлением жизненным циклом артефактов и научимся математически отслеживать старение моделей в production.

    Кризис воспроизводимости и Data Version Control (DVC)

    В классической разработке ПО для воспроизводимости достаточно зафиксировать коммит в Git. В машинном обучении модель — это производная от трех компонентов: Модель = Код + Гиперпараметры + Данные.

    Git отлично справляется с кодом, но архитектурно не предназначен для хранения терабайтных датасетов или тяжелых весов нейросетей. Попытка закоммитить в Git файл размером 50 ГБ приведет к падению репозитория.

    Для решения этой проблемы используется DVC (Data Version Control) — инструмент, реализующий концепцию Content-Addressable Storage (хранилище, адресуемое по содержимому) для ML-артефактов.

    Как работает DVC под капотом

    Вместо того чтобы хранить сами данные в Git, DVC вычисляет MD5-хеш от содержимого большого файла (или директории) и создает легковесный текстовый файл-указатель с расширением .dvc.

  • Вы добавляете тяжелый датасет dataset.csv через команду DVC.
  • DVC копирует этот файл в свой скрытый локальный кэш, переименовывая его в хеш (например, a1b2c3d4...).
  • DVC генерирует файл dataset.csv.dvc, внутри которого записан этот хеш.
  • Вы коммитите файл dataset.csv.dvc в Git.
  • Вы отправляете тяжелые данные из кэша DVC в удаленное объектное хранилище (S3, GCS) через команду dvc push.
  • > DVC превращает проблему версионирования данных в проблему версионирования легковесных текстовых указателей, делегируя контроль версий Git, а хранение байтов — S3.

    !Как DVC обрабатывает изменения в данных

    MLflow: связывание пайплайнов и артефактов

    Теперь у нас есть версионированный код и версионированные данные. Но обучение модели — это итеративный процесс. Как понять, какой именно запуск Airflow-пайплайна сгенерировал конкретный файл model.pkl, и какие метрики качества при этом получились?

    Здесь на сцену выходит MLflow — платформа для управления жизненным циклом ML. Для Senior-инженера наибольший интерес представляют два её компонента: Tracking и Model Registry.

    MLflow Tracking

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

  • Параметры (Parameters): learning rate, размер батча, архитектуру.
  • Метрики (Metrics): Loss, ROC-AUC, F1-score на каждом этапе эпохи.
  • Теги (Tags): хеш Git-коммита, хеш DVC-файла, имя разработчика или ID Airflow-таски.
  • Артефакты (Artifacts): сами веса модели, графики валидации, логи.
  • MLflow Model Registry

    Когда эксперимент признан успешным, артефакт модели не просто лежит в S3 — он регистрируется в Model Registry. Это витрина моделей компании, где каждая модель имеет имя (например, fraud_detection_model) и версионируется.

    Реестр управляет статусами (Stages): Staging, Production, Archived. Ваш сервис инференса больше не скачивает модель по захардкоженному пути. Он запрашивает у MLflow: "Отдай мне веса модели fraud_detection_model, которая сейчас в статусе Production".

    Сборка пазла: как это выглядит в архитектуре

    | Инструмент | Зона ответственности | Что хранит | | :--- | :--- | :--- | | Git | Логика | Код обучения, .dvc файлы, манифесты Kubernetes | | DVC / S3 | Состояние данных | Бинарники датасетов, эмбеддинги, тяжелые фичи | | Airflow | Оркестрация | Граф задач (DAG), расписание, логика ретраев (без самих данных) | | MLflow | Метаданные ML | Метрики, параметры, статусы моделей (Staging/Prod) |

    Тихая смерть модели: мониторинг деградации

    Вы связали пайплайн, обучили модель на идеальных данных, проверили отсутствие утечек (Target Leakage) и выкатили её в Production. Но с первой же секунды работы модель начинает деградировать. Это происходит из-за дрейфа (Drift).

    В машинном обучении выделяют два фундаментально разных типа дрейфа.

    1. Data Drift (Ковариатный сдвиг)

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

    2. Concept Drift (Концептуальный сдвиг)

    Распределение признаков может оставаться стабильным, но меняется сама суть целевой переменной — зависимость . Пример: Модель кредитного скоринга обучена считать зарплату в 100 000 руб. "высокой" (низкий риск дефолта). Из-за гиперинфляции 100 000 руб. становятся прожиточным минимумом. Цифры на входе те же, но их экономический смысл и влияние на вероятность возврата кредита изменились кардинально.

    !Определение типа дрейфа в production

    Математическая оценка дрейфа: Population Stability Index (PSI)

    Ждать, пока упадут бизнес-метрики (например, вырастет доля невозврата кредитов) — слишком дорого. Нам нужен математический инструмент, который забьет тревогу на этапе инференса, как только входящие данные начнут отклоняться от тех, что мы зафиксировали в MLflow при обучении.

    Одной из индустриальных метрик для этого является PSI (Population Stability Index). Он измеряет, насколько распределение признака в текущем периоде (Production) отличается от базового распределения (Train).

    Формула PSI вычисляется для каждого признака отдельно:

    Где:

  • — количество корзин (бин), на которые мы разбиваем диапазон значений признака (обычно 10-20).
  • — доля наблюдений в -й корзине на обучающей (базовой) выборке.
  • — доля наблюдений в -й корзине на текущей выборке в production.
  • Математический смысл: PSI симметрично оценивает разницу долей. Если в определенную корзину значений (например, возраст "25-30 лет") при обучении попадало 20% пользователей (), а в production стало попадать 5% (), множитель сильно увеличит итоговое значение PSI.

    Индустриальные пороги PSI:

  • : Зеленая зона. Распределения практически идентичны, дрейфа нет.
  • : Желтая зона. Наблюдается легкий сдвиг. Требуется внимание и, возможно, планирование переобучения.
  • : Красная зона. Значительный дрейф. Модель работает в непредсказуемых условиях, требуется срочное переобучение (Retraining) или откат на эвристики.
  • > В MLOps-системах расчет PSI часто встраивается как отдельный DAG в Airflow, который ежесуточно забирает логи инференса, сравнивает их с распределениями из Feature Store на момент обучения и отправляет алерты, если PSI превышает 0.2.

    Грамотно выстроенная инфраструктура (DVC + MLflow + мониторинг PSI) позволяет не просто констатировать факт падения качества, а нажать одну кнопку, чтобы Airflow запустил переобучение на новых данных, MLflow автоматически сравнил новую модель с текущей Production-версией, а DVC гарантировал, что мы всегда сможем воспроизвести этот процесс.

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

    14. Оптимизация инференса: квантование, дистилляция и ускорение работы моделей в production-среде

    Оптимизация инференса: квантование, дистилляция и ускорение работы моделей в production-среде

    Представьте, что вы успешно дообучили LLaMA-3 70B под задачи вашей компании. Модель выдает блестящие результаты на тестах. Вы загружаете ее на сервер с двумя GPU A100 (по 80 ГБ VRAM каждая), запускаете production-трафик и... система выдает ошибку Out Of Memory (OOM) на десятом одновременном пользователе, а скорость генерации падает до мучительных 2 токенов в секунду. Почему мощнейшее железо за десятки тысяч долларов не справляется с задачей, которая казалась решенной?

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

    Арифметика бутылочного горлышка: Compute-bound vs Memory-bound

    Чтобы оптимизировать систему, нужно понимать, где она проводит больше всего времени. Любая операция в нейросети состоит из двух этапов:

  • Переместить данные (веса и активации) из медленной памяти (HBM) в быструю накристальную (SRAM).
  • Выполнить математическую операцию (FLOPs) в вычислительных блоках.
  • Ключевая метрика здесь — арифметическая интенсивность (Arithmetic Intensity). Это отношение количества выполненных математических операций к объему перекачанных из памяти байт.

    > Арифметическая интенсивность определяет режим работы модели: > - Compute-bound (ограничение по вычислениям): математики так много, что память успевает подвозить данные. Ускорение достигается за счет более мощных GPU. > - Memory-bound (ограничение по памяти): математики мало, вычислительные блоки простаивают в ожидании данных. Ускорение достигается только сжатием данных или оптимизацией доступа к ним.

    При обработке пользовательского промпта (Prefill phase) модель умножает большие матрицы. Это Compute-bound задача. Но при генерации ответа токен за токеном (Decode phase) модель каждый раз заново загружает все веса из HBM в SRAM, чтобы умножить их на вектор одного текущего токена. Это классическая Memory-bound задача.

    Именно поэтому инференс LLM такой медленный. Нам нужно радикально сократить объем данных, перемещаемых по шине памяти.

    Квантование: от PTQ к QAT

    Самый прямолинейный способ уменьшить объем перекачиваемых данных — изменить их тип. Ранее мы обсуждали формат NF4 для эффективного Fine-Tuning. В production-среде для инференса стандартом является линейное квантование в форматы INT8 или INT4.

    Суть линейного квантования описывается простым уравнением маппинга вещественных чисел в целые:

    Где:

  • — исходное вещественное значение (например, FP16).
  • — Scale, масштабирующий множитель (вещественное число).
  • — квантованное целочисленное значение (например, INT8 от -128 до 127).
  • — Zero-point, целочисленное смещение, гарантирующее, что реальный ноль точно отображается в квантованный ноль (критично для операций вроде ReLU).
  • Две парадигмы внедрения квантования

    | Характеристика | Post-Training Quantization (PTQ) | Quantization-Aware Training (QAT) | | :--- | :--- | :--- | | Суть | Берем готовую FP16 модель и математически сжимаем веса до INT8/INT4. | Модель обучается (или дообучается) с симуляцией квантования на прямом проходе. | | Ресурсы | Требует минут/часов на CPU, не требует данных (или нужен небольшой калибровочный датасет). | Требует полноценного цикла обучения на GPU с обучающей выборкой. | | Деградация качества | Заметна при агрессивном сжатии (INT4 и ниже), особенно для моделей меньше 10B параметров. | Минимальна. Модель сама адаптирует свои веса так, чтобы минимизировать ошибку округления. |

    !Выбор стратегии квантования

    PTQ — это индустриальный стандарт для быстрого деплоя. Однако, если при сжатии до INT4 метрики вашей модели необратимо падают (из-за выбросов в активациях, которые "сплющивают" весь полезный диапазон чисел), архитектурно правильным шагом будет переход к QAT.

    Дистилляция знаний (Knowledge Distillation)

    Если квантование сжимает данные, то дистилляция сжимает саму архитектуру. Что, если нам не нужна универсальная модель на 70B параметров, а нужен быстрый классификатор или генератор кода на 7B параметров, который работает так же хорошо?

    Мы используем парадигму Teacher-Student. Большая модель-учитель обучает маленькую модель-ученика. Ключевой инсайт дистилляции: ученик должен учиться не на жестких ответах (Hard labels: 1 для правильного класса, 0 для остальных), а на вероятностном распределении учителя (Soft labels).

    Представьте классификацию изображений. Для фотографии кота жесткий ответ: [Кот: 1.0, Собака: 0.0, Машина: 0.0]. Но модель-учитель выдает вероятности: [Кот: 0.85, Собака: 0.14, Машина: 0.01].

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

    Чтобы усилить этот эффект, в функцию Softmax добавляется параметр Температуры ():

    Где:

  • — итоговая вероятность -го класса.
  • — логит (сырое предсказание сети) для -го класса до нормализации.
  • — температура. При это обычный Softmax. При распределение становится более "плоским", сглаживая разницу между уверенными и неуверенными предсказаниями.
  • Обучая студента предсказывать сглаженное распределение учителя при высокой температуре (например, ), мы заставляем компактную модель перенять сложную внутреннюю логику гиганта.

    Спекулятивное декодирование (Speculative Decoding)

    Что делать, если дистилляция дала нам быструю маленькую модель (Student), но ее качества все равно чуть-чуть не хватает для production, и нам приходится использовать большую (Teacher)? Мы можем заставить их работать в тандеме прямо во время инференса.

    Спекулятивное декодирование решает проблему Memory-bound генерации элегантным алгоритмическим трюком:

  • Черновик (Drafting): Маленькая, быстрая модель (например, 7B) авторегрессионно генерирует токенов (например, 4 токена). Поскольку модель мала, загрузка ее весов из HBM происходит быстро.
  • Проверка (Verification): Большая целевая модель (70B) берет эти токенов и прогоняет их через себя за один параллельный прямой проход (как при обработке промпта, что является Compute-bound операцией и эффективно утилизирует GPU).
  • Принятие решений: Большая модель оценивает вероятности сгенерированных токенов. Если они совпадают с тем, что сгенерировала бы она сама — токены принимаются. Как только целевая модель не согласна с токеном черновика, она отбрасывает его и все последующие, заменяя на свой правильный токен.
  • > Парадокс спекулятивного декодирования: > Математически доказано, что итоговый текст, полученный этим методом, абсолютно идентичен тексту, который сгенерировала бы большая целевая модель в одиночку. Мы не жертвуем качеством ради скорости.

    !Влияние спекулятивного декодирования

    Если маленькая модель угадывает часто, мы получаем несколько токенов за время генерации одного. Это может ускорить инференс в 2-3 раза без потери качества.

    PagedAttention: виртуальная память для KV-Cache

    Мы сжали веса (Квантование), научились использовать быстрые модели-черновики (Спекулятивное декодирование). Последний враг в борьбе за пропускную способность и память — это KV-Cache.

    Как мы разбирали ранее, KV-Cache хранит состояния внимания для прошлых токенов, чтобы не пересчитывать их. Проблема production-среды в том, что длина ответа заранее неизвестна. Стандартные фреймворки резервируют непрерывный кусок памяти в GPU под максимально возможную длину ответа (например, 4096 токенов). Если ответ занимает всего 100 токенов, оставшиеся 3996 слотов простаивают. Это называется внутренней фрагментацией. В результате, даже при наличии свободной памяти, сервер не может взять новые запросы в батч.

    Архитектура PagedAttention (популяризованная фреймворком vLLM) решает это, заимствуя концепцию виртуальной памяти из операционных систем:

  • KV-Cache разбивается на блоки фиксированного размера (например, по 16 токенов).
  • Блоки не обязаны лежать в физической памяти GPU непрерывно.
  • Система поддерживает таблицу страниц (Block Table), которая мапит логические токены запроса на физические блоки в памяти.
  • Когда генерируется новый токен, система просто выделяет новый блок, если текущий заполнен. Результат? Фрагментация памяти падает с 60% до 4%. Это позволяет увеличить размер батча (Dynamic Batching) в несколько раз на том же самом оборудовании, радикально повышая пропускную способность ML-сервиса.

    Резюме архитектурного подхода

    Оптимизация инференса на Senior-уровне — это не поиск одной "волшебной кнопки". Это каскад инженерных решений: Мы анализируем узкое место (Memory-bound) сжимаем типы данных (INT8/PTQ) если теряем качество, переходим к QAT или дистиллируем знания в компактную архитектуру объединяем сильные стороны большой и малой моделей через Speculative Decoding и, наконец, утилизируем освободившуюся память по максимуму с помощью PagedAttention для обслуживания сотен пользователей одновременно.

    15. Финальный синтез: прохождение System Design интервью и защита архитектурных решений перед бизнесом

    Финальный синтез: прохождение System Design интервью и защита архитектурных решений перед бизнесом

    На техническом скрининге кандидату дают задачу: «Спроектируйте систему рекомендаций для главной страницы». Кандидат тратит 35 минут на детальное описание архитектуры Two-Tower модели, расписывает функцию потерь и методы сэмплинга негативных примеров. Интервьюер вежливо кивает, а через день приходит отказ. Причина? Кандидат спроектировал идеальную модель в вакууме, забыв спросить, сколько у сервиса пользователей, каков лимит на время ответа (SLA) и откуда берутся данные в реальном времени.

    Переход от Middle к Senior — это смена фокуса с «как обучить модель» на «как эта модель решает задачу бизнеса в условиях жестких инфраструктурных ограничений». В этой финальной главе мы соберем воедино все концепции, пройденные ранее, и применим их к формату System Design интервью.

    Анатомия ML System Design интервью

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

    Успешное прохождение строится на строгом фреймворке из четырех шагов:

  • Clarification (Прояснение требований): Перевод абстрактной бизнес-задачи в инженерные метрики и ограничения.
  • High-Level Design (Верхнеуровневая архитектура): Отрисовка основных компонентов системы (источники данных, пайплайны, сервинг) без погружения в детали.
  • Deep Dive (Глубокое погружение): Выбор ML-моделей, стратегий валидации и особенностей обучения.
  • Bottlenecks & Scale (Узкие места и масштабирование): Защита архитектуры от пиковых нагрузок, отказов и деградации данных.
  • > System Design интервью — это тест на умение управлять неопределенностью. Ваша первая задача — сузить бесконечное пространство возможных решений до одного жизнеспособного.

    !С чего начать дизайн системы

    Шаг 1: От бизнес-цели к инженерным ограничениям

    Бизнес говорит на языке денег и вовлеченности (DAU, Conversion Rate, Churn). Инженеры говорят на языке задержек, пропускной способности и метрик качества (Latency, Throughput, ROC-AUC, Groundedness). Ваша задача на первых минутах интервью — связать эти два мира.

    Сравнительная таблица перевода требований:

    | Бизнес-требование | ML-метрика | Инфраструктурное ограничение (SLA) | | :--- | :--- | :--- | | «Хотим чат-бота для саппорта, чтобы снизить нагрузку на операторов» | Answer Relevance, Groundedness | сек, USD за запрос | | «Нужно находить мошеннические транзакции до списания средств» | ROC-AUC, Precision при фиксированном Recall | мс, Streaming Inference | | «Рекомендации товаров в еженедельной email-рассылке» | NDCG, MAP@K | Batch Inference (ночной пересчет), важнее |

    На этапе сбора требований необходимо провести базовое Capacity Planning (планирование мощностей). Если вам говорят, что у сервиса 1 миллион активных пользователей в день (DAU), каждый делает в среднем 10 запросов, вы должны уметь на лету прикинуть нагрузку:

    запросов в секунду.

    Зная (Queries Per Second) и целевой , вы можете обосновать выбор между тяжелой LLM и быстрой классической моделью.

    Шаг 2 и 3: Синтез архитектуры (Кейс: Enterprise AI-ассистент)

    Давайте применим весь арсенал нашего курса для решения классической задачи Senior-интервью: «Спроектируйте внутреннего AI-ассистента для сотрудников банка, который отвечает на вопросы по регламентам и кредитным продуктам».

    Выбор парадигмы: RAG против Fine-tuning

    Опираясь на знания о природе LLM, мы понимаем, что регламенты банка меняются часто. Дообучение (даже эффективное) не подходит для обновления фактов. Решение: Мы выбираем архитектуру RAG с двухэтапным поиском.

    Проектирование пайплайнов

    Мы разбиваем систему на два независимых контура:

  • Offline-контур (Индексация данных):
  • * Данные (PDF, Confluence) версионируются через DVC для обеспечения воспроизводимости. * Оркестратор запускает DAG по расписанию: парсинг, чанкинг с перекрытием (Overlap), векторизация через Bi-encoder. * Векторы складываются в векторную БД.

  • Online-контур (Инференс и генерация):
  • * Пользователь отправляет запрос через gRPC (для минимизации накладных расходов сети). * Bi-encoder извлекает топ-100 чанков. * Cross-encoder выполняет Re-ranking, оставляя топ-5 релевантных кусков. * Промпт с контекстом отправляется в LLM.

    Адаптация LLM под домен

    Базовая модель (например, LLaMA) может отвечать слишком вольно. Нам нужен строгий банковский тон. Решение: Мы применяем QLoRA. Обучаем легковесный адаптер на исторических диалогах лучших операторов. При деплое мы выполняем Weight Merging — сливаем веса адаптера с базовой моделью, чтобы не платить штраф за скорость на этапе инференса.

    Шаг 4: Защита решений и работа с узкими местами

    Интервьюер обязательно попытается сломать вашу систему. Умение защитить архитектуру — это понимание концепции Trade-off (компромисса). В инженерии нет идеальных решений, есть только решения, подходящие под текущие ограничения.

    Атака интервьюера: «Ваш Cross-encoder на этапе Re-ranking работает слишком медленно, мы не укладываемся в SLA 2 секунды. Что будем делать?»

    Здесь вы должны достать инструменты оптимизации инференса. Ваш ответ: > «Мы находимся в зоне Compute-bound ограничений. Чтобы ускорить вычисления без сильной потери качества, я применю PTQ (Post-Training Quantization) к весам Cross-encoder, снизив точность до 8 бит. Если этого не хватит, мы заменим Cross-encoder на более легкую модель-ученика, обученную через дистилляцию знаний от текущей тяжелой модели, используя высокую Температуру в Softmax для передачи скрытых распределений».

    !Оптимизация инференса LLM под нагрузкой

    Атака интервьюера: «Утром все сотрудники банка приходят на работу и одновременно открывают ассистента. GPU простаивают из-за неэффективного использования памяти, сервис падает с OOM (Out of Memory).»

    Здесь вы обращаетесь к инфраструктуре и оркестрации. Ваш ответ: > «Во-первых, на уровне Kubernetes я настрою HPA (Horizontal Pod Autoscaler), который будет масштабировать поды не по CPU, а по кастомной метрике — длине очереди запросов. Во-вторых, для решения проблемы фрагментации KV-Cache внутри самой LLM, я буду использовать сервер инференса с поддержкой PagedAttention. Наконец, я включу Dynamic Batching, чтобы группировать асинхронные утренние запросы в единые батчи, повышая арифметическую интенсивность и утилизацию GPU».

    От инженера к архитектору

    Архитектура ML-систем — это не набор изолированных скриптов в Jupyter Notebook. Это живой организм.

    Вы начали этот курс с понимания того, как Python работает с памятью на уровне SIMD-инструкций, и прошли путь до проектирования распределенных кластеров, способных обслуживать миллиарды параметров. Вы знаете, почему внутренний сдвиг ковариаты мешает обучению, как причинная маска делает возможной авторегрессию, и почему метрика PSI заставит вас переобучить модель через полгода после деплоя.

    Senior Data Scientist отличается от Middle не тем, что знает больше алгоритмов. Он отличается тем, что видит весь путь данных: от сырого лога в базе до изменения бизнес-показателей на дашборде CEO. И теперь вы тоже его видите.

    2. Математический фундамент Senior-уровня: продвинутая статистика и теория оптимизации в обучении моделей

    Математический фундамент Senior-уровня: продвинутая статистика и теория оптимизации в обучении моделей

    Мы можем довести утилизацию CPU и GPU до предела, выровнять массивы в памяти и заставить SIMD-инструкции обрабатывать батчи за микросекунды. Но вычислительная скорость абсолютно бесполезна, если миллион быстрых шагов ведет алгоритм в неверном направлении. Обучение современной нейросети — это не проблема скорости вычислений, это проблема навигации в пространстве, где количество измерений исчисляется миллиардами.

    Чтобы модель действительно обучалась, а не просто сжигала электричество, необходимо понимать топологию этого пространства и статистическую природу шагов, которые мы в нем делаем.

    Иллюзия локальных минимумов и реальность седловых точек

    Интуиция, которую мы получаем из двухмерных графиков функции потерь, где алгоритм скатывается в аккуратную ямку (локальный минимум), катастрофически ломается в многомерных пространствах.

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

    > Главный враг оптимизации глубоких сетей — не локальные минимумы, а седловые точки и обширные плато. > > Ян Лекун, Yoshua Bengio, Geoffrey Hinton: Deep Learning (Nature, 2015)

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

    Экспоненциальное скользящее среднее как физическая инерция

    Чтобы выйти из седловой точки, алгоритм должен помнить свое предыдущее направление. Это достигается за счет накопления градиентов через экспоненциальное скользящее среднее (EMA). Мы вводим понятие момента (Momentum).

    Формула обновления скорости принимает вид:

    Где:

  • — новый вектор скорости (направление шага).
  • — текущий вектор скорости.
  • — коэффициент сохранения импульса (обычно около 0.9).
  • — градиент функции потерь на текущем шаге.
  • — веса модели.
  • !Влияние коэффициента Momentum на сходимость

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

    Адаптивная оптимизация: почему Adam победил, и где он ошибается

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

    Алгоритм Adam решает это, вычисляя независимую скорость обучения для каждого параметра. Он делит накопленный момент (EMA первого порядка) на корень из накопленного квадрата градиентов (EMA второго порядка). Если градиент параметра часто бывает большим, его индивидуальный шаг искусственно уменьшается.

    Однако на Senior-интервью часто звучит вопрос: почему в современных архитектурах используют AdamW, а не классический Adam? Ответ кроется в математической разнице между L2-регуляризацией и убыванием весов (Weight Decay).

    В классическом SGD эти понятия математически эквивалентны. Убывание весов применяется напрямую к параметру:

    Где:

  • — обновленный вес.
  • — коэффициент убывания весов.
  • — текущий вес.
  • — скорость обучения (Learning Rate).
  • — градиент функции потерь.
  • В Adam добавление L2-штрафа в функцию потерь приводит к тому, что градиент штрафа также масштабируется знаменателем адаптивного шага. Параметры с большими градиентами (которые мы больше всего хотим регуляризовать) получают меньший штраф. AdamW исправляет это, вынося операцию Weight Decay за пределы адаптивного вычисления шага, применяя ее напрямую к весам.

    Статистическая стабильность: борьба со сдвигом ковариаты

    Оптимизатор вычисляет шаг на основе батча данных. Батч — это выборка из генеральной совокупности. Если распределение входов в слой нейросети меняется на каждом шаге (потому что предыдущий слой только что обновил свои веса), оптимизатор пытается попасть в движущуюся мишень. Этот феномен называется внутренним сдвигом ковариаты (Internal Covariate Shift).

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

    Для нормализации вычисляются среднее и дисперсия :

    Где:

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

    | Характеристика | Batch Normalization | Layer Normalization | | :--- | :--- | :--- | | Ось усреднения | По размерности батча (по одному признаку для всех примеров) | По размерности признаков (по всем признакам для одного примера) | | Зависимость от батча | Критически зависит. При размере батча < 8 статистика становится шумной и ломает обучение. | Не зависит. Нормализация происходит внутри одного примера. | | Работа с последовательностями | Плохо. Разная длина текстов приводит к смещению нулей (padding) в статистику батча. | Отлично. Каждый токен нормализуется независимо от длины батча. | | Применение | Компьютерное зрение (CNN, ResNet) | NLP, Трансформеры, LLM |

    !Выбор нормализации для архитектуры

    Понимание того, что Layer Norm изолирует примеры друг от друга, делая обучение независимым от размера батча, является фундаментальным для проектирования современных LLM. Это позволяет нам обучать гигантские модели, где в память GPU помещается батч размером всего в 1 или 2 примера.

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

    3. Проектирование стратегий валидации и обработки данных для предотвращения утечек в сложных системах

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

    Модель выявления мошеннических транзакций показывает выдающийся ROC-AUC 0.98 на кросс-валидации. Команда с триумфом разворачивает сервис, но в первые же дни production-эксплуатации метрика падает до 0.55 — модель работает немногим лучше случайного угадывания. Вы можете идеально настроить оптимизатор AdamW и успешно обойти все седловые точки многомерного ландшафта потерь, как мы разбирали ранее, но если сам этот ландшафт построен на искаженных данных, математика бессильна. Модель просто выучила «будущее», к которому у нее не будет доступа в реальности.

    Фундаментальная задача Senior ML-инженера при проектировании системы — гарантировать, что процесс оценки модели строго симулирует условия ее будущего применения. Любое нарушение этого принципа приводит к утечке данных (Data Leakage).

    Анатомия утечки данных

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

    Первый класс — Target Leakage (утечка целевой переменной). Она возникает на этапе конструирования признаков (Feature Engineering). В обучающую выборку попадают предикторы, значения которых формируются после наступления целевого события или напрямую зависят от него.

    Классический пример: предсказание оттока клиентов (Churn Prediction). Если среди признаков есть «дата последнего звонка в службу удержания клиентов», модель быстро поймет, что наличие этой даты гарантирует отток. В production, когда модель должна предсказать риск ухода заранее, этого звонка еще не существует.

    !Что такое Target Leakage

    Второй класс — Train-Test Contamination (смешивание выборок). В этом случае сами признаки могут быть корректными, но нарушена изоляция между обучающим и валидационным множествами. Модель «подсматривает» в тестовую выборку из-за неверной стратегии разбиения (сплитования) или ошибок в конвейере предобработки.

    > Валидационная выборка должна быть не просто отложенной частью данных, а точной симуляцией того состояния информационного поля, в котором система окажется в момент production-инференса.

    Временная валидация и парадокс «путешествия во времени»

    Самый надежный способ спровоцировать Train-Test Contamination в бизнес-задачах — использовать стандартную K-Fold кросс-валидацию со случайным перемешиванием для данных, имеющих временную природу.

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

    Для предотвращения временной утечки применяется Walk-forward validation (валидация скользящим или расширяющимся окном). Обучающая выборка охватывает интервал , а валидационная строится строго на интервале , где — точка отсечения в историческом времени, а — горизонт прогнозирования.

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

    | Характеристика | Expanding Window (Расширяющееся окно) | Sliding Window (Скользящее окно) | | :--- | :--- | :--- | | Принцип работы | Размер обучающей выборки увеличивается с каждым шагом, фиксируя начало отсчета. | Обучающая выборка имеет фиксированный размер, старые данные отбрасываются по мере движения окна. | | Преимущества | Модель видит всю доступную историю, улавливая долгосрочные макро-тренды. | Модель быстрее адаптируется к недавним изменениям, экономя вычислительные ресурсы. | | Когда применять | Стабильные рынки, задачи с сильной сезонностью и медленно меняющимися паттернами. | Высокодинамичные среды (биржевые котировки, новостные ленты), где старые данные теряют релевантность. |

    Выбор между этими подходами диктуется скоростью устаревания концепций в ваших данных.

    Утечка через сущности: почему случайное разбиение обманчиво

    Даже если данные не имеют строгой временной зависимости, случайное разбиение по строкам может привести к катастрофической утечке, если в датасете присутствуют множественные записи для одних и тех же объектов (сущностей).

    Рассмотрим систему компьютерного зрения для анализа медицинских снимков. В датасете 1000 рентгенограмм, принадлежащих 200 пациентам (в среднем по 5 снимков на человека). Если применить случайный сплит 80/20, снимки одного и того же пациента с вероятностью около 99% окажутся одновременно и в train, и в test.

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

    Для решения этой архитектурной проблемы применяется Group-level split (например, GroupKFold). Разбиение происходит не по строкам, а по уникальным идентификаторам сущностей (ID пациента, ID сессии, ID устройства). Если пациент попал в обучающую выборку, все его снимки остаются там; тестовая выборка состоит исключительно из абсолютно новых для модели пациентов.

    Иллюзия независимости: утечка на этапе препроцессинга

    Самая коварная форма Train-Test Contamination скрывается в безобидных на первый взгляд операциях предобработки данных. Она возникает, когда трансформации применяются ко всему датасету до его разделения на обучающую и валидационную части.

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

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

    !Какие операции вызывают утечку

    Архитектурно правильный подход требует строгой изоляции:

  • Разделить данные на train и test (с учетом времени и групп).
  • Обучить (fit) все объекты предобработки (скейлеры, импутеры, векторизаторы текста) строго на обучающей выборке.
  • Применить трансформацию (transform) к обучающей и тестовой выборкам.
  • В production-системах это реализуется через паттерн ML Pipeline. Пайплайн инкапсулирует в себе всю последовательность преобразований и саму модель, гарантируя, что при вызове метода предсказания на новых данных будут использованы только те статистики, которые были сохранены на этапе обучения.

    Синтез: проектирование репрезентативной валидации

    Разработка стратегии валидации — это процесс обратного инжиниринга условий эксплуатации. Senior-специалист не просто вызывает функцию разбиения, а задает себе три вопроса:

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

    4. Глубинное обучение: архитектурные нюансы трансформеров и механизмов внимания за пределами стандартных библиотек

    Глубинное обучение: архитектурные нюансы трансформеров и механизмов внимания за пределами стандартных библиотек

    В 2020 году модель GPT-3 поразила мир, имея контекстное окно в 2048 токенов. Сегодня модели вроде Gemini обрабатывают миллионы токенов за один проход. Почему этот переход занял несколько лет и потребовал переосмысления работы с памятью на уровне железа? Ответ кроется в фундаментальном узком месте механизма внимания — квадратичной сложности. На уровне Senior-разработчика недостаточно уметь вызывать nn.Transformer из PyTorch. Необходимо понимать, как именно матрицы внимания взаимодействуют с кэшем графического процессора, почему индустрия отказалась от оригинальной архитектуры из статьи «Attention Is All You Need» и как векторы осознают свою позицию в пространстве без рекуррентности.

    Анатомия Self-Attention: больше, чем просто поиск

    В основе трансформеров лежит механизм Self-Attention (внимание к самому себе). Его задача — для каждого токена в последовательности определить, насколько важны все остальные токены для понимания его смысла.

    Рассмотрим фразу: «Сотрудник банка одобрил кредит клиенту, потому что он предоставил справку». К кому относится «он» — к сотруднику или клиенту? Механизм внимания решает эту задачу через три проекции входных данных: Query (запрос), Key (ключ) и Value (значение).

    Представьте это как систему поиска в базе данных:

  • Query (Q): Токен «он» формирует запрос: «Я ищу существительное мужского рода, которое может что-то предоставлять».
  • Key (K): Все остальные токены выступают как ключи-описания. Токен «клиенту» имеет ключ: «Я получатель услуги, я приношу документы».
  • Value (V): Фактическое смысловое содержание токена, которое будет передано дальше, если ключ совпадет с запросом.
  • Математически это выражается формулой:

    Где , , — матрицы запросов, ключей и значений, а — размерность вектора ключа.

    Ключевая операция здесь — матричное умножение . Это вычисление скалярного произведения между каждым запросом и каждым ключом. Чем больше векторы сонаправлены в многомерном пространстве, тем выше результат скалярного произведения, и тем сильнее будет связь между токенами.

    > Масштабирующий множитель критически важен. При больших размерностях векторов скалярное произведение выдает очень большие по модулю значения. Функция softmax от таких значений превратится в вектор, где одно значение равно 1, а остальные 0. Это приведет к исчезновению градиентов при обратном распространении ошибки. Деление на корень из размерности возвращает дисперсию значений к единице.

    !Интерактивная визуализация механизма QKV

    Чтобы модель могла одновременно обращать внимание на разные аспекты (например, одна «голова» ищет синтаксические связи, другая — смысловые, третья — ролевые), используется Multi-Head Attention (MHA). Входные векторы проецируются в несколько независимых подпространств меньшей размерности, в каждом из которых параллельно считается свой Self-Attention. Результаты конкатенируются и умножаются на финальную матрицу весов.

    Эволюция архитектуры: почему Post-LN больше не используют

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

    В оригинальной статье 2017 года применялась архитектура Post-LN. Нормализация происходила после сложения выхода механизма внимания (или Feed-Forward сети) с residual-связью.

    Современные модели (LLaMA, GPT-4, Mistral) используют архитектуру Pre-LN. В ней нормализация применяется к входу до механизма внимания, а residual-связь обходит нормализацию стороной.

    !Сравнение архитектур Pre-LN и Post-LN

    Почему произошел этот сдвиг? В Post-LN градиенты на обратном проходе вынуждены каждый раз проходить через слой нормализации. При глубоких сетях (десятки слоев) это приводит к нестабильности градиентов на ранних этапах обучения, что требует сложных схем разогрева learning rate (warmup).

    В Pre-LN residual-связь формирует прямую, ничем не прерываемую магистраль от самого последнего слоя к самому первому. Градиенты текут по этой магистрали свободно, что делает обучение глубоких сетей исключительно стабильным даже без долгого разогрева.

    !Проверка понимания потока градиентов в Pre-LN

    Проблема порядка: Rotary Position Embedding (RoPE)

    Механизм Self-Attention сам по себе инвариантен к перестановкам. Если перемешать слова в предложении, матрица просто поменяет порядок строк и столбцов, но сами значения внимания между конкретными парами слов останутся теми же. Для языка порядок слов критичен.

    Изначально использовались абсолютные позиционные кодировки (синусы и косинусы), которые просто прибавлялись к эмбеддингам токенов на входе. Но они плохо обобщаются на последовательности, длина которых превышает длину, виденную при обучении.

    Современный стандарт — Rotary Position Embedding (RoPE). Вместо сложения, RoPE поворачивает векторы Query и Key в многомерном пространстве на угол, кратный позиции токена в последовательности.

    Математическая магия RoPE заключается в том, что скалярное произведение двух повернутых векторов зависит только от относительного угла между ними (то есть от разницы их позиций), а не от их абсолютных координат. Если токен стоит на 5 позиций раньше токена , их скалярное произведение будет учитывать именно эту дистанцию «5», независимо от того, находятся ли они в начале документа или на тысячной странице. Это позволяет современным LLM эффективно экстраполировать контекст на невиданные ранее длины.

    FlashAttention: битва за SRAM

    Главное архитектурное проклятие трансформеров — матрица внимания , где — длина последовательности. Для 1000 токенов это миллион элементов. Для 100 000 токенов — 10 миллиардов. Память и вычисления растут квадратично — .

    Долгое время исследователи пытались создать «Sparse Attention» (разреженное внимание), вычисляя не все ячейки матрицы. Но оказалось, что главная проблема — это не количество математических операций (FLOPs), а ввод-вывод (I/O).

    GPU имеет огромную, но относительно медленную HBM (High Bandwidth Memory) память (например, 80 ГБ в A100), и крошечную, но невероятно быструю SRAM-память прямо на чипе (около 40 МБ).

    Стандартная реализация внимания делает так:

  • Читает и из HBM в SRAM.
  • Считает и записывает огромную матрицу обратно в HBM.
  • Читает из HBM, считает softmax, пишет результат в HBM.
  • Читает результат и из HBM, умножает, пишет в HBM.
  • Большую часть времени GPU просто ждет, пока данные перекачаются из медленной памяти в быструю и обратно.

    FlashAttention — это алгоритм, который решает проблему на аппаратном уровне. Он использует технику tiling (мощение). Матрицы разбиваются на небольшие блоки, которые помещаются в SRAM. Алгоритм вычисляет softmax и умножение на блоками, ни разу не записывая промежуточную матрицу в HBM.

    Результат: FlashAttention не меняет математику (результат побитово совпадает со стандартным вниманием), но ускоряет работу в 2–4 раза и снижает потребление памяти с до . Именно эта архитектурная оптимизация позволила моделям совершить скачок от контекста в 2K токенов к контекстам в сотни тысяч.

    !Проверка понимания узкого места в Self-Attention

    Понимание этих низкоуровневых механизмов — от нормализации до работы с памятью GPU — отличает инженера, который просто использует API, от архитектора, способного проектировать и оптимизировать высоконагруженные ML-системы. Базовый двигатель мы собрали. Далее мы рассмотрим, как этот двигатель применяется в архитектуре Decoder-only и как сырой текст превращается в числа на этапе токенизации.

    5. Компьютерное зрение: от классической детекции объектов к современным архитектурам YOLO и сегментации

    Компьютерное зрение: от классической детекции объектов к современным архитектурам YOLO и сегментации

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

    Эволюция парадигмы: от регионов к единому тензору

    Исторически задача детекции (Object Detection) решалась в два этапа (Two-Stage Detectors), самым известным семейством которых является R-CNN.

    Сначала отдельный алгоритм — Region Proposal Network — генерировал несколько тысяч гипотез: «кажется, здесь что-то есть». Затем эти участки вырезались, приводились к одному размеру и подавались в классификатор. Это работало точно, но последовательная обработка тысяч фрагментов создавала непреодолимое «бутылочное горлышко» для систем реального времени.

    Прорыв произошел с появлением архитектур One-Stage, флагманом которых стала YOLO (You Only Look Once). Идея заключалась в радикальной смене математической постановки задачи: вместо поиска множества регионов детекция была сведена к одной задаче регрессии поверх пространственной сетки.

    Изображение виртуально делится на сетку размером . Если центр объекта попадает в определенную ячейку сетки, именно эта ячейка становится «ответственной» за его обнаружение.

    !Сетка YOLO и формирование выходного тензора

    Каждая ячейка предсказывает фиксированное количество ограничивающих рамок (bounding boxes). Для каждой рамки нейросеть выдает 5 значений: координаты центра и относительно границ ячейки, ширину и высоту относительно всего изображения, а также уверенность (confidence), что в рамке вообще есть объект. Дополнительно ячейка предсказывает вероятности принадлежности объекта к каждому из классов.

    В результате единственный проход (forward pass) через сеть выдает трехмерный тензор размерности:

    Где: — итоговый тензор предсказаний. — размер сетки (например, 13, 26 или 52). — количество рамок-кандидатов от одной ячейки. — четыре координаты рамки плюс оценка уверенности. — количество предсказываемых классов.

    Анатомия современного детектора: Backbone, Neck, Head

    Современные версии YOLO (начиная с v4 и вплоть до актуальных v8/v10) давно ушли от простой линейной последовательности сверточных слоев. Архитектура Senior-уровня всегда рассматривается как модульная система из трех блоков.

    Backbone (Хребет)

    Задача: извлечь признаки из сырого изображения. Это глубокая сверточная сеть (например, CSPDarknet), которая постепенно уменьшает пространственное разрешение картинки, увеличивая при этом количество каналов (глубину признаков). На выходе Backbone мы имеем несколько карт признаков разного масштаба.

    Neck (Шея)

    Задача: агрегация признаков разного уровня. Это критически важный блок для обнаружения объектов разного размера. Глубокие слои Backbone имеют отличное семантическое понимание («это собака»), но низкое пространственное разрешение (координаты размыты). Ранние слои отлично видят края и текстуры (высокое разрешение), но не понимают контекста.

    Neck решает эту проблему с помощью архитектур вроде FPN (Feature Pyramid Network) и PANet. Они создают двунаправленные пути: сначала семантика спускается сверху вниз к слоям высокого разрешения, а затем точные координаты поднимаются снизу вверх.

    !Архитектура Feature Pyramid Network (FPN) в блоке Neck

    Head (Голова)

    Задача: финальное предсказание. Head берет обогащенные карты признаков из Neck и формирует тот самый тензор . В современных детекторах используется несколько «голов» для разных масштабов: одна сетка ищет крупные объекты, — средние, — мелкие.

    !Зачем нужен Neck

    Фильтрация шума: IoU и Non-Maximum Suppression

    Поскольку каждая ячейка сетки предсказывает несколько рамок, а соседние ячейки могут среагировать на один и тот же крупный объект, сырой выход YOLO содержит сотни дублирующихся предсказаний.

    Для очистки результатов применяется алгоритм NMS (Non-Maximum Suppression), который опирается на метрику Intersection over Union.

    Где: — отношение площади пересечения к площади объединения. — площадь пересечения двух рамок. — общая площадь, покрываемая двумя рамками.

    Алгоритм NMS работает так:

  • Выбирается рамка с наивысшей уверенностью (confidence).
  • Вычисляется этой рамки со всеми остальными рамками того же класса.
  • Если превышает заданный порог (например, 0.5), рамка-кандидат удаляется как дубликат.
  • Процесс повторяется для следующей по уверенности оставшейся рамки.
  • > Выбор порога NMS — это всегда компромисс. Слишком низкий порог (например, 0.1) удалит близко стоящие разные объекты (например, людей в толпе). Слишком высокий порог (0.9) оставит дубликаты на одном объекте.

    !Влияние порога IoU на алгоритм NMS

    За пределами прямоугольников: Сегментация

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

    Существует два фундаментально разных подхода:

    | Характеристика | Семантическая сегментация (Semantic) | Инстанс-сегментация (Instance) | | :--- | :--- | :--- | | Суть | Классификация каждого пикселя изображения. | Выделение конкретных объектов с попиксельной маской. | | Отношение к объектам | Все объекты одного класса сливаются в одну маску. | Каждый объект одного класса получает уникальную маску. | | Пример | Пиксели 1-100 — это «лес», пиксели 101-200 — «дорога». | Это «Машина 1», а это «Машина 2» (даже если они перекрываются). | | Базовая архитектура | U-Net | Mask R-CNN, YOLO-Seg |

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

    Инстанс-сегментация работает иначе. Архитектуры вроде Mask R-CNN или современных веток YOLO-Seg сначала выполняют детекцию (находят bounding box), а затем внутри этого прямоугольника применяют небольшую полносвязную сверточную сеть (FCN), чтобы предсказать бинарную маску объекта, отделив его от фона.

    !Семантическая vs Инстанс сегментация

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

    6. Основы Large Language Models: архитектура декодеров, токенизация и специфика предобучения

    Основы Large Language Models: архитектура декодеров, токенизация и специфика предобучения

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

    Опираясь на понимание механизмов внимания и аппаратных ограничений памяти, мы разберем, как сырой текст превращается в тензоры, почему индустрия отказалась от классических Encoder-Decoder архитектур в пользу чистых декодеров, и как законы масштабирования определяют процесс предобучения.

    От текста к числам: алгоритмы токенизации

    Нейронные сети не умеют работать со строками. Прежде чем входные данные попадут в эмбеддинг-слой и пройдут через блоки внимания с Pre-LN и RoPE, текст необходимо разбить на дискретные единицы — токены.

    Выбор стратегии разбиения — это поиск баланса между размером словаря и длиной итоговой последовательности:

    | Подход | Суть | Проблемы | | :--- | :--- | :--- | | Словарный (Word-level) | Каждый токен — целое слово. | Огромный словарь. Невозможность обработки опечаток и неологизмов (проблема Out-Of-Vocabulary). | | Символьный (Char-level) | Каждый токен — одна буква. | Словарь минимален (алфавит), но длина последовательности возрастает многократно. Контекстное окно тратится впустую, модели сложно улавливать смысл на уровне букв. | | Подсловарный (Subword) | Разбиение на морфемы, слоги или частые сочетания символов. | Является индустриальным стандартом. Требует отдельного алгоритма обучения токенизатора до обучения самой LLM. |

    Стандартом де-факто стал алгоритм BPE (Byte-Pair Encoding). Изначально это алгоритм сжатия данных, который адаптировали для NLP.

    BPE начинает работу с базового словаря, состоящего из всех возможных отдельных байтов (что позволяет обработать абсолютно любой текст на любом языке). Затем алгоритм итеративно находит самую часто встречающуюся пару соседних токенов в обучающем корпусе и объединяет их в новый, единый токен. Процесс повторяется, пока словарь не достигнет заданного размера (обычно от 32 000 до 128 000 токенов).

    > В результате частые слова (например, «машинное») становятся одним токеном, а редкие слова или опечатки разбиваются на более мелкие, уже известные модели фрагменты (вплоть до отдельных букв-байтов). Модель никогда не сталкивается с неизвестным токеном.

    !Что произойдет при токенизации опечатки

    Архитектура Decoder-only: причинное маскирование и генерация

    Исторически архитектура Transformer состояла из двух частей: Энкодера (читает весь входной текст целиком) и Декодера (генерирует ответ). Эта схема отлично работала для машинного перевода, где длина входа известна заранее. Однако для задач открытой генерации (zero-shot) индустрия перешла к архитектуре Decoder-only (семейство GPT, LLaMA, Qwen).

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

    Математически это реализуется через причинное маскирование (Causal Mask) в механизме Self-Attention. К матрице скалярных произведений запросов и ключей добавляется маскирующая матрица , где элементы выше главной диагонали равны .

    Формула внимания принимает вид:

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

    Проблема производительности: введение KV-кэша

    При авторегрессионной генерации модель выдает по одному токену за проход. Чтобы сгенерировать 100-й токен, на вход подаются 99 предыдущих. Чтобы сгенерировать 101-й — 100 предыдущих. Вычисление проекций Query, Key и Value для всех исторических токенов на каждом шаге приводит к катастрофическим затратам вычислений.

    Решением является KV-Cache. Поскольку исторические токены не меняются, их векторы Key и Value вычисляются один раз и сохраняются в памяти GPU. На каждом новом шаге модель вычисляет Q, K, V только для одного нового токена, добавляет новые K и V в кэш, и вычисляет внимание нового Query ко всем закэшированным Key.

    > KV-кэш превращает квадратичную вычислительную сложность генерации в линейную, но переносит нагрузку на подсистему памяти. Объем кэша динамически растет с каждым сгенерированным токеном, становясь главным узким местом (memory-bound) при долгом инференсе.

    !Влияние длины последовательности на инференс

    Специфика предобучения: цель и законы масштабирования

    Архитектура готова. Теперь модель необходимо обучить. Этап Pre-training — это самый долгий и дорогой процесс в жизненном цикле LLM, требующий тысяч GPU и месяцев работы.

    Обучение происходит в режиме самоконтроля (Self-Supervised Learning). Функция потерь — Cross-Entropy Loss. Модели подается батч текстов, и она пытается предсказать каждый следующий токен. Ошибка вычисляется путем сравнения предсказанного распределения вероятностей с реальным следующим токеном из текста (который выступает в роли метки класса).

    Законы масштабирования Chinchilla

    Долгое время считалось, что для улучшения качества модели достаточно просто увеличивать количество параметров (размер матрицы весов). Однако в 2022 году исследователи из DeepMind опубликовали законы оптимального масштабирования (Chinchilla Scaling Laws).

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

    Связь выражается эмпирической формулой:

    Это означает, что на каждый параметр модели должно приходиться около 20 токенов обучающих данных. Если вы увеличиваете модель в 2 раза, вы обязаны увеличить объем данных в 2 раза, иначе вычислительные ресурсы будут потрачены неоптимально (модель будет недообучена).

    Стратегия Over-training

    Современные открытые модели (например, семейство LLaMA) часто намеренно нарушают закон Chinchilla. Разработчики фиксируют небольшой размер модели (например, 8 млрд параметров) и обучают ее на колоссальном объеме данных (15 триллионов токенов), что многократно превышает оптимальные пропорции.

    Зачем тратить лишние вычислительные мощности? Цель — сместить затраты с этапа обучения на этап эксплуатации. Переобученная на избыточных данных компактная модель показывает качество, сопоставимое с огромными моделями, но при этом требует значительно меньше VRAM и работает в разы быстрее на инференсе у конечного пользователя.

    От Base Model к Assistant

    Результатом этапа Pre-training является Base Model (базовая, или фундаментальная модель).

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

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

    7. Продвинутый Fine-tuning LLM: методы PEFT, LoRA и QLoRA для адаптации моделей под доменные задачи

    Продвинутый Fine-tuning LLM: методы PEFT, LoRA и QLoRA для адаптации моделей под доменные задачи

    Обучение LLaMA-3 с 70 миллиардами параметров требует около 1.5 терабайт видеопамяти только для того, чтобы загрузить веса, градиенты и состояния оптимизатора. При стоимости аренды одного узла с 8 видеокартами H100 около 30 долл. в час, один эксперимент по полному дообучению обойдется бизнесу в тысячи долларов. Однако сегодня инженеры адаптируют модели такого масштаба под специфические задачи юриспруденции, медицины или программирования, используя всего одну потребительскую видеокарту. Этот переход от кластерных вычислений к локальным стал возможен благодаря радикальному пересмотру того, что именно мы обновляем в нейросети.

    В предыдущей главе мы разобрали, что базовая модель (Base Model) умеет лишь предсказывать следующий токен. Чтобы превратить ее в полезного ассистента, требуется дообучение на инструкциях (Instruction Tuning). В этой статье мы разберем математику и архитектуру методов, которые позволяют проводить этот процесс без катастрофических затрат на инфраструктуру.

    Стена памяти: почему Full Fine-Tuning не работает для LLM

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

    Каждый параметр модели в формате bfloat16 занимает 2 байта. Для модели на 70 млрд параметров (70B) сами веса займут 140 ГБ. При обучении нам необходимо хранить градиенты для каждого параметра — еще 140 ГБ. Но главная проблема кроется в оптимизаторе. Если мы используем стандартный AdamW, он хранит два дополнительных состояния для каждого веса: экспоненциальное скользящее среднее градиентов (Momentum) и скользящее среднее квадратов градиентов (Variance). Обычно они хранятся в формате float32 (4 байта каждый), что добавляет еще 8 байт на параметр, то есть 560 ГБ.

    Итого: ГБ видеопамяти, и это без учета памяти под активации батча данных.

    Чтобы обойти эту «стену памяти», исследователи предложили парадигму PEFT.

    > PEFT (Parameter-Efficient Fine-Tuning) — семейство методов дообучения, при которых подавляющее большинство параметров предварительно обученной модели замораживается, а обновляется лишь небольшое количество дополнительных весов.

    Фундаментом для PEFT стала гипотеза о низкой «внутренней размерности» (intrinsic dimension) больших языковых моделей. Суть гипотезы в том, что предобученная на триллионах токенов модель уже обладает репрезентацией языка и знаний. Для адаптации к новой задаче (например, отвечать в стиле JSON или использовать медицинские термины) не нужно менять всю матрицу знаний — достаточно лишь слегка скорректировать направление ее работы в пространстве меньшей размерности.

    Математика LoRA: низкоранговая адаптация

    Самым успешным методом из семейства PEFT стал LoRA (Low-Rank Adaptation). Вместо того чтобы менять исходную матрицу весов, LoRA добавляет к ней небольшую обучаемую «пристройку».

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

    LoRA замораживает и представляет огромную матрицу в виде произведения двух маленьких матриц и :

    Разберем элементы этой формулы:

  • — оригинальные веса модели. Они заморожены и не требуют вычисления градиентов.
  • — первая матрица адаптера.
  • — вторая матрица адаптера.
  • — ранг (rank), гиперпараметр, определяющий размер матриц и . Обычно . Например, при ранг может быть равен 8 или 16.
  • — коэффициент масштабирования (alpha), который контролирует силу влияния адаптера на базовые веса.
  • Секрет эффективности кроется в количестве параметров. Если и , то матрица содержит около 16.7 млн параметров. При использовании LoRA с рангом , матрица имеет параметров, матрица — столько же. Суммарно мы обучаем всего около 65 тысяч параметров вместо 16.7 миллионов. Это сокращает размер состояний оптимизатора в сотни раз.

    Важный нюанс кроется в инициализации. Матрица инициализируется случайными числами из нормального распределения, а матрица — строго нулями. !Инициализация матриц в LoRA

    При прямом проходе (forward pass) входной вектор умножается параллельно на замороженную базу и на адаптер, после чего результаты складываются:

    QLoRA: экстремальное сжатие для потребительских GPU

    LoRA блестяще решает проблему состояний оптимизатора и градиентов — теперь они занимают мегабайты, а не сотни гигабайт. Однако замороженные веса по-прежнему хранятся в памяти в формате 16 бит. Для модели 70B это 140 ГБ, что все еще недоступно для одной видеокарты (например, RTX 4090 имеет лишь 24 ГБ VRAM).

    Здесь на сцену выходит QLoRA (Quantized LoRA) — метод, объединяющий квантование базовой модели с обучением низкоранговых адаптеров.

    Суть QLoRA заключается в том, чтобы загрузить в экстремально сжатом 4-битном формате, при этом сохранив адаптеры и в 16-битном формате для точного обучения.

    Для минимизации потери качества при таком агрессивном сжатии QLoRA вводит специальный тип данных — 4-bit NormalFloat (NF4). Поскольку веса обученной нейросети имеют распределение, близкое к нормальному (колокол Гаусса), стандартное линейное квантование выделяло бы слишком много битов на редкие выбросы. Формат NF4 распределяет уровни квантования не равномерно, а в соответствии с плотностью нормального распределения: больше уровней там, где сосредоточено большинство весов (около нуля), и меньше на краях. Это делает NF4 информационно-оптимальным типы данных для весов нейросетей.

    !Механизмы экономии памяти в QLoRA

    Но современные GPU не умеют выполнять матричное умножение напрямую в 4-битном формате. Поэтому в QLoRA применяется хитрость: динамическая деквантизация. Во время прямого прохода небольшие блоки 4-битных весов на лету распаковываются в кэше процессора GPU (SRAM) до формата bfloat16. Матричное умножение с входными данными происходит в 16 битах, после чего распакованные веса удаляются из быстрой памяти.

    Таким образом, QLoRA — это компромисс: мы экономим глобальную память видеокарты (HBM) за счет небольшого увеличения вычислительной нагрузки на постоянную распаковку весов.

    Сравнение подходов

    Чтобы закрепить понимание, сведем характеристики методов в таблицу на примере модели 7B:

    | Характеристика | Full Fine-Tuning | LoRA | QLoRA | | :--- | :--- | :--- | :--- | | Память для весов модели | ~14 ГБ (bf16) | ~14 ГБ (bf16) | ~3.5 ГБ (NF4) | | Память для оптимизатора | ~56 ГБ | < 1 ГБ | < 1 ГБ | | Обучаемые параметры | 100% | ~0.1 - 1% | ~0.1 - 1% | | Скорость обучения | Эталонная | Быстрее (меньше градиентов) | Медленнее (деквантизация) |

    Инференс: слияние весов (Weight Merging)

    После завершения обучения методами LoRA или QLoRA мы получаем два артефакта: огромную базовую модель и крошечный файл с весами адаптеров (обычно от 10 до 200 МБ).

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

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

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

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

    Методы PEFT демократизировали работу с LLM. Теперь, когда мы умеем адаптировать модель под специфический домен на уровне весов, возникает следующий архитектурный вопрос: как заставить модель оперировать фактами, которых не было в обучающей выборке, и предотвратить галлюцинации? Эту задачу решает связка инженерии промптов и систем поисковой генерации (RAG), к проектированию которых мы перейдем в следующей главе.

    8. Инженерия промптов и RAG-системы: построение надежных цепочек извлечения знаний и генерации ответов

    Инженерия промптов и RAG-системы: построение надежных цепочек извлечения знаний и генерации ответов

    Вы успешно применили QLoRA и дообучили модель на внутренней документации компании. Она идеально отвечает на вопросы о регламентах. Но через неделю бизнес меняет тарифы и лимиты API. Модель продолжает уверенно выдавать старые цифры. Запускать цикл Fine-tuning при каждом обновлении базы знаний — непозволительная трата вычислительных ресурсов и времени. Веса модели (даже адаптеры LoRA) отлично подходят для усвоения стиля, формата и глубоких паттернов предметной области, но они катастрофически неэффективны в качестве базы данных для динамических фактов.

    Чтобы отделить логику рассуждений от хранилища фактов, мы переходим от обновления весов к управлению контекстом.

    Вычислительная природа Chain-of-Thought

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

    LLM не имеет скрытой «рабочей памяти» (scratchpad) вне KV-кэша генерируемых токенов. Если задать сложный логический вопрос и потребовать ответ одним словом, модель должна вычислить итоговую вероятность через одно применение матрицы внимания и прямой проход (Feed-Forward Network). Для сложных задач глубины слоев просто не хватает, чтобы свернуть все логические операции в один шаг.

    Заставляя модель генерировать промежуточные токены рассуждений, мы фактически даем ей дополнительное вычислительное время. Каждый сгенерированный токен добавляется в KV-кэш, становясь доступным для механизма Self-Attention на следующем шаге.

    > Chain-of-Thought превращает пространственную сложность (которую модель не может преодолеть из-за фиксированного числа слоев) во временную сложность (генерация дополнительных токенов), распределяя вычисления вдоль оси времени.

    !Почему Chain-of-Thought улучшает качество ответов LLM?

    Когда модель умеет рассуждать, ей нужны актуальные данные. Мы подошли к концепции Retrieval-Augmented Generation (RAG).

    Архитектура RAG: мост между генерацией и поиском

    Если мы не можем запечь факты в веса, мы передадим их в промпт. Но контекстное окно ограничено (даже при 128k токенов), и чем больше контекста мы подаем, тем сильнее проявляется эффект Lost in the Middle — модель хорошо обращает внимание на начало и конец промпта, но «забывает» факты, спрятанные в середине длинного текста. Это связано с тем, как распределяются веса Softmax в механизме внимания при большом количестве токенов.

    Поэтому мы должны подавать в контекст только самую релевантную информацию. Стандартный пайплайн RAG состоит из трех этапов:

  • Индексация (Offline): нарезка документов на чанки (chunks) и их векторизация.
  • Поиск (Online): векторизация запроса пользователя и поиск ближайших чанков в векторном пространстве.
  • Генерация (Online): склейка найденных чанков с запросом и отправка в LLM.
  • Стратегии чанкинга

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

    Размер чанка — это компромисс:

  • Маленькие чанки (100-200 токенов): высокая точность поиска, но LLM может не хватить контекста для ответа (например, найдено определение переменной, но не функция, где она используется).
  • Большие чанки (1000+ токенов): много контекста, но падает точность поиска, растет потребление токенов и риск галлюцинаций.
  • Чтобы не разорвать семантическую связь на стыке блоков, используется Overlap (перекрытие) — каждый следующий чанк захватывает 10–20% предыдущего.

    Двухэтапный поиск: Bi-encoder и Cross-encoder

    Основа векторного поиска — вычисление близости между запросом и документом. Чаще всего используется косинусное сходство:

    Где:

  • — вектор-эмбеддинг запроса.
  • — вектор-эмбеддинг чанка документа.
  • — длины векторов (нормы).
  • Чтобы это работало быстро, мы используем архитектуру Bi-encoder. Запрос и документ проходят через энкодер (например, BERT) независимо друг от друга. Эмбеддинги документов предвычисляются заранее и лежат в векторной БД. Во время запроса мы делаем один проход для и быстро считаем скалярные произведения с миллионами .

    Но у Bi-encoder есть фундаментальный недостаток: модель не видит запрос и документ одновременно. Сложные смысловые пересечения (когда одно слово в запросе меняет смысл в зависимости от контекста документа) теряются при сжатии в единый вектор.

    Здесь на сцену выходит Re-ranking (Переранжирование) с помощью Cross-encoder. В Cross-encoder запрос и документ склеиваются (через специальный токен-разделитель) и подаются в трансформер вместе. Механизм Self-Attention на каждом слое позволяет токенам запроса взаимодействовать с токенами документа.

    Сравнение архитектур:

    | Характеристика | Bi-encoder (Векторный поиск) | Cross-encoder (Реранкер) | | :--- | :--- | :--- | | Взаимодействие | Независимое вычисление векторов | Совместное вычисление (Attention между Q и D) | | Скорость | Миллисекунды (поиск по миллионам записей) | Секунды (тяжелый инференс) | | Точность | Средняя (хорошо для грубой фильтрации) | Высокая (понимает тонкие связи) | | Роль в RAG | Этап 1: Достать топ-100 кандидатов (Retrieval) | Этап 2: Отсортировать топ-100 и оставить топ-5 (Re-ranking) |

    !Почему мы не используем Cross-encoder для поиска по всей базе?

    Пайплайн Senior-уровня всегда использует двухэтапный поиск (Retrieve & Re-rank). Сначала быстрый Bi-encoder (или гибридный поиск вместе с лексическим BM25) извлекает сотню кандидатов, затем тяжелый Cross-encoder переоценивает их, оставляя 3–5 самых релевантных чанков. Именно они формируют итоговый промпт для LLM, минимизируя шум и предотвращая потерю фокуса в середине контекста.

    Внедрение RAG решает проблему актуальности фактов, но порождает новую: как измерить, насколько точно модель опирается на предоставленный контекст, а не фантазирует? Оценка качества таких генеративных систем требует перехода от классических метрик к LLM-as-a-Judge.

    9. Оценка качества LLM: метрики, бенчмарки и человеческая обратная связь (RLHF) в производственном цикле

    Оценка качества LLM: метрики, бенчмарки и человеческая обратная связь (RLHF) в производственном цикле

    Двухэтапный пайплайн поиска отработал идеально: система нашла топ-5 релевантных документов и сгенерировала ответ. Но как доказать бизнесу, что этот ответ действительно хорош? Если пользователь спросил «Как сбросить пароль?», а модель выдала технически безупречное, но написанное гекзаметром стихотворение о безопасности, классические метрики совпадения текста покажут низкий балл, а человек-асессор поставит ноль. В мире генеративного ИИ больше нет заранее известного «правильного» ответа, с которым можно посимвольно сравнить результат.

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

    Триада оценки RAG-систем

    Вместо того чтобы оценивать финальный ответ монолитно, производственный стандарт предписывает декомпозировать качество RAG-системы на три независимые оси. Это позволяет точно локализовать проблему: виноват поиск или генерация.

  • Context Relevance (Релевантность контекста)
  • Оценивает работу поискового движка. Содержит ли извлеченный чанк информацию, необходимую для ответа на запрос пользователя? Если метрика низкая — нужно менять стратегию чанкинга или настраивать Re-ranking.
  • Groundedness / Faithfulness (Обоснованность)
  • Оценивает отсутствие галлюцинаций. Все ли факты в сгенерированном ответе опираются строго на извлеченный контекст? Если модель добавляет знания из своих весов, которых нет в документах компании, метрика падает.
  • Answer Relevance (Релевантность ответа)
  • Оценивает полезность для пользователя. Отвечает ли финальный текст на исходный вопрос, или модель ушла в пространные рассуждения на смежную тему?

    > Разделение Groundedness и Answer Relevance критично. Ответ может быть на 100% обоснован извлеченным документом (высокий Groundedness), но если сам документ не относится к вопросу пользователя, Answer Relevance будет низким.

    !Определение проблемного узла в RAG

    LLM-as-a-Judge: автоматизация семантической оценки

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

    Решением стала концепция LLM-as-a-Judge. Мы используем мощную, проприетарную модель (например, GPT-4), чтобы она оценивала ответы нашей легковесной production-модели. Судье передается специальный промпт, содержащий критерии оценки, исходный вопрос, контекст и ответ тестируемой модели. Судья должен выдать оценку от 1 до 5 и текстовое обоснование.

    У этого подхода есть известные уязвимости, которые необходимо компенсировать при проектировании системы оценки:

  • Verbosity bias (Смещение к многословности): Модели-судьи склонны ставить более высокие оценки длинным ответам, даже если в них много «воды».
  • Position bias (Позиционное смещение): При сравнении двух ответов (А и Б) судья часто отдает предпочтение тому, который передан в промпте первым. Для компенсации порядок ответов меняют местами и усредняют результат.
  • Бенчмарки и ловушка контаминации

    Если мы оцениваем не RAG-систему, а саму базовую или дообученную модель, индустрия использует стандартизированные бенчмарки:

  • MMLU (Massive Multitask Language Understanding): Тестирование знаний в 57 дисциплинах (от математики до права).
  • HumanEval: Оценка способностей к написанию кода на Python.
  • Однако при анализе результатов бенчмарков Data Scientist сталкивается с фундаментальной проблемой, природа которой аналогична Train-Test Contamination.

    В классическом ML утечка происходит, когда тестовая выборка попадает в обучающую. В мире LLM обучающая выборка (корпус претрейна) настолько огромна (триллионы токенов), что в нее неминуемо попадают тексты самих бенчмарков, опубликованные в интернете. Это явление называется Benchmark Contamination (Контаминация бенчмарков).

    Если модель показывает 90% на MMLU, это может означать не выдающиеся способности к рассуждению (reasoning), а то, что модель просто запомнила вопросы и ответы на этапе предобучения. Выявление контаминации — сложная задача, требующая поиска n-грамм из тестов в терабайтах исходных данных.

    !Оценка результатов бенчмарка

    Alignment: выравнивание с человеческими предпочтениями

    Высокие баллы на бенчмарках не гарантируют, что модель будет удобной в использовании. Базовая модель (Base Model) обучена просто предсказывать следующий токен. Чтобы она стала помощником (Assistant), ее поведение нужно «выровнять» (Alignment) по критериям HHH: Helpful (полезная), Honest (честная), Harmless (безопасная).

    Для этого применяется RLHF (Reinforcement Learning from Human Feedback). Процесс состоит из двух этапов:

    1. Обучение модели вознаграждения (Reward Model)

    Сначала собираются данные человеческих предпочтений. Человеку показывают промпт и два варианта ответа модели. Асессор выбирает лучший. На этих парах обучается Reward Model (часто на базе той же LLM, но с замененным на скалярный выход последним слоем). Ее задача — принимать на вход текст и выдавать число (награду), предсказывая, насколько этот текст понравится человеку.

    2. Оптимизация политики (PPO)

    Далее основная LLM генерирует ответы, а Reward Model их оценивает. Веса LLM обновляются с помощью алгоритма обучения с подкреплением PPO (Proximal Policy Optimization) так, чтобы максимизировать выдаваемую награду.

    Здесь возникает риск Reward Hacking: LLM может найти уязвимость в Reward Model и начать генерировать бессмысленный текст, который по какой-то причине получает наивысший балл. Чтобы этого избежать, в функцию потерь добавляется штраф за отклонение от исходной модели.

    Где:

  • — оценка, выданная Reward Model для ответа на промпт .
  • — текущая обучаемая модель (политика).
  • — замороженная референсная модель (обычно модель после этапа SFT).
  • — дивергенция Кульбака-Лейблера, измеряющая разницу между распределениями вероятностей токенов двух моделей.
  • — коэффициент, контролирующий силу штрафа.
  • Дивергенция Кульбака-Лейблера выступает как эластичный трос: она позволяет модели адаптироваться для получения высокой награды, но жестко штрафует, если модель слишком сильно меняет свой словарь и стиль по сравнению с адекватной референсной моделью.

    DPO: Отказ от модели вознаграждения

    RLHF — крайне нестабильный и дорогой в настройке пайплайн (нужно держать в памяти GPU одновременно 4 модели: обучаемую, референсную, Reward Model и модель оценки ценности состояний).

    Современной альтернативой стал метод DPO (Direct Preference Optimization).

    | Характеристика | RLHF (PPO) | DPO | | :--- | :--- | :--- | | Архитектура | Требует отдельной Reward Model | Не требует Reward Model | | Алгоритм | Обучение с подкреплением (Reinforcement Learning) | Обычное обучение с учителем (Supervised Learning) | | Стабильность | Низкая (чувствителен к гиперпараметрам) | Высокая (математически эквивалентно RLHF, но решается аналитически) |

    DPO математически доказывает, что саму языковую модель можно использовать как неявную Reward Model. Метод напрямую обновляет веса LLM на основе датасета предпочтений (ответ А лучше ответа Б), используя модифицированную функцию кросс-энтропии. Это радикально снижает требования к инфраструктуре, делая Alignment доступным для локального применения.

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