- BrainTools - https://www.braintools.ru -

Граф из миллионов вершин не загружает современную GPU на все 100%: видеокарта почти всё время не вычисляет, а ждёт загрузки данных из памяти [1]. Графовые нейросети, или GNN, упираются в это давно: сами операции достаточно простые, но доступ к памяти нерегулярный и разреженный. И чем мощнее GPU, тем заметнее недостаточная её утилизация.
Идея выросла из проектного курса в ШАДе. Толчком стало то, что один из самых популярных фреймворков для работы с графами, Deep Graph Library, на момент начала работы не обновлялся уже около года — это знак того, что в области что‑то застряло.
Меня зовут Федя Великонивцев, я старший исследователь Yandex Research, руковожу группой, которая занимается эффективными вычислениями на GPU. На том курсе мы с коллегами — Дарьей Фоминой из команды ML‑инфраструктуры Яндекса, Вячеславом Ждановским из команды разработки инференса — и студентами Даниилом Красильниковым, Алексеем Бойковым и Андреем Долговязовым взялись выяснить, почему графовые нейросети тормозят на современных GPU.
Так появился проект, который мы оформили в отдельную статью — On Efficient Scaling of GNNs via IO‑Aware Layer Implementations [2]. Её приняли на ICML-2026 со статусом Spotlight. Для контекста: из 23 918 поданных работ приняли 6 352 (26,6%), а Spotlight достался только 536 работам — это 2,2% заявок с самыми высокими оценками программного комитета.
Дальше расскажу, как мы прошли путь от этого вопроса до трёх семейств специализированных GPU‑кернелов — с парой неожиданных находок по дороге.
Графовые нейросети используются во множестве прикладных задач: рекомендательные системы, антифрод, анализ социальных сетей, работа с молекулами, прогнозирование временных рядов.
Но есть проблема: графы очень неудобны для GPU.
В отличие от трансформеров, где данные обычно лежат в памяти компактно и регулярно, для выполнения агрегации признаков соседей графовые модели постоянно обращаются к случайным участкам памяти. Дело в том, что на вершинах нет порядка и индексы вершин могут быть выданы произвольно. У одной вершины может быть три соседа, у другой — три тысячи, а их признаки могут находиться в совершенно разных местах памяти.
Сами же графовые агрегации — это в большинстве своём вычислительно несложные операции (например, усреднение признаков по соседям), но есть и исключения в виде порёберного внимания [3], когда признаки соседей агрегируются с обучаемыми коэффициентами по аналогии с трансформерами.
Всё это приводит к тому, что GPU нередко проводят больше времени за ожиданием данных из памяти, чем за выполнением вычислений.
По мере роста производительности GPU эта проблема становится только заметнее: вычислительные блоки становятся быстрее, а пропускная способность памяти растёт значительно медленнее.
Похожая проблема несколько лет назад возникла у трансформеров. Работа FlashAttention и её последующие продолжения показали, что ускорение может достигаться в том числе за счёт уменьшения количества обращений к памяти.
Мы решили посмотреть на графовые нейросети с той же точки зрения [4]. Соответственно, один из вопросов, который мы задали, был: «Что, если оптимизировать не сами вычисления GNN, а то, как данные перемещаются между HBM (High Bandwidth Memory) и быстрой памятью GPU?»
Большинство популярных GNN можно свести к трём типам вычислительных паттернов:
Свертки, представляемые в виде матричного умножения, — простые арифметические операции взвешенной суммы признаков соседей, например усреднение. Такие операции представляются в виде умножения матрицы признаков на разреженную матрицу смежности графа — этот паттерн вычисления называется Sparse‑Dense Matrix Multiplication (SpMM).
Свертки на основе редукции — различные варианты min/max‑агрегации и pooling’ов, которые нельзя представить в виде матричного умножения.
Attention‑свертки — свертки с обучаемыми коэффициентами для агрегации признаков. Например, GATv2 и Graph Transformer.
Для каждого семейства боттлнеки оказались разными, поэтому и решения пришлось разрабатывать отдельно.
Расскажу, как мы получили заметный выигрыш для attention‑based‑моделей.
Обычные реализации GATv2 и Graph Transformer создают большие промежуточные тензоры для каждого ребра графа. Эти данные записываются в глобальную память и затем снова считываются для вычисления softmax и агрегации.
Мы убрали этот этап.
Вместо материализации промежуточных значений вычисления выполняются непосредственно во время прохода по соседям вершины. Для этого используется online softmax — тот же базовый принцип, который лежит в основе FlashAttention.
В результате:
уменьшается объём передаваемых данных;
снижается нагрузка на HBM;
сокращается пиковое потребление памяти.
Для backward pass мы используем идею из FlashAttention-2: веса внимания не сохраняются целиком, а пересчитываются на лету из компактной статистики.
Дополнительно мы добавили простое балансирование нагрузки, позволяющее выделять больше параллельных вычислений для вершин с большим количеством соседей.
Кроме того, мы реализовали вариант с блочно‑разреженным представлением графа, позволяющим использовать тензорные ядра — специализированные модули на GPU, отвечающие за выполнение инструкций матричного умножения. Такой подход особенно эффективен для графов с высокой плотностью.
Для операций на основе редукции проблема оказалась другой. У реальных графов обычно тяжёлый хвост распределения степеней: большинство вершин имеют немного соседей, но существует небольшое количество очень крупных вершин. Именно такие вершины часто становятся главным источником замедления.
Чтобы решить эту проблему, мы разделяем вершины на две группы: лёгкие — с малым числом соседей, тяжёлые — с большим. Делаем это, отсекая по статистикам степеней вершин в графе.
Вычисления мы распределяем по‑разному. Для тяжёлых вершин мы параллельно считаем редукцию по чанкам и затем параллельно агрегируем результат. Мы можем себе это позволить, так как такая операция ассоциативна (то есть не зависит от порядка вычисления).
Это позволяет лучше загрузить GPU и убрать дисбаланс вычислений.
Один из интересных результатов работы связан с GNN, представляемыми в виде SpMM. Мы ожидали, что специализированные реализации смогут существенно обогнать существующие решения.
На практике оказалось иначе.
Для многих SpMM‑based‑операций современные библиотеки NVIDIA (например, взятая нами cuSPARSE [5]) уже настолько хорошо оптимизированы, что оказываются быстрее большинства опубликованных специализированных реализаций. После правильного кеширования и настройки cuSPARSE зачастую показывает лучший результат.
Это хороший пример того, что иногда оптимизация заключается не в написании нового кернела, а в правильном использовании уже существующих инструментов.
На наборе реальных графов и популярных архитектур нам удалось получить:
до 8,5× ускорения для attention‑сверток;
до 10× ускорения для некоторых сверток‑редукций операций;
до 8× ускорения при использовании модифицированного cuSPARSE для SpMM‑сверток;
до 76× снижения пикового потребления памяти в отдельных конфигурациях.
Особенно важно, что все реализации доступны как drop‑in replacement для популярных GNN‑фреймворков. Для использования не требуется переписывать модель или менять архитектуру.
Граф — структура без порядка: вершины можно пронумеровать как угодно, и это не повлияет на результат вычислений. Но то, в каком порядке признаки вершин лежат в памяти GPU, очень влияет на производительность.
Дело в том, что GPU подгружает данные из HBM не по одному числу, а блоками. Если нужный байт лежит рядом с уже загруженными, он достаётся «бесплатно», прямо из кеша. При этом связи в графе — кто с кем сосед — изначально никак не привязаны к тому, как признаки вершин разложены в памяти. Когда кернел проходит по соседям одной вершины, он может прыгать по совершенно случайным адресам. Каждый такой прыжок — отдельный «холодный» поход в HBM: кеш ограничен, и данные, которые мы доставали секунду назад, могли уже быть вытеснены другими.
Идея переупорядочивания простая: если переставить индексы вершин так, чтобы соседи по графу оказались соседями и по памяти. Тогда при обходе графа данные будут чаще попадать в кеш. Это похоже на то, как если бы вы расставляли книги на полке не по алфавиту, а по тому, какие из них вы читаете друг за другом, — тогда не придётся бегать по всей комнате за каждой следующей.
Вот так, например, может выглядеть матрица смежности графа после переупорядочивания:

