Выступление профессора Адвайта Джога в Институте информационных технологий в Бомбее, 29 марта 2023 г.

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

Пришло время процессорам отказаться от работы с тяжелой параллельной обработкой и передать эстафету так называемым графическим процессорам (GPU). Это не шокирует непостижимая потребность в высокоскоростной вычислительной мощности в ML/DL, Graphics, Crypto и т. д., учитывая их растущую популярность и спрос. Итак, давайте погрузимся прямо в эти таинственные и мощные процессоры. Что именно они собой представляют? Каковы их компромиссы? Безопасны ли они? Надежный? Помогут ли они ИИ захватить мир? Постараюсь ответить на все, кроме последнего вопроса.

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

Все мы знаем, что такое процессоры. Они выполняют инструкции машинного уровня на двоичном уровне и, по сути, управляют нашим компьютером. Я признаю, что процессоры отлично подходят для последовательных инструкций, другими словами, инструкций, в которых ввод одного зависит от вывода предыдущего. Это инструкции, которые имеют смысл только в том случае, если выполняются в определенном порядке, и, следовательно, нет смысла выполнять их одновременно. ЦП хорошо справляется с тем, чтобы эти инструкции выполнялись с наименьшей задержкой. Конечно, вы не сможете одновременно провести тысячи таких казней, но это потому, что вы этого не хотите. Вы хотите их по порядку, последовательно, один за другим.

Как насчет независимых инструкций?
Возьмем, к примеру, умножение матриц. Никоим образом не требуется определенный элемент результирующей матрицы для вычисления другого. Продукты полностью независимы. Потенциально мы могли бы выполнить все умножения вместе, используя кучу АЛУ, почти за короткое время и просуммировать соответствующие произведения, чтобы получить результирующую матрицу. Ну, типичный процессор имеет только около 8 ядер, работающих вместе. 8 — ничто по сравнению с размером матриц, используемых в типичной модели машинного обучения.

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

Давайте посмотрим на структуру GPU.
Графический процессор обычно делится на несколько процессорных блоков, каждый из которых имеет потоковые процессоры. Каждый потоковый процессор имеет доступ к одному кешу уровня 1, и обычно они совместно используют кеш уровня 2. Каждый адрес памяти в контексте графического процессора имеет идентификатор потока. Поскольку графические процессоры так часто используются в многопоточных системах, они имеют тысячи регистров, каждому из которых назначается поток. Следует отметить, что в целом графические процессоры следуют структуре SIMD, также известной как Single Instruction Multiple Data. Одна инструкция копируется и выполняется на каждом ядре одновременно. Это возможно, поскольку у каждого процессора есть собственная выделенная память, которая обеспечивает параллелизм на уровне данных (также известный как «параллелизм данных»).
Графические процессоры также выполняют нечто, называемое объединением. По сути, это объединение нескольких обращений к памяти в одно.

Вот пример. Допустим, у нас есть 8 векторов и 8 потоков, каждый из которых запрашивает первый элемент в соответствующем векторе, затем второй, затем третий и так далее. Естественным способом хранения векторов является сохранение последовательных элементов в последовательных ячейках памяти, так что каждый вектор хранится непрерывно. Однако это означает, что каждый поток должен будет осуществлять отдельный доступ к данным из DRAM. Вместо этого, если бы данные каким-то образом хранились таким образом, что потребовался бы единый доступ для всех потоков, это сэкономило бы нам массу времени.

Естественно, последовательное хранение первых элементов всех векторов сделало бы так, что один запрос доступа к этой строке удовлетворил бы требования к памяти всех потоков.

Круто не правда ли! По сути, у нас есть структура со многими подструктурами, которые могут выполнять инструкции вместе, каждая со своим назначенным кешем L1, адресами памяти с индексами потоков, несколькими логическими единицами, быстрым доступом и т. д. Что может пойти не так?

ГП — это только люди

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

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

Другим довольно важным вопросом является «размер кристалла» или размер чипа. Графические процессоры, как правило, очень большие из-за большого количества процессорных блоков. Но это проблемы масштабируемости. Оставим их пока в стороне. Нам предстоит разобраться с чем-то большим и опасным.

Безопасность

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

Мы подходим к сути доклада проф. Джога.

Временные атаки на графические процессоры

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

Давайте снова посмотрим на объединение. Время, необходимое для выполнения, будет пропорционально количеству объединенных доступов, поскольку все потоки используют общий доступ. Ну и что? Что ж, давайте разберемся, что такое тайминг-атака.

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

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

Итак, как мы можем решить эту проблему?

  • Отключить объединение:
    Это очень хорошо работает с точки зрения безопасности. Корреляция между временем выполнения и шаблонами ключей становится равной 0, и правильный байт ключа невозможно декодировать.
    Естественно, это также снижает производительность примерно на 178% и увеличивает размер обычного текста, а это то, чего мы определенно не хотим.

Современные решения

  • Рандомизированное объединение
    Работает хорошо, но приводит к большим потерям производительности и по-прежнему уязвимо из-за кешей и MSHR (отсутствуют регистры хранения статуса).
  • Программные решения
    Устранение утечек только в приложениях AES

Кажется, что ни один из них не работает так, как мы этого хотим.

Именно здесь в игру вступают профессор Джог и его команда из Университета Вирджинии :)

Объединение памяти на основе сегментирования

Святые ведра

Решение заключается в объединении в ведрах. Количество объединенных доступов больше не пропорционально времени выполнения, поскольку оно зависит от размера корзины! Большее количество сегментов явно повысит безопасность за счет уменьшения корреляции, а меньшее количество повысит производительность и пропускную способность.

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

Надежность

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

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

Давайте рассмотрим управление избыточными данными.

Не все ошибки памяти одинаковы

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

Разделите память на различные группы; высокодоступные, широко используемые и горячая память. Можно заметить, что на самом деле очень небольшая часть памяти часто используется или совместно используется, а выходные данные любой программы оченьчувствительны к ошибкам в горячей памяти.
Мы используем то, что известно как профилирование исходного кода для выявления горячих участков памяти. Это может быть автоматизировано и должно быть сделано только один раз, в автономном режиме. Горячая память занимает очень мало места и может быть легко воспроизведена для обнаружения/исправления ошибок.
Это приводит к снижению скрытого повреждения данных на 98,97 % и снижению производительности всего на 1,2 %. Управление избыточными данными может помочь в повышении безопасности и надежности при низких накладных расходах.

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

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

Партх Пуджари,
информатика и инженерия, ИИТ Бомбея