Почему в Cuda/OpenCL отсутствуют глобальные банковские конфликты?
Одна вещь, которую я не понял, и Google не помогает мне, почему это может иметь конфликты банков с общей памятью, но не в глобальной памяти? Могут ли быть банковские конфликты с регистрами?
ОБНОВЛЕНИЕ Вау, я действительно ценю два ответа от Tibbit и Grizzly. Кажется, что я могу поставить только зеленую галочку на один ответ. Я новичок в стек переполнения. Я думаю, я должен выбрать один ответ как лучший. Могу ли я сделать что-то, чтобы поблагодарить вас за ответ, который я не дал зеленый чек?
4 ответа
Краткий ответ: Банковских конфликтов нет ни в глобальной памяти, ни в регистрах.
Объяснение:
Ключ к пониманию того, почему нужно понимать детализацию операций. Один поток не имеет доступа к глобальной памяти. Глобальные обращения к памяти "слиты". Поскольку глобальная память слишком медленная, любой доступ потоков внутри блока группируется, чтобы сделать как можно меньше запросов к глобальной памяти.
Общая память может быть доступна для потоков одновременно. Когда два потока пытаются получить доступ к адресу в одном и том же банке, это вызывает конфликт банков.
Регистры не могут быть доступны ни одному потоку, кроме того, которому он назначен. Поскольку вы не можете читать или писать в мои регистры, вы не можете заблокировать мне доступ к ним - следовательно, нет никаких банковских конфликтов.
Кто может читать и писать в глобальную память?
Only blocks
, Один поток может сделать доступ, но транзакция будет обрабатываться на уровне блока (на самом деле уровень деформации / половины деформации, но я пытаюсь не усложнять). Если два блока обращаются к одной и той же памяти, я не верю, что это займет больше времени, и это может произойти с ускорением кешем L1 в новейших устройствах - хотя это не очевидно.
Кто может читать и писать в общую память?
Any thread within a given block.
Если у вас есть только 1 поток на блок, у вас не может быть конфликта с банком, но у вас не будет разумной производительности. Конфликты банков возникают из-за того, что блоку выделяется несколько, скажем, 512 потоков, и все они борются за разные адреса в одном и том же банке (не совсем один и тот же адрес). В конце "Руководства по программированию CUDA C" есть отличные изображения этих конфликтов - рис. G2, стр. 167 (фактически стр. 177 в pdf). Ссылка на версию 3.2
Кто может читать и писать в регистры?
Only the specific thread to which it is allocated.
Следовательно, только один поток обращается к нему одновременно.
Возможны ли банковские конфликты для данного типа памяти, очевидно, зависит от структуры памяти и, следовательно, от ее цели.
Так почему же общая память спроектирована таким образом, что допускает банковские конфликты?
Это относительно просто, не так просто спроектировать контроллер памяти, который может обрабатывать независимые обращения к одной и той же памяти одновременно (доказано тем, что большинство не может). Таким образом, для того чтобы каждый поток в полупериоде мог получить доступ к индивидуально адресуемому слову, память распределяется по банкам с независимым контроллером для каждого банка (по крайней мере, так можно думать об этом, не будучи уверенным в фактическом оборудовании). Эти банки чередуются для обеспечения быстрого доступа последовательных потоков к последовательной памяти. Таким образом, каждый из этих банков может обрабатывать один запрос за раз, идеально допуская одновременное выполнение всех запросов в полупериоде (очевидно, что эта модель теоретически может поддерживать более высокую пропускную способность из-за независимости этих банков, что также является плюсом).
А как насчет регистров?
Регистры предназначены для доступа к ним в качестве операндов для инструкций ALU, то есть к ним нужно обращаться с очень низкой задержкой. Поэтому они получают больше транзисторов / бит, чтобы сделать это возможным. Я не уверен, как именно регистры доступны в современных процессорах (не та информация, которая вам нужна часто и которую не так легко найти). Однако, очевидно, было бы крайне непрактично организовывать регистры в банках (для более простых архитектур вы обычно видите, что все регистры висят на одном большом мультиплексоре). Так что нет, не будет банковских конфликтов для реестров.
Глобальная память
Прежде всего, глобальная память работает с другой гранулярностью, чем разделяемая память. Доступ к памяти осуществляется в блоках по 32, 64 или 128 байт (по крайней мере, для GT200, по крайней мере, для fermi он равен 128B, но кешируется, AMD немного отличается), где каждый раз, когда вам требуется что-то из блока, осуществляется доступ к целому блоку / его передача. Вот почему вам нужен объединенный доступ, так как, если каждый поток обращается к памяти из другого блока, вы должны перенести все блоки.
Но кто сказал, что нет банковских конфликтов? Я не совсем уверен в этом, потому что я не нашел ни одного фактического источника для поддержки этого для оборудования NVIDIA, но это кажется логичным: глобальная память обычно распределяется по нескольким чипам оперативной памяти (что можно легко проверить, посмотрев на видеокарта). Это имеет смысл, если каждый из этих чипов подобен банку локальной памяти, поэтому вы можете столкнуться с банковскими конфликтами, если в одном и том же банке есть несколько одновременных запросов. Однако эффекты будут гораздо менее выражены с одной стороны (поскольку большая часть времени, затрачиваемого на доступ к памяти, в любом случае представляет собой задержку при получении данных от A до B), и это не будет заметным эффектом "внутри" одной рабочей группы. (поскольку за один раз выполняется только одна полуверсия, и если эта полуверсия выдает более одного запроса, у вас есть несвязанный доступ к памяти, поэтому вы уже получаете удар, затрудняющий измерение последствий этого конфликта. Таким образом, вы получите конфликты только в том случае, если несколько рабочих групп пытаются получить доступ к одному и тому же банку. В вашей типичной ситуации для gpgpu у вас есть большой набор данных, лежащий в последовательной памяти, поэтому эффекты не должны быть заметны, так как есть достаточное количество других рабочих групп, одновременно обращающихся к другим банкам, но это должна быть возможность построить ситуации, когда набор данных центрируется только на нескольких банках, что может привести к снижению пропускной способности (поскольку максимальная пропускная способность будет зависеть от равномерного распределения доступа по всем банкам, поэтому каждый банк будет только часть этой полосы пропускания). Опять же, я ничего не читал, чтобы доказать эту теорию для оборудования nvidia (в основном все сосредоточено на объединении, что, конечно, более важно, так как делает это проблемой для естественных наборов данных). Однако, согласно руководству по вычислениям ATI Stream, это ситуация для карт Radeon (для 5xxx: банки разнесены на 2 КБ, и вы хотите убедиться, что вы распределяете свои доступы (то есть из всех ворггрупп, симулирующих активные) равными по всем банкам), поэтому я Вообразил бы, что карты NVidia ведут себя аналогично.
Конечно, для большинства сценариев возможность возникновения банковских конфликтов в глобальной памяти не является проблемой, поэтому на практике вы можете сказать:
- Следите за объединением при доступе к глобальной памяти
- Следите за конфликтами банков при доступе к локальной памяти
- Нет проблем с доступом к регистрам
Несколько потоков, обращающихся к одному и тому же банку, не обязательно означают конфликт банков. Возникает конфликт, если потоки хотят одновременно читать из РАЗНОГО СТРОКА в одном и том же банке.
Почему возможны конфликты банков с общей памятью, но не с глобальной памятью?
Конфликты банков и конфликтов каналов действительно существуют при доступе к глобальной памяти. Максимальная глобальная пропускная способность памяти достигается только тогда, когда каналы и банки памяти имеют равномерный доступ в циклическом порядке. Для линейного доступа к памяти к одному одномерному массиву контроллер памяти обычно предназначен для автоматического равномерного чередования запросов к памяти каждого банка и канала. Однако при одновременном доступе к нескольким одномерным массивам (или разным строкам многомерного массива) и если их базовые адреса кратны размеру канала или банка памяти, может произойти несовершенное чередование памяти. В этом случае один канал или банк подвергается большему воздействию, чем другой канал или банк, что приводит к сериализации доступа к памяти и уменьшению доступной глобальной пропускной способности памяти.
Из-за отсутствия документации я не совсем понимаю, как это работает, но оно наверняка существует. В своих экспериментах я наблюдал снижение производительности на 20% из-за неудачных базовых адресов памяти. Эта проблема может быть довольно коварной — в зависимости от размера выделения памяти снижение производительности может происходить случайным образом. Иногда размер выравнивания распределителя памяти по умолчанию также может быть слишком умным для его же блага - когда базовый адрес каждого массива выровнен по большому размеру, это может увеличить вероятность конфликта канала/банка, иногда приводя к 100% вероятности конфликта. время. Я также обнаружил, что выделение большого пула памяти, а затем добавление ручных смещений для «смещания» меньших массивов вдали от одного и того же канала/банка может помочь смягчить проблему.
Схема чередования памяти иногда может оказаться сложной. Например, в руководстве AMD говорится, что графические процессоры серии Radeon HD 79XX имеют 12 каналов памяти — это не степень двойки, поэтому сопоставление каналов далеко не интуитивно понятно без документации, поскольку его нельзя просто вывести из битов адреса памяти. К сожалению, я обнаружил, что поставщики графических процессоров зачастую плохо документируют эту возможность, поэтому может потребоваться метод проб и ошибок. Например, руководство AMD по оптимизации OpenCL ограничено только оборудованием GCN и не предоставляет никакой информации для оборудования новее, чем Radeon HD 7970 — информация о новых графических процессорах GCN с HBM VRAM, найденных в Vega, или о новых архитектурах RDNA/CDNA. полностью отсутствует. Однако AMD предоставляет расширения OpenCL для сообщения о размерах каналов и банков оборудования, что может помочь в экспериментах. На моем Radeon VII/Instinct MI50 они:
Global memory channels (AMD) 128
Global memory banks per channel (AMD) 4
Global memory bank width (AMD) 256 bytes
Огромное количество каналов, вероятно, является результатом 4096-битной памяти HBM2.
Руководство AMD по оптимизации
Старое руководство по оптимизации OpenCL AMD APP SDK от AMD дает следующее объяснение:
2.1 Глобальная оптимизация памяти
[...] Если два запроса доступа к памяти направляются на один и тот же контроллер, аппаратное обеспечение сериализует доступ. Это называется конфликтом каналов. Аналогично, если два запроса доступа к памяти поступают в один и тот же банк памяти, аппаратно сериализует доступ. Это называется банковским конфликтом. С точки зрения разработчика, особой разницы между конфликтами каналов и банков нет. Часто большая мощность двух шагов приводит к конфликту каналов. Размер силы двух шагов, вызывающей конфликт конкретного типа, зависит от чипа. Шаг, который приводит к конфликту каналов на машине с восемью каналами, может привести к конфликту банков на машине с четырьмя. В этом документе термин «банковский конфликт» используется для обозначения любого вида конфликта.
2.1.1 Конфликты каналов
Важным понятием является шаг памяти: приращение адреса памяти, измеряемое в элементах, между последовательными элементами, выбранными или сохраненными последовательными рабочими элементами в ядре. Многие важные ядра не используют исключительно простые шаблоны доступа с одним шагом; вместо этого они имеют большие неединичные шаги. Например, многие коды выполняют аналогичные операции с каждым измерением двух- или трехмерного массива. Выполнение вычислений в нижнем измерении часто может выполняться с единичным шагом, но шаги вычислений в других измерениях обычно имеют большие значения. Это может привести к значительному снижению производительности при переносе кодов в системы графических процессоров в неизмененном виде. Процессор с кэшем представляет ту же проблему: при больших шагах в степени двойки данные помещаются всего в несколько строк кэша.
Одним из решений является переписать код, чтобы использовать транспозицию массивов между ядрами. Это позволяет выполнять все вычисления с единичным шагом. Убедитесь, что время, необходимое для транспонирования, относительно мало по сравнению со временем выполнения вычисления ядра.
Для многих ядер снижение производительности достаточно велико, поэтому стоит попытаться разобраться и решить эту проблему.
При программировании на графическом процессоре лучше всего, чтобы соседние рабочие элементы считывали или записывали соседние адреса памяти. Это один из способов избежать конфликтов каналов. Когда приложение имеет полный контроль над шаблоном доступа и генерацией адресов, разработчик должен организовать структуры данных так, чтобы минимизировать банковские конфликты. Доступы, которые отличаются младшими битами, могут выполняться параллельно; те, которые отличаются только старшими битами, могут быть сериализованы.
В этом примере:
for (ptr=base; ptr<max; ptr += 16KB) R0 = *ptr ;
где все младшие биты одинаковы, все запросы памяти обращаются к одному и тому же банку на одном и том же канале и обрабатываются последовательно. Это модель низкой производительности, которой следует избегать. Когда шаг равен степени 2 (и больше, чем чередование каналов), приведенный выше цикл обращается только к одному каналу памяти.
Также стоит отметить, что распределение доступа к памяти по всем каналам не всегда помогает повысить производительность, а наоборот, может ухудшить производительность. AMD предупреждает, что может быть лучше получить доступ к одному и тому же каналу/банку памяти в одной рабочей группе — поскольку графический процессор одновременно запускает множество рабочих групп, достигается идеальное чередование памяти. С другой стороны, доступ к нескольким каналам/банкам памяти в одной рабочей группе снижает производительность.
Если каждый рабочий элемент в рабочей группе ссылается на последовательные адреса памяти, а адрес рабочего элемента 0 выровнен по 256 байтам, а каждый рабочий элемент извлекает 32 бита, весь волновой фронт обращается к одному каналу. Хотя это кажется медленным, на самом деле это быстрый шаблон, поскольку необходимо учитывать доступ к памяти всего устройства, а не только одного волнового фронта.
[...]
В любой момент времени каждый вычислительный блок выполняет команду одного волнового фронта. В ядрах с интенсивным использованием памяти вполне вероятно, что инструкция представляет собой доступ к памяти. Поскольку графический процессор AMD Radeon HD 7970 имеет 12 каналов, максимум 12 вычислительных блоков могут выполнить операцию доступа к памяти за один такт. Наиболее эффективно, если доступы с 12 волновых фронтов будут идти по разным каналам. Один из способов добиться этого — для каждого волнового фронта получить доступ к последовательным группам по 256 = 64 * 4 байта. Обратите внимание, как показано на рис. 2.1, выборка 256 * 12 байт подряд не всегда выполняется по всем каналам. Неэффективная модель доступа — это ситуация, когда каждый волновой фронт обращается ко всем каналам. Это может произойти, если последовательные рабочие элементы обращаются к данным, имеющим большую мощность в два шага.
Прочтите исходное руководство для получения более подробной информации об аппаратной реализации, которая здесь опущена.