Кой начин да поръчате споделен 2D/3D масив за паралелна редукция над 1 измерение в CUDA/OpenCL?

Обща цел

Трябва да направя няколко съкращения на двустранен граф, представен от два плътни масива за върхове и плътен масив, указващ дали ребро присъства между двете. Да кажем, че два масива са a0[] и a1[] и всички ръбове вървят като e[i0][i1] (т.е. от елементи в a0 до елементи в a1).

Има ~100+100 върха и ~100*100 ръба, така че всяка нишка отговаря за един ръб.

Задача 1: максимално намаление

За всеки връх в a0 искам да намеря максимума от всички върхове (в a1), свързани с него, и след това същото в обратен ред: след като присвоих резултата на масив b0, за всеки връх в a1, искам да намеря максимума b0[i0] на свързаните върхове.

За да направя това, аз:

1) зареждане в споделена памет

    #define DC_NUM_FROM_SHARED 16
    #define DC_NUM_TO_SHARED 16
    __global__ void max_reduce_down(
            Value* value1
        , Value* max_value_in_connected
        , int r0_size, int r1_size
        , bool** connected
        )
    {
        int id_from;
        id_from = blockIdx.x * blockDim.x + threadIdx.x;
        id_to   = blockIdx.y * blockDim.y + threadIdx.y;
        bool within_bounds = (id_from < r0_size) && (id_to < r1_size);

        //load into shared memory
        __shared__ Value value[DC_NUM_TO_SHARED][DC_NUM_FROM_SHARED]; //FROM is the inner (consecutive) dimension
        if(within_bounds)
            value[threadIdx.y][threadIdx.x] = connected[id_to][id_from]? value1[id_to] : 0;
        else
            value[threadIdx.y][threadIdx.x] = 0;
        __syncthreads();

        if(!within_bounds)
            return;

2) намалявам

for(int stride = DC_NUM_TO_SHARED/2; threadIdx.y < stride; stride >>= 1)
{
    value[threadIdx.y][threadIdx.x] = max(value[threadIdx.y][threadIdx.x], dc[threadIdx.y + stride][threadIdx.x]);
    __syncthreads();
}

3) пишете обратно

max_value_connected[id_from] = value[0][threadIdx.x];

Задача 2: най-добрият k

Подобен проблем, но намаляването е само за върхове в a0, трябва да намеря k най-добрите кандидати са избрани от свързани в a1 (k е ~5).

1) Инициализирам споделения масив с нула елементи с изключение на 1-во място

int id_from, id_to;
id_from = blockIdx.x * blockDim.x + threadIdx.x;
id_to   = blockIdx.y * blockDim.y + threadIdx.y;

__shared Value* values[MAX_CHAMPS * CHAMPS_NUM_FROM_SHARED * CHAMPS_NUM_TO_SHARED]; //champion overlaps
__shared int* champs[MAX_CHAMPS * CHAMPS_NUM_FROM_SHARED * CHAMPS_NUM_TO_SHARED]; // overlap champions


bool within_bounds = (id_from < r0_size) && (id_to < r1_size);
int i = threadIdx.y * CHAMPS_NUM_FROM_SHARED + threadIdx.x;
if(within_bounds)
{
    values[i] = connected[id_to][id_from] * values1[id_to];
    champs[i] = connected[id_to][id_from] ? id_to : -1;
}
else
{
    values[i] = 0;
    champs[i] = -1;
}

for(int place = 1; place < CHAMP_COUNT; place++)
{
    i = (place * CHAMPS_NUM_TO_SHARED + threadIdx.y) * CHAMPS_NUM_FROM_SHARED + threadIdx.x;
    values[i] = 0;
    champs[i] = -1;
}
if(! within_bounds)
    return;
__syncthreads();

2) намалете го

for(int stride = CHAMPS_NUM_TO_SHARED/2; threadIdx.y < stride; stride >>= 1)
{
    merge_2_champs(values, champs, CHAMP_COUNT, id_from, id_to, id_to + stride);
    __syncthreads();
}

3) запишете резултатите обратно

for(int place = 0; place < LOCAL_DESIRED_ACTIVITY; place++)
    champs0[place][id_from] = champs[place * CHAMPS_NUM_TO_SHARED * CHAMPS_NUM_FROM_SHARED + threadIdx.x];

Проблем

Как да подредя (транспонирам) елементите в споделения масив, така че достъпът до паметта да използва по-добре кеша? Има ли значение в този момент или има много повече, което мога да спечеля от други оптимизации? Ще бъде ли по-добре да транспонирам матрицата на ръбовете, ако трябва да оптимизирам за Задача 2? (доколкото разбрах в 1 задача има симетрия, така че няма значение).

P.S.

Забавих разгръщането на цикли и извършването на първата редуцираща итерация по време на зареждането, тъй като смятах, че е твърде сложно да се направи, преди да проуча по-прости начини.

За Задача 2 би било хубаво да не се зареждат нулеви елементи, тъй като масивът никога няма да има нужда да расте и да започне да се свива едва след като са направени log k стъпки. Това би го направило k пъти по-компактен в споделената памет! Но се страхувам от получената математика на индекса.

Синтаксис и коректност

Необичайните типове са просто дефинирани ints/chars/etc - AFAIK, в графичните процесори има смисъл да ги компактифицирате колкото е възможно повече. Все още не съм стартирал кода, няма нужда да проверявам за грешки при индексирането.

Освен това използвам CUDA, но се интересувам и от гледна точка на OpenCL, тъй като смятам, че най-доброто решение трябва да е същото и така или иначе ще използвам OpenCL в бъдеще.


person Leo    schedule 11.12.2014    source източник


Отговори (2)


Добре, мисля, че разбрах това.

Двете алтернативи, които обмислям, са редукциите да работят върху измерението y и независимо от измерението x или обратното (x измерението е съседното). Във всеки случай планировчикът може да сглобява нишки в изкривявания по дължината на x измерението, така че известна съгласуваност е гарантирана. Въпреки това, ако кохерентността се простира отвъд деформацията, би било страхотно. Освен това, поради 2D/3D характера на споделените масиви, ще трябва да се ограничат размерите до 16 или дори 8.

За да осигури коалесценция в рамките на деформация, планировчикът трябва да сглоби деформации по дължината на x измерението.

Ако се намали над измерението x, след всяка итерация броят на активните нишки в една деформация ще намалее наполовина. Въпреки това, ако се намали над измерението y, броят на активните деформации ще намалее наполовина.

И така, трябва да намаля над y.

Освен ако транспонирането (зареждането) не е най-бавното, което е необичаен случай.

person Leo    schedule 12.12.2014

Обединените буферни четения наистина имат значение; ядрата могат да бъдат 32 пъти по-бавни, ако не ги правите. Може да си струва да направите пропуск за пренареждане, ако това означава да можете да ги направите (разбира се, пропускът за пренареждане също трябва да бъде обединен, но често можете да използвате споделена локална памет, за да направите това).

person Dithermaster    schedule 11.12.2014