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, очень важно понять иерархию памяти на устройстве и написать хорошие ядра.
Надеюсь, это поможет!!