Цвет показывает плотность ненулевых значений в блоке 128 × 128. Слева — до переупорядочивания: соседи разбросаны по всей матрице. Справа — после. Видно, что вершины по большей части сконцентрированы рядом с теми, с которыми они связаны
Кстати, этим вопросом занимались и до нас. Например, работа Can Graph Reordering Speed Up Graph Neural Network Training [6] показывает, что для обучения [7] GNN переупорядочивание может давать честный выигрыш. Мы решили разобраться, при каких условиях это работает, а при каких — нет.
Оказалось, что эффект зависит от того, как именно кернел читает данные. Мы сравнили два паттерна доступа: один параллелит вычисления по признакам (feature‑parallel, данные читаются предсказуемо, подряд), другой — по соседям (neighbor‑parallel, каждый поток читает своего соседа из случайного места). Переупорядочивание помогает в основном второму паттерну — там, где без него память читается совсем вразнобой.
Дальше — интереснее: эффект зависит ещё и от степени вершин. На графах с высокой степенью — например, в графах покупок или социальных сетях, где у вершины могут быть десятки и сотни соседей, — переупорядочивание даёт стабильный и заметный прирост: там действительно есть что кешировать.
А вот на графах с низкой степенью — скажем, дорожных сетях, где у вершины в среднем два соседа, — выигрыша почти нет. Соседей просто слишком мало, чтобы локальность вообще на что‑то влияла, и время съедают накладные расходы на каждую вершину, а не промахи кеша.
Получается, переупорядочивание — это не универсальный буст, а инструмент, который нужно применять осознанно: смотреть на структуру графа и на то, как написан кернел, который будет по нему бегать.
Последние несколько лет машинное обучение училось простой вещи: быстрый алгоритм — это не только хорошая математика [8], но и реализация, которая дружит с железом. FlashAttention показал это на трансформерах. Мы убедились, что с графовыми нейросетями всё ровно так же.
И чем больше становятся графы, чем быстрее GPU, тем сильнее перекос: считать мы научились быстро, а вот подвозить данные из памяти — нет. Так что работа с памятью потихоньку превращается в отдельную инженерную задачу, не менее важную, чем сами вычисления. Похоже, писать алгоритмы под конкретное железо придётся всё чаще.
Подробнее — в нашей статье [2]. Исходный код и библиотека с GPU‑кернелами доступны на GitHub [9] и могут использоваться как drop‑in replacement для существующих архитектур.
Если тема для вас актуальна, подписывайтесь на YR Reading Group [10]. А всё самое интересное с конференции, как всегда, расскажем в MLunderhood [11].
Автор: mightyneighbor
Источник [12]
Сайт-источник BrainTools: https://www.braintools.ru
Путь до страницы источника: https://www.braintools.ru/article/32676
URLs in this post:
[1] памяти: http://www.braintools.ru/article/4140
[2] On Efficient Scaling of GNNs via IO‑Aware Layer Implementations: https://arxiv.org/abs/2605.31500
[3] внимания: http://www.braintools.ru/article/7595
[4] зрения: http://www.braintools.ru/article/6238
[5] cuSPARSE: https://developer.nvidia.com/cusparse
[6] Can Graph Reordering Speed Up Graph Neural Network Training: https://arxiv.org/html/2409.11129v1
[7] обучения: http://www.braintools.ru/article/5125
[8] математика: http://www.braintools.ru/article/7620
[9] GitHub: https://github.com/yandex-research/On-Efficient-Scaling-Of-GNNs
[10] YR Reading Group: https://t.me/YSDA_YR_2019
[11] MLunderhood: https://t.me/MLunderhood
[12] Источник: https://habr.com/ru/companies/yandex/articles/1055232/?utm_campaign=1055232&utm_source=habrahabr&utm_medium=rss
Нажмите здесь для печати.