← Все статьи

Три уровня стека языковой модели: ядро, граф и система

Как устроен программный стек большой языковой модели: вычисления, память, обмен данными и параллелизм обучения и вывода.

Три уровня стека языковой модели: ядро, граф и система
Содержание

Коротко

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

Что произошло

Любую такую систему проектируют под конкретные цели и жёсткие пределы. Для языковых моделей главные цели — пропустить больше запросов и сократить ожидание ответа. Пределы задают скорость и типы аппаратных операций, объём и иерархия памяти, а также ширина, задержка и иерархия передачи данных — и внутри машины, и по сети. Автор отсылает к разделу Roofline в Scaling Book от Google: там как раз видно, упираетесь вы в счёт или в подвоз данных.

Три уровня абстракции разводят эти пределы по разным инструментам. Ядро — скалярные и векторные команды и команды над плитками данных; типичные среды — CUDA, C/C++, PTX, Triton. Граф — тензорные операции одной модели на одной видеокарте: здесь важны собираемость и то, насколько легко поменять схему. Система — как делить память, вычисления и связь между многими устройствами при обучении и при выводе. На каждом этаже оптимизируют своё, и приём с нижнего уровня не заменяет решение на верхнем.

На уровне ядра показательный пример — внимание. Наивная формула заставляет таскать большую строку матрицы внимания из глобальной памяти. Онлайновый softmax переписывает ту же формулу как рекуррентную, и тогда не нужны лишние чтения и записи в глобальную память. На уровне графа исторически удобные среды проигрывали по скорости специализированным: для серьёзного вывода брали ONNX Runtime, TensorRT, FasterTransformer. Сейчас PyTorch отыграл долю вывода: дешевле стали графы CUDA и torch.compile, а сами языковые модели настолько тяжелы, что накладные расходы каркаса уже не главная статья. Типичные приёмы графа — слияние соседних ядер, объединение операций, квантование и разреженность: меньше гонять данные и меньше занимать память при тех же вычислениях.

На системном уровне почти любой параллелизм поднимает пропускную способность, но не любой снижает задержку. Параллелизм по данным режет пакеты: при выводе обмена почти нет, при обучении он слабый, задержка не падает. Конвейер режет пакеты и слои: связь слабая и при обучении, и при выводе, память под модель экономится, задержка снова не падает. Тензорный параллелизм режет полносвязные слои по строкам и столбцам и внимание по головам: задержка снижается, зато обмен дорогой. Выбор зависит от того, чего не хватает — памяти или канала связи. Объём вычислений при этом обычно постоянен; исключения вроде полностью разделённого параллелизма данных автор оговаривает отдельно. Для связки обучения и вывода он указывает на VeRL и стыковку с Megatron-LM и vLLM, а обзор стратегий — на документацию NeMo и DeepSpeed.

Почему это важно

Без этой карты легко ускорять не тот этаж. Квантование не починит дорогой обмен при тензорном параллелизме, а лишняя видеокарта не уберёт задержку, если схема параллелизма её принципиально не снижает. Инженеру, который выбирает между «дообучить», «сжать» и «разложить на несколько карт», полезно сначала назвать ограничение: счёт, память или связь.

Второй практический сдвиг — вывод больше не живёт в отдельном мире от обучения. PyTorch на выводе и общие каркасы вроде связки Megatron-LM с vLLM значат, что одна и та же команда может говорить на одном стеке, а не держать две несовместимые цепочки. Это не отменяет специализированные среды там, где каждый процент задержки стоит денег. Это убирает привычку считать «удобный граф» и «быстрый вывод» взаимоисключающими.

На практике

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

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

  1. Назовите узкое место до выбора приёма: вычисления, память или передача данных.
  2. На одной карте сначала смотрите слияние операций, квантование и то, не гоняет ли ядро лишние данные, как в примере с вниманием.
  3. Если добавляете устройства, проверьте, падает ли задержка: параллелизм по данным и конвейер её не снижают.
  4. Не держите обучение и вывод в несовместимых стеках без причины — посмотрите, стыкуются ли ваши каркасы так, как описано для Megatron-LM и vLLM.

Итог

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

Комментарии

Загрузка комментариев…