Параллельные вычисления

Зачем нужен параллелизм

NVIDIA H100 - 16896 CUDA-ядер. GPT-4 тренировался на 25 000 A100 параллельно. PyTorch по умолчанию задействует все ядра CPU и всю GPU. И при этом наивно написанный код использует одно ядро из всех. Но вот ловушка: параллелизм не ускоряет произвольную программу. Закон Амдала говорит жестко: если 5% кода последовательно - максимум 20x, хоть с миллионом ядер. Именно поэтому PyTorch разделяет data parallelism и model parallelism - это не прихоть, это математика.

  • **LLM training:** GPT-4 обучался на 25 000 A100 (~100 дней). Один A100 - то же самое за 6 800 лет. Data parallelism + model parallelism - единственный способ уложиться в бюджет команды
  • **GPU inference:** H100 - 79.5 TFLOPS FP32, 3 958 TFLOPS FP8. Разрыв в 50x достигается именно параллелизмом внутри одного чипа - 16 896 ядер считают независимые части тензора одновременно
  • **Веб-серверы:** Nginx держит 10 000+ соединений на одном ядре через конкурентность - не параллелизм. Понимать разницу критично: ошибка в выборе модели стоит 10x производительности

Закон Мура и конец частотной гонки

2005 год. Intel тихо убивает Pentium 4 Prescott - чип на 3.8 ГГц, который грелся как утюг. Следующая версия на 4+ ГГц была готова - и отменена. Физика не позволила: мощность процессора растёт как куб от частоты. 10 ГГц потребляли бы сотни ватт, расплавив что угодно. С тех пор транзисторов становится больше, а частота - константа.

ГодПроцессорЧастотаТранзисторовЯдер
1993Pentium60 МГц3.1M1
2000Pentium 41.5 ГГц42M1
2004Pentium 4 Prescott3.8 ГГц125M1
2006Core 2 Duo2.4 ГГц291M2 ← поворот!
2024Core i9-14900K6.0 ГГц20,000M24

**Закон Мура** (1965): количество транзисторов на кристалле удваивается каждые ~2 года. Закон до сих пор работает! Но **следствие** "процессоры становятся быстрее каждый год" перестало выполняться. Транзисторы теперь идут на **больше ядер**, а не на **более быструю частоту**.

Herb Sutter назвал это в 2005-м «The Free Lunch Is Over» - и не преувеличил. Программа, написанная в 2000-м, к 2004-му ускорялась сама: Intel выпускала новый процессор, и всё работало быстрее. После 2005-го этот халявный прирост закончился. Однопоточный код на Core i9-14900K работает примерно столь же быстро, как на Core 2 Quad 2008 года. Транзисторов в 70 раз больше - но однопоточный код их не видит.

Herb Sutter: The Free Lunch Is Over (2005)

Статья Герба Саттера "The Free Lunch Is Over" стала манифестом параллельного программирования. Он предупредил: эра, когда программа ускоряется просто от покупки нового процессора, закончилась. Разработчикам придётся учиться параллелизму - или мириться со стагнацией производительности.

Почему тактовая частота процессоров перестала расти после 2005 года?

Закон Амдала

Добавить ядра - звучит как решение. Джин Амдал в 1967-м показал, почему это иллюзия. **Последовательная часть программы - абсолютный потолок ускорения**. Не важно, сколько ядер добавить - горлышко не расширится. Именно этот закон объясняет, почему PyTorch разделяет data parallelism (каждый GPU - своя копия модели) и model parallelism (слои модели на разных GPU): без разбивки последовательной части никакое количество H100 не помогает.

**Последовательное горлышко:** если 5% кода нельзя распараллелить, максимальное ускорение - 20x. Хоть миллион ядер добавьте. Вот почему оптимизация последовательной части часто важнее добавления ядер.

**Закон Густафсона** (1988) - более оптимистичный взгляд: с ростом числа процессоров мы увеличиваем и **размер задачи**. Если параллельная часть масштабируется с данными, ускорение растёт линейно: S(p) = p - s·(p-1). Это актуально для big data и научных вычислений.

Программа: 20% - последовательный ввод/вывод, 80% - параллельные вычисления. Сколько ядер нужно для 4x ускорения?

Параллелизм vs конкурентность

Два слова, которые смешивают постоянно - и это дорого стоит. Разработчик говорит «сделаем параллельно» и пишет `asyncio`. Получает конкурентность - и удивляется, что CPU-bound код не ускорился. **Параллелизм** - несколько вещей происходят одновременно на разных ядрах. **Конкурентность** - одно ядро умно чередует задачи, не теряя время на ожидание I/O. Роб Пайк сформулировал точно: concurrency про структуру программы, parallelism про исполнение.

СвойствоПараллелизм (Parallelism)Конкурентность (Concurrency)
СутьОдновременное выполнениеУправление множеством задач
ТребуетНесколько ядер/процессоровМожет работать на 1 ядре
ЦельУскорение вычисленийОтзывчивость, I/O overlap
ПримерРендер 4 кадров одновременноВеб-сервер обслуживает 1000 запросов
Аналогия4 кассы в магазине1 касса, но быстрое переключение

