Машинное обучение
Distributed Training
Обучение GPT-4, по данным аналитиков, стоило более 100 миллионов долларов и потребовало десятков тысяч GPU, работавших вместе несколько месяцев. На одном GPU это заняло бы сотни лет. Распределённое обучение - единственный способ создавать современный AI, и понимание его принципов - это разница между обучением модели за день и за десятилетие.
- **Обучение ChatGPT и GPT-4** - OpenAI использовал тысячи GPU A100, соединённых InfiniBand, с комбинацией data, pipeline и tensor parallelism. Без распределённого обучения создание таких моделей было бы физически невозможно
- **Рекомендательные системы Meta** - модели рекомендаций Facebook обрабатывают триллионы примеров и требуют сотни GPU для обучения. Каждый час задержки стоит компании миллионы долларов упущенной прибыли
- **Научные вычисления** - AlphaFold от DeepMind для предсказания структуры белков обучался на сотнях TPU. Распределённое обучение позволило решить задачу, над которой биологи работали 50 лет
Предварительные знания
Parameter server, Horovod и ZeRO: как масштабировали обучение по машинам
Когда модели и датасеты переросли одну машину, в поле выработали две взаимодополняющие стратегии: data parallelism, где каждый воркер держит полную копию модели и обрабатывает разные данные, и model parallelism, где модель, слишком большую для одного устройства, разбивают между несколькими. В 2014 году Му Ли с соавторами формализовал архитектуру parameter server, в которой воркеры без состояния считают градиенты и отправляют их на центральные серверы, хранящие параметры модели, что позволяло обучать на тысячах машин. У parameter server было узкое место по коммуникации на центральных узлах, поэтому в 2017 году Александр Сергеев и Майк Дель Балсо в Uber выпустили Horovod, перенёсший ring-allreduce из высокопроизводительных вычислений в глубокое обучение. Ring-allreduce передавал градиенты по кольцу воркеров так, что пропускная способность оставалась постоянной независимо от размера кластера, и стал стандартом для синхронного data-parallel обучения. Для моделей с миллиардами параметров Microsoft представила ZeRO в составе DeepSpeed примерно в 2020 году, разбивая состояния оптимизатора, градиенты и параметры между воркерами, чтобы убрать избыточность памяти и сделать обучение моделей с триллионом параметров реальным.
Data Parallelism
Data parallelism - самый распространённый подход к распределённому обучению. Идея проста: **каждый GPU получает полную копию модели**, но обрабатывает разную часть батча данных. Если у вас 8 GPU и батч из 256 примеров, каждый GPU обрабатывает 32 примера одновременно. После вычисления градиентов на каждом GPU нужно их **синхронизировать** - усреднить градиенты со всех GPU, чтобы все копии модели оставались идентичными.
**AllReduce** - ключевая операция синхронизации. Она собирает градиенты со всех GPU, усредняет их и рассылает результат обратно на все GPU. Наивная реализация (все отправляют одному серверу, он усредняет и рассылает обратно) создаёт узкое место - параметрный сервер становится бутылочным горлышком. Более эффективный подход - **ring-allreduce**, где GPU образуют кольцо и передают данные по цепочке, равномерно распределяя нагрузку.
**Синхронный vs Асинхронный Data Parallelism:** - **Синхронный** (SGD): все GPU ждут друг друга на каждом шаге. Градиенты точные, обучение стабильное, но самый медленный GPU тормозит остальных (straggler problem). - **Асинхронный** (Async SGD): GPU обновляют модель независимо, не ждут остальных. Быстрее, но возникают "stale gradients" - GPU обновляет модель градиентами, вычисленными на устаревшей версии весов. Может замедлить сходимость. На практике **синхронный** подход доминирует - он проще и предсказуемее. PyTorch DDP использует именно его.
**Масштабирование learning rate** - важный нюанс data parallelism. Если вы увеличили эффективный размер батча в N раз (N GPU), то learning rate тоже нужно увеличить примерно в N раз - иначе обучение будет слишком медленным. Но слишком большой learning rate в начале обучения дестабилизирует сходимость. Решение - **linear warmup**: начать с маленького learning rate и линейно увеличивать его до целевого за первые несколько сотен шагов.
В data parallelism с 8 GPU и батчем 512 примеров: что происходит после того, как каждый GPU вычислил градиенты на своих 64 примерах?
Model Parallelism
Data parallelism отлично работает, пока модель помещается в память одного GPU. Но что если модель **слишком большая**? GPT-3 имеет 175 миллиардов параметров - это около 350 ГБ в fp16. Ни один GPU (даже A100 с 80 ГБ памяти) не вмещает такую модель. Решение - **model parallelism**: разделить саму модель между несколькими GPU, чтобы каждый хранил и обрабатывал только свою часть.
Простой model parallelism (naive pipeline) имеет серьёзную проблему: **пока GPU 0 обрабатывает слои 1-4, GPU 1-3 простаивают**. Потом GPU 0 передаёт активации на GPU 1, и теперь GPU 0 простаивает. В итоге утилизация каждого GPU всего 1/N (25% при 4 GPU). **Pipeline parallelism** (GPipe) решает эту проблему, разбивая мини-батч на ещё более мелкие **micro-batches**. Пока GPU 1 обрабатывает micro-batch 1, GPU 0 уже работает с micro-batch 2.
**Tensor Parallelism (Megatron-LM):** Pipeline parallelism режет модель по слоям (вертикально). Tensor parallelism режет **отдельные слои** (горизонтально) - каждый GPU вычисляет часть матричного умножения внутри одного слоя. Пример для слоя Y = XW + b, где W - матрица [4096 x 4096]: - GPU 0: вычисляет Y_0 = X * W[:, :2048] (первая половина столбцов) - GPU 1: вычисляет Y_1 = X * W[:, 2048:] (вторая половина столбцов) - Результат: Y = concat(Y_0, Y_1) Tensor parallelism требует **очень быстрой связи** между GPU (NVLink), потому что коммуникация происходит внутри каждого слоя, а не между слоями.
На практике **комбинируют** оба подхода. Например, GPT-3 от OpenAI обучался с tensor parallelism внутри одного сервера (8 GPU, соединённых NVLink) и data + pipeline parallelism между серверами (тысячи серверов, соединённых InfiniBand). Такая гибридная стратегия называется **3D parallelism**: data parallelism x pipeline parallelism x tensor parallelism.
Модель GPT-3 занимает 350 ГБ в fp16, а максимальная память GPU - 80 ГБ. Какой подход позволит обучить эту модель?
Horovod
**Horovod** - фреймворк распределённого обучения, созданный в Uber. Главная идея: сделать распределённое обучение максимально простым. Вместо переписывания всего кода тренировки, Horovod требует **буквально 5 строк изменений** в существующем скрипте. Под капотом Horovod использует **ring-allreduce** - алгоритм, где GPU образуют кольцо и передают градиенты по цепочке, обеспечивая линейное масштабирование.
Horovod построен на основе MPI (Message Passing Interface) - стандарта для параллельных вычислений из мира суперкомпьютеров. Это означает, что Horovod наследует зрелую инфраструктуру запуска: `horovodrun` (или `mpirun`) автоматически запускает процессы на нескольких серверах, настраивает коммуникацию, обрабатывает отказы. Поддерживаются TensorFlow, PyTorch и Keras.
**Horovod vs PyTorch DDP - что выбрать?** **Horovod:** - Поддерживает TensorFlow, PyTorch и Keras одновременно - Запуск через `horovodrun` (на основе MPI) - Проще для multi-node (несколько серверов) - Elastic training: добавление/удаление GPU на лету - Исторически появился раньше, широко используется в enterprise **PyTorch DDP:** - Только PyTorch, но глубже интегрирован - Запуск через `torchrun` (встроенный launcher) - Overlap communication и computation (быстрее) - Gradient bucketing - группирует маленькие тензоры для эффективности - Рекомендован командой PyTorch как стандарт В 2024+ PyTorch DDP доминирует для чистого PyTorch. Horovod актуален для multi-framework проектов и legacy кода на TensorFlow.
**Elastic Horovod** - уникальная функция, позволяющая менять число GPU во время обучения. Если один сервер отказал, обучение продолжается на оставшихся GPU. Если добавился новый сервер, он подключается к обучению. Это критично для облачных сред, где spot-инстансы могут быть отозваны в любой момент, и для длительных задач обучения (дни и недели), где сбои неизбежны.
Почему ring-allreduce в Horovod масштабируется лучше, чем parameter server?
DeepSpeed и ZeRO
**DeepSpeed** - библиотека от Microsoft для обучения сверхбольших моделей. Ключевая технология - **ZeRO (Zero Redundancy Optimizer)**, которая устраняет избыточное дублирование данных в data parallelism. Проблема: при обычном data parallelism каждый GPU хранит полную копию модели, градиентов И состояния оптимизатора. Для модели в 10 ГБ с Adam оптимизатором каждый GPU тратит ~50 ГБ памяти, из которых 40 ГБ - дубликаты!
**ZeRO имеет три стадии**, каждая разделяет больше данных между GPU. **Stage 1** разделяет только состояние оптимизатора - каждый GPU хранит 1/N от Adam momentum и variance. **Stage 2** дополнительно разделяет градиенты. **Stage 3** разделяет всё, включая параметры модели - каждый GPU хранит только 1/N параметров и собирает недостающие части на лету через AllGather перед forward/backward pass.
**Mixed Precision Training (fp16/bf16):** DeepSpeed автоматически использует mixed precision - вычисления в fp16 (16 бит), а мастер-копия весов в fp32 (32 бит). - **fp16**: вдвое меньше памяти, вдвое быстрее вычисления на Tensor Cores (V100, A100). Проблема: маленький диапазон значений, возможен overflow/underflow. - **bf16** (bfloat16): такой же диапазон как fp32, но меньшая точность. Более стабильное обучение. Поддерживается на A100, H100. - **Loss scaling**: DeepSpeed автоматически масштабирует loss перед backward, чтобы маленькие градиенты не обнулились в fp16. Эффект: модель тренируется почти в 2 раза быстрее при том же качестве.
DeepSpeed произвёл революцию в обучении больших моделей. Если раньше для обучения модели с сотнями миллиардов параметров требовались тысячи дорогих GPU уровня A100, то ZeRO-Infinity позволяет использовать обычные серверы с большим количеством RAM и NVMe SSD. Именно на DeepSpeed были обучены модели семейства Bloom (176B параметров) и множество других open-source LLM. В сочетании с FSDP (Fully Sharded Data Parallel) из PyTorch, идеи ZeRO стали стандартом индустрии.
Распределённое обучение нужно только для огромных моделей
Даже средние модели обучаются в 4-8 раз быстрее на нескольких GPU, экономя дни ожидания
Распределённое обучение - это не только про размер модели, но и про скорость. Модель BERT-base (110M параметров) легко помещается на один GPU, но обучение занимает 4 дня. На 8 GPU с data parallelism - 12 часов. На 64 GPU - меньше 2 часов. Для итеративных экспериментов (подбор гиперпараметров, архитектуры) ускорение в несколько раз критично: вместо 1 эксперимента в неделю вы запускаете 5-10.
Модель занимает 20 ГБ в fp16. При обучении с Adam на 4 GPU обычный DDP требует ~100 ГБ на каждый GPU. Какая стадия ZeRO максимально сократит потребление памяти?
Итоги
- **Data Parallelism:** каждый GPU хранит полную копию модели, но обрабатывает свою часть батча. AllReduce синхронизирует градиенты между GPU. PyTorch DDP делает это с минимальными изменениями кода и перекрытием коммуникации с вычислениями
- **Model Parallelism:** когда модель не помещается на один GPU, её разрезают между устройствами. Pipeline parallelism (GPipe) устраняет простои через micro-batches, а tensor parallelism (Megatron-LM) разделяет отдельные слои для максимальной утилизации
- **Horovod:** фреймворк от Uber с ring-allreduce вместо parameter server. Равномерное распределение нагрузки устраняет бутылочное горлышко и обеспечивает линейное масштабирование с ростом кластера
- **DeepSpeed и ZeRO:** три стадии устранения дублирования - от optimizer states (Stage 1) до полного разделения всех данных (Stage 3). Offload на CPU и NVMe позволяет обучать модели с триллионами параметров. Именно так обучают модели, стоимость которых измеряется в сотнях миллионов долларов - как GPT-4 из нашего примера
Связанные темы
Распределённое обучение находится на стыке алгоритмов оптимизации, системного дизайна и инженерии больших моделей:
- ML System Design — Распределённое обучение - ключевой компонент проектирования ML-систем. Выбор стратегии параллелизма, размера кластера и инфраструктуры напрямую влияет на стоимость, скорость и масштабируемость production ML pipeline
- Оптимизаторы — SGD, Adam, AdaFactor и их распределённые варианты - основа обучения. ZeRO разделяет именно состояние оптимизатора (самый большой потребитель памяти). Выбор оптимизатора влияет на стратегию распределения и потребление памяти
Вопросы для размышления
- Почему синхронный data parallelism доминирует на практике, несмотря на straggler problem? Какие архитектурные решения (однородный кластер, fault tolerance) делают его предпочтительнее асинхронного подхода?
- ZeRO Stage 3 разделяет все данные между GPU, но требует AllGather перед каждым forward/backward pass. При каком соотношении вычислений и коммуникации это становится невыгодным? Как пропускная способность сети (PCIe vs NVLink vs InfiniBand) влияет на выбор стратегии?
- Если бы у вас был бюджет на 100 GPU A100 для обучения модели с 70B параметров, как бы вы скомбинировали data, pipeline и tensor parallelism? Какие факторы определяют оптимальное разбиение?
Связанные уроки
- ml-53-ab-testing-ml — Продолжает последовательность production ML
- ml-28-optimizers — Распределённый SGD синхронизирует градиенты
- ml-55-ml-system-design — Масштабирование обучения - вопрос проектирования
- ml-46-model-serving — Оба масштабируют ML по машинам
- sd-03-scalability — Data и model parallelism как масштабирование систем
- dl-12