Какова связь между занятостью потоков графического процессора и остановками синхронизации?

Я пишу ядро ​​CUDA с внутренним циклом, который выглядит примерно так:

for (int i = 0; i < NUM_ITERATIONS; i++)
{
  // read global memory, write shared memory
  __syncthreads();
  // read shared memory, do math
  __syncthreads();
}

Для повышения производительности я хочу минимизировать общее количество времени, которое потоки тратят на ожидание достижения барьерной синхронизации другими потоками. Повлияет ли количество потоков на блок на среднее время, которое поток проводит в ожидании у барьера? Общее количество времени, которое все потоки проводят в ожидании? Что делать, если у меня ядро ​​с низкой занятостью (т. е. много регистров на поток, малое количество потоков на блок), существуют ли какие-либо стратегии, которые могут помочь уменьшить ожидание синхронизации в этом случае?

Загадки Python - Генерация простых чисел!
Загадки Python - Генерация простых чисел!
Обычно существует несколько способов решения задач даже пограничной сложности. Как же определить оптимальное и эффективное решение?
3
0
73
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Повлияет ли количество потоков на блок на среднее время, которое поток проводит в ожидании у барьера?

Вероятный. Большее количество потоков на блок, безусловно, увеличивает худший случай. Допустим, вы запускаете 512 потоков на блок на RTX 3080 (CUDA 8.6, 1536 потоков на SM), затем три варпа (по одному на блок) могут заблокировать продвижение вперед 45 других варпов на том же SM из-за опоздания.

Вам следует провести собственные тесты, но в прошлый раз, когда я тестировал это, я использовал блоки меньшего размера, даже если это немного снизило эффективность памяти.

Общее количество времени, которое все потоки проводят в ожидании? Что делать, если у меня ядро ​​с низкой занятостью (т. е. много регистров на поток, малое количество потоков на блок)

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

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

существуют ли какие-либо стратегии, которые могут помочь уменьшить ожидание синхронизации в этом случае?

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

__global__ void kernel()
{
  __shared__ float data[N];
  for (int i = 0; i < NUM_ITERATIONS; i++)
  {
    data[threadIdx.x] = load_global();
    __syncthreads();
    float loc = data[y];
    __syncthreads();
  }
}

сделай это:

__global__ void kernel()
{
  __shared__ float data[2][N];
  for (int i = 0; i < NUM_ITERATIONS; i++)
  {
    data[i & 1][threadIdx.x] = load_global();
    __syncthreads();
    float loc = data[i & 1][y];
  }
}

Вы можете расширить это, используя Асинхронные копии данных . Примеры CUDA показывают, как это сделать, в примере globalToShmemAsyncCopy. Я думаю, что ядро ​​MatrixMulAsyncCopyMultiStage — это то, что вам нужно.

Я пишу это ядро ​​для графического процессора с архитектурой Тьюринга. Я провел небольшое экспериментирование и обнаружил, что конвейер cuda и асинхронное копирование довольно неэффективны на устройствах Тьюринга. У Ampere/Hopper есть специальное оборудование, к которому подключаются эти функции. Они работают на старых устройствах, но неэффективны.

Alex Armbruster 18.06.2024 21:02

Следите за конфликтами банков при доступе к общей памяти - особенно при двойной буферизации! Иногда может помочь другое выравнивание второго буфера.

einpoklum 18.06.2024 21:10

@einpoklum Есть ли у вас пример макета + шаблон доступа, который может вызвать конфликты с банками? Потому что я не понимаю, как это могло произойти, если вы не сделаете двойной буфер внутренним измерением вместо внешнего.

Homer512 18.06.2024 23:51

@Homer512: Гомер512: Да. Предположим, что чтение из глобальной памяти — это не просто копирование, но каким-то образом включает в себя значение; или что чтение происходит только при выполнении некоторого условия. Тогда у вас будет распределение сообщений shmem по банкам, которое вполне может быть неравномерным; а затем это помогает сместить банки другого буфера, чтобы получить другое, будем надеяться, более близкое к взаимодополняющему распределение банков.

einpoklum 19.06.2024 09:43

или, если вы ниндзя, вы можете крутить Developer.download.nvidia.com/video/gputechconf/gtc/2019/…

Alex Armbruster 19.06.2024 16:12

@einpoklum Я до сих пор не понимаю, как двойная буферизация что-либо меняет в конфликтах банков, если только варпы не получили возможность загружать и сохранять общую память в одном и том же такте. В противном случае, как могло бы быть больше банковских конфликтов, чем в случае с одним буфером?

Homer512 19.06.2024 17:20

Другие вопросы по теме

Похожие вопросы