Роб Пайк (создатель Go): "Concurrency is about dealing with lots of things at once. Parallelism is about doing lots of things at once." Конкурентность - про **структуру** программы. Параллелизм - про **выполнение**. Можно иметь конкурентность без параллелизма (async на 1 ядре) и параллелизм без конкурентности (SIMD-инструкции).

Node.js - чистая конкурентность: один поток, event loop, тысячи соединений - но один CPU-bound вызов заморозит всё. NumPy и PyTorch - чистый параллелизм: BLAS-операции на нескольких ядрах через OpenBLAS/MKL, SIMD-инструкции, и ни одного `async` в API. Знать разницу - значит выбрать правильный инструмент вместо правдоподобного.

Веб-сервер на Node.js обрабатывает 10000 одновременных HTTP-запросов на одном ядре. Это пример:

Метрики ускорения

«Мы распараллелили и стало быстрее» - не измерение, а ощущение. Три метрики превращают ощущение в число. **Speedup** - во сколько раз быстрее. **Efficiency** - насколько честно используется каждое ядро. **Scalability** - что происходит при удвоении числа ядер. Без них невозможно сказать: проблема в алгоритме, в железе или в последовательном горлышке.

Тип масштабируемостиSpeedupПример
Линейная (идеальная)S(p) = pПолностью независимые задачи
Сублинейная (типичная)S(p) < pБольшинство реальных программ
Суперлинейная (редко)S(p) > pДанные помещаются в кеши при разбиении

**Суперлинейное ускорение** (S > p) кажется парадоксом, но возможно: когда данные разбиваются между ядрами, каждый кусок помещается в L2/L3 кеш, а исходный набор - нет. Кеш-эффект даёт дополнительный прирост сверх параллелизма.

**Strong scaling** - фиксированный объём работы, растёт число ядер. Быстро упирается в Амдала. **Weak scaling** - объём работы и число ядер растут вместе. Именно этот режим делает суперкомпьютеры полезными: задача на 1 000 GPU просто в 1 000 раз больше, чем задача на одном - и каждый GPU справляется со своей долей. Distributed training LLM работает именно так.

Не забывайте про **overhead** параллелизма: создание потоков, синхронизация, коммуникация. Для маленьких задач overhead может превысить выигрыш, и параллельная версия окажется **медленнее** однопоточной!

8 ядер = 8x быстрее

Ускорение ограничено последовательной частью кода (закон Амдала) и overhead параллелизма. 8 ядер обычно дают 3-6x ускорения.

Закон Амдала: если 10% кода выполняется последовательно, потолок - 10x при любом числе ядер. Но это теоретический максимум без учёта реального мира. На практике добавляется overhead: создание потоков стоит ~50 мкс каждый, синхронизация через mutex добавляет contention, cache lines передаются между ядрами за десятки нс. PyTorch знает об этом - поэтому `torch.compile` и TorchDynamo оптимизируют именно последовательные секции графа, а не только добавляют ядра.

Программа на 4 ядрах работает за 30с, на 1 ядре - за 100с. Какова эффективность параллелизации?

Ключевые идеи

  • **Power wall 2005:** частота стала константой, рост производительности ушёл в ядра - однопоточный код больше не ускоряется автоматически с новым железом
  • **Закон Амдала - главный холодный душ:** 5% последовательного кода = потолок 20x при любом количестве ядер. Именно поэтому оптимизация последовательной части ценнее добавления ядер
  • **Параллелизм vs конкурентность - разные инструменты:** параллелизм - несколько ядер, одновременное выполнение (PyTorch, NumPy, BLAS); конкурентность - одно ядро, умное чередование I/O (Node.js, asyncio)
  • **Метрики не врут:** Speedup = T₁/Tₚ, Efficiency = S/p. 8 ядер при 70% эффективности = 5.6x реального ускорения, а не 8x - overhead и Amdahl съедают остальное

Связанные темы

Параллелизм связан со многими областями CS:

  • Потоки и процессы — Базовые примитивы для реализации параллелизма
  • Синхронизация — Координация параллельных потоков - mutex, semaphore, barrier
  • Операционные системы — ОС управляет процессами, потоками и планированием на ядрах

Вопросы для размышления

  • Почему закон Амдала так пессимистичен, а реальные суперкомпьютеры с миллионами ядер всё же полезны?
  • Может ли конкурентность ускорить CPU-bound задачу? А параллелизм - I/O-bound?
  • Вернёмся к началу: если ваш телефон имеет 8 ядер, почему приложения всё ещё тормозят?

Связанные уроки

  • par-02 — Потоки и процессы - базовые примитивы, через которые параллелизм реализуется на практике.
  • os-01-intro — ОС управляет планированием ядер; понимание процессов и потоков ОС делает закон Амдала конкретным.
  • opt-01 — Параллелизм - инструмент оптимизации производительности; закон Амдала формализует trade-off между последовательной и параллельной частью.
  • alg-01-big-o — Сложность алгоритма определяет потолок параллелизации: алгоритм O(n²) с 50% последовательной частью не выиграет от 100 ядер.
  • calc-06-derivative-intro — Speedup как функция от числа ядер - та же идея убывающей предельной отдачи, что в математическом анализе и экономике.
  • arch-04-cpu
  • os-02-processes
Зачем нужен параллелизм

0

1

Войти