Эффективность размеров блоков и сеток CUDA - PullRequest
19 голосов
/ 28 апреля 2011

Каков рекомендуемый способ работы с наборами данных динамического размера в cuda?

Это случай «установки размеров блока и сетки на основе поставленной задачи» или целесообразно назначить размеры блока в виде коэффициентов 2 и иметь некоторую логику в ядре, чтобы справиться с переполнением?

Я могу видеть, как это, вероятно, имеет большое значение для размеров блока, но насколько это имеет значение для размеров сетки? Насколько я понимаю, фактические аппаратные ограничения прекращаются на уровне блоков (т. Е. Блоки, назначенные SM, имеют заданное количество SP и могут обрабатывать определенный размер деформации).

Я просмотрел «Программирование массово параллельных процессоров» Кирка, но в действительности это не касается этой области.

Ответы [ 4 ]

14 голосов
/ 28 апреля 2011

Это обычно случай установки размера блока для оптимальной производительности и размера сетки в соответствии с общим объемом работы.У большинства ядер есть количество точек перекоса на Mp, где они работают лучше всего, и вы должны сделать несколько тестов / профилировок, чтобы увидеть, где это.Вам, вероятно, по-прежнему нужна логика переполнения в ядре, потому что размеры проблем редко бывают кратными размерам блоков.

РЕДАКТИРОВАТЬ: привести конкретный пример того, как это можно сделать для простого ядра (в данном случаепользовательская операция типа dscal BLAS уровня 1, выполняемая как часть факторизации Холецкого упакованных матриц симметричных полос):

// Fused square root and dscal operation
__global__ 
void cdivkernel(const int n, double *a)
{
    __shared__ double oneondiagv;

    int imin = threadIdx.x + blockDim.x * blockIdx.x;
    int istride = blockDim.x * gridDim.x;

    if (threadIdx.x == 0) {
        oneondiagv = rsqrt( a[0] );
    }
    __syncthreads();

    for(int i=imin; i<n; i+=istride) {
        a[i] *= oneondiagv;
    }
}

Для запуска этого ядра параметры выполнения рассчитываются следующим образом:

  1. Мы допускаем до 4 деформаций на блок (т. Е. 128 потоков).Обычно вы исправляете это в оптимальном количестве, но в этом случае ядро ​​часто вызывается для очень маленьких векторов, поэтому иметь переменный размер блока имеет смысл.
  2. Затем мы вычисляем количество блоков в соответствии с общимобъем работы, всего до 112 блоков, что эквивалентно 8 блокам на MP на 14 MP Fermi Telsa.Ядро будет выполнять итерацию, если объем работы превышает размер сетки.

Результирующая функция-обертка, содержащая вычисления параметров выполнения и запуск ядра, выглядит следующим образом:

// Fused the diagonal element root and dscal operation into
// a single "cdiv" operation
void fusedDscal(const int n, double *a)
{
    // The semibandwidth (column length) determines
    // how many warps are required per column of the 
    // matrix.
    const int warpSize = 32;
    const int maxGridSize = 112; // this is 8 blocks per MP for a Telsa C2050

    int warpCount = (n / warpSize) + (((n % warpSize) == 0) ? 0 : 1);
    int warpPerBlock = max(1, min(4, warpCount));

    // For the cdiv kernel, the block size is allowed to grow to
    // four warps per block, and the block count becomes the warp count over four
    // or the GPU "fill" whichever is smaller
    int threadCount = warpSize * warpPerBlock;
    int blockCount = min( maxGridSize, max(1, warpCount/warpPerBlock) );
    dim3 BlockDim = dim3(threadCount, 1, 1);
    dim3 GridDim  = dim3(blockCount, 1, 1);

    cdivkernel<<< GridDim,BlockDim >>>(n,a);
    errchk( cudaPeekAtLastError() );
}

Возможно, это даетнекоторые советы о том, как разработать «универсальную» схему для установки параметров выполнения в зависимости от размера входных данных.

3 голосов
/ 28 апреля 2011

Хорошо, я думаю, что здесь мы имеем дело с двумя вопросами.

1) Хороший способ назначить размеры блока (то есть количество потоков) Обычно это зависит от типа данных, с которыми вы имеете дело. Вы имеете дело с векторами? Вы имеете дело с матрицами? Предложенный способ состоит в том, чтобы количество потоков было кратно 32. Поэтому при работе с векторами запуск 256 × 1, 512 × 1 блоков может быть подходящим И аналогично при работе с матрицами, 32 х 8, 32 х 16.

2) Хороший способ назначить размеры сетки (то есть количество блоков) Здесь становится немного сложно. Просто запустить 10 000 блоков, потому что мы обычно не лучший способ сделать что-то. Переключение блоков в и из аппаратных средств является дорогостоящим. Необходимо учитывать две вещи: общую память, используемую на блок, и общее количество доступных SP, и выбрать оптимальное количество.

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

1 голос
/ 18 июня 2016

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

Этот сайт имеет некоторые отличные эвристические параметры.Некоторые основные моменты:

Выбор блоков на сетку

  • Количество блоков на сетке должно быть> = количество мультипроцессоров.
  • Чем больше используется__syncthreads() в ваших ядрах, чем больше блоков (чтобы один блок мог работать, пока другой ожидает синхронизации)

Выбор потоков на блок

  • Потоки, кратные размеру деформации (то есть обычно 32)

  • Как правило, удобно выбирать количество потоков, чтобы максимальное число потоков в блоке (на основе аппаратного обеспечения) было кратнымколичество потоков.Например, с максимальным числом потоков 768, использование 256 потоков на блок будет иметь тенденцию быть лучше, чем 512, потому что несколько потоков могут одновременно выполняться в одном блоке.

1 голос
/ 28 апреля 2011

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

...