CUDA: могут ли устройство и код работать параллельно до истечения срока жизни, пинг-понг данных?

Я программирую матричное векторное умножение с помощью Cuda. Матрица находится в блоках, поэтому каждый поток может хранить один блок матрицы в общей памяти (как локальные переменные потока). Я также отправляю вектор в качестве аргумента, умножение выполняется в блоках, без проблем. Но проблема зависит от времени. Я должен вычислить матричное векторное произведение с той же самой матрицей, но разным вектором для каждого временного среза. Поэтому было бы напрасно вызывать ядро ​​для каждой оценки продукта, вызывая копирование блоков матрицы из глобальной памяти графической карты в общую память. Я думал, что смогу сделать один вызов ядра и поддерживать его всегда, не теряя локальные переменные потока. Вектор может быть выделен как отображенная память на хосте, скажем V. Теперь ядро ​​умножается, сохраняет его в еще одной отображенной области, скажем, P, устанавливает флаг (еще одно целочисленное отображение памяти). Хост опрашивает флаг, когда флаг установлен, он отображает произведение из P, загружает новый вектор в V и сбрасывает флаг. Ядро также опрашивает флаг, видит сброс, умножает, сохраняет продукт в P и устанавливает флаг.

Я написал небольшую дополнительную программу, чтобы увидеть, работает ли такое межпроцессное взаимодействие. Проблема заключается в том, что когда ядро ​​записывает что-либо в отображенную память, память на хосте не обновляется, пока хост не вызовет cudaThreadSynchronize(); Но если синхронизация потоков не может произойти, пока ядро ​​опрашивает флаг. Есть ли выход из этой ситуации?

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

Любая помощь приветствуется.

Спасибо,

Elan.

1 ответ

Я не уверен, полностью ли я понимаю вашу проблему, но да, у вас может быть несколько активных одновременно работающих ядер.

Из Руководства по вычислениям CUDA v. 3.2 pg. 38: http://developer.download.nvidia.com/compute/cuda/3_2/toolkit/docs/CUDA_C_Programming_Guide.pdf

Некоторые устройства с вычислительной способностью 2.x могут выполнять несколько ядер одновременно. Приложения могут запрашивать эту возможность, вызывая cudaGetDeviceProperties() и проверяя свойство concurrentKernels.

Мой вопрос к вам: почему вы хотите использовать несколько ядер?

Я думаю, что вам нужно перечитать руководство CUDA (см. Выше). Похоже, что вы хотите сделать, это одно ядро ​​с несколькими блоками / потоками, каждый из которых имеет свой кусок общей памяти. Далее вам нужно выяснить, насколько большой элемент использовать для каждого блока. Помните, что вам нужны два "куска" памяти и продукт (3 квадратные 2D матрицы одинакового размера). Для этого сделайте запрос к вашему устройству и получите вычислительную мощность и прочитайте приведенное выше руководство, чтобы определить итоговый объем разделяемой памяти.

Затем используйте код как:

if (ComputeCapability >= 2.0)
   {
      NumberOfSharedValues = (32768/GetSize(Dummy));
      FullUseageThreadsPerBlock = 512;
      MaxBlocksPerSM = 3;
   }
   else
   {
      //Tot. Shared mem / Size per var / Number of Arrays
      NumberOfSharedValues = (16384/GetSize(Dummy)/3);

      //CC1.2 && CC1.3
      if (ComputeCapability >= 1.2)
      {
         FullUseageThreadsPerBlock = 512;
         MaxBlocksPerSM = 2;
      }  
      else  //CC1.0 && CC1.1
      {
         FullUseageThreadsPerBlock = 256;
         MaxBlocksPerSM = 3;
      }   
   }   

куда Dummy имеет шаблонный тип, и я написал функции для возврата размеров общих шаблонных типов (например, int, float, double) в CUDA (например, в C и int обычно 16 бит, в CUDA его 32-битный).

Скопируйте массивы для умножения в глобальную память на устройстве.

Затем возьмите квадратный корень из вашего NumberOfSharedValues переменную, и напишите ядро, которое объявляет три массива в разделяемой памяти этой длины (два "куска" и произведение).

Попросите ядро ​​скопировать фрагменты для умножения в общую память, выполнить умножение, а затем записать полученный "продуктовый" блок обратно в глобальную память.

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

Вуаля, я думаю, это должно помочь тебе.

Имейте в виду, что вы будете назначать общую память, описанную выше - это объем, доступный для одного мультипроцессора (и так как каждый из ваших блоков выделяет столько памяти, блоков = # мультипроцессоров), так что вы можете определить свой общий размер элементов. Быстрая обработка будет зависеть от того, сколько у вас мультипроцессоров, опять же количество, которое можно запросить.

Также имейте в виду, что вы можете использовать cuMemGetInfo чтобы получить объем свободной памяти, чтобы убедиться, что вы можете вписать массив who в глобальную память.

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

Надеюсь, это поможет!!

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