WedX - журнал о программировании и компьютерных науках

Межблочный барьер на CUDA

Хочу реализовать межблочный барьер на CUDA, но столкнулся с серьезной проблемой.

Я не могу понять, почему это не работает.

#include <iostream>
#include <cstdlib>
#include <ctime>

#define SIZE 10000000
#define BLOCKS 100 

using namespace std;

struct Barrier {
    int *count;

    __device__ void wait() {
        atomicSub(count, 1);
        while(*count)
            ;
    }

    Barrier() {
        int blocks = BLOCKS;
        cudaMalloc((void**) &count, sizeof(int));
        cudaMemcpy(count, &blocks, sizeof(int), cudaMemcpyHostToDevice);
    }

    ~Barrier() {
        cudaFree(count);
    }
};


__global__ void sum(int* vec, int* cache, int *sum, Barrier barrier)
{
    int tid = blockIdx.x;

    int temp = 0;
    while(tid < SIZE) {
        temp += vec[tid];
        tid += gridDim.x;
    }

    cache[blockIdx.x] = temp;

    barrier.wait();

    if(blockIdx.x == 0) {
        for(int i = 0 ; i < BLOCKS; ++i)
            *sum += cache[i];
    }
}

int main()
{
    int* vec_host = (int *) malloc(SIZE * sizeof(int));    
    for(int i = 0; i < SIZE; ++i)
        vec_host[i] = 1;

    int *vec_dev;
    int *sum_dev;
    int *cache;
    int sum_gpu = 0;

    cudaMalloc((void**) &vec_dev, SIZE * sizeof(int));
    cudaMemcpy(vec_dev, vec_host, SIZE * sizeof(int), cudaMemcpyHostToDevice);
    cudaMalloc((void**) &sum_dev, sizeof(int));
    cudaMemcpy(sum_dev, &sum_gpu, sizeof(int), cudaMemcpyHostToDevice);
    cudaMalloc((void**) &cache, BLOCKS * sizeof(int));
    cudaMemset(cache, 0, BLOCKS * sizeof(int));

    Barrier barrier;
    sum<<<BLOCKS, 1>>>(vec_dev, cache, sum_dev, barrier);

    cudaMemcpy(&sum_gpu, sum_dev, sizeof(int), cudaMemcpyDeviceToHost);

    cudaFree(vec_dev);
    cudaFree(sum_dev);
    cudaFree(cache);
    free(vec_host);
    return 0;
}

На самом деле, даже если я перепишу wait() следующим образом

    __device__ void wait() {
        while(*count != 234124)
            ;
    }

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

09.10.2011

  • Я подозреваю, что ваше ядро ​​​​на самом деле дает сбой из-за разыменования неправильного указателя внутри Barrier::wait. Используйте cudaGetLastError для проверки ошибок во время работы ядра. 10.10.2011

Ответы:


1

К сожалению, то, чего вы хотите достичь (межблочная связь/синхронизация), строго невозможно в CUDA. В руководстве по программированию CUDA говорится, что «блоки потоков должны выполняться независимо: должна быть возможность выполнять их в любом порядке, параллельно или последовательно». Причина этого ограничения заключается в том, чтобы обеспечить гибкость планировщика блоков потоков и позволить коду независимо масштабироваться в зависимости от количества ядер. Единственный поддерживаемый метод межблочной синхронизации — это запуск другого ядра: запуски ядра (в том же потоке) — это неявные точки синхронизации.

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

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

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

09.10.2011
  • Как насчет примера threadFenceReduction из последнего CUDA SDK? Они не выполняют там барьерную синхронизацию, но достигают результата, аналогичного тому, который хочет автор темы, используя глобальную блокировку памяти (на самом деле код почти такой же, но вместо спин-блокировки они просто проверяют, является ли текущий блок блоком). последним, чтобы закончить его выполнение). 10.10.2011
  • Возможно, можно реализовать сумму с забором памяти, но вопрос ОП касался межблочной синхронизации. В любом случае уменьшение масштаба примера в ОП лучше реализовать в двухфазном подходе без опоры на атомарность. Еще лучше просто вызвать thrust::reduce. 10.10.2011

  • 2

    Возможна синхронизация блоков. См. этот бумагу.
    Бумага не идет в мельчайших подробностях о том, как это работает, но это зависит от операции __syncthreads(); чтобы создать барьер паузы для текущего блока,... пока другие блоки не достигнут точки синхронизации.

    Один момент, который не отмечен в документе, заключается в том, что синхронизация возможна только в том случае, если количество блоков достаточно мало или количество SM достаточно велико для поставленной задачи. т. е. если у вас есть 4 SM и вы пытаетесь синхронизировать 5 блоков, ядро ​​заблокируется.

    С их подходом я смог распределить длинную последовательную задачу на множество блоков, легко сэкономив 30% времени по сравнению с подходом с одним блоком. т.е. у меня работала блочная синхронизация.

    05.10.2012
  • но тогда есть противоречие с предыдущим ответом? 03.02.2014

  • 3

    Похоже на проблемы с оптимизацией компилятора. Я плохо разбираюсь в PTX-коде, но похоже, что компилятор вообще пропустил while-цикл (даже при компиляции с -O0):

    .loc    3   41  0
    cvt.u64.u32     %rd7, %ctaid.x; // Save blockIdx.x to rd7
    ld.param.u64    %rd8, [__cudaparm__Z3sumPiS_S_7Barrier_cache];
    mov.s32     %r8, %ctaid.x; // Now calculate ouput address
    mul.wide.u32    %rd9, %r8, 4;
    add.u64     %rd10, %rd8, %rd9;
    st.global.s32   [%rd10+0], %r5; // Store result to cache[blockIdx.x]
    .loc    17  128 0
    ld.param.u64    %rd11, [__cudaparm__Z3sumPiS_S_7Barrier_barrier+0]; // Get *count to rd11
    mov.s32     %r9, -1; // put -1 to r9
    atom.global.add.s32     %r10, [%rd11], %r9; // Do AtomicSub, storing the result to r10 (will be unused)
    cvt.u32.u64     %r11, %rd7; // Put blockIdx.x saved in rd7 to r11
    mov.u32     %r12, 0; // Put 0 to r12
    setp.ne.u32     %p3, %r11, %r12; // if(blockIdx.x == 0)
    @%p3 bra    $Lt_0_5122;
    ld.param.u64    %rd12, [__cudaparm__Z3sumPiS_S_7Barrier_sum];
    ld.global.s32   %r13, [%rd12+0];
    mov.s64     %rd13, %rd8;
    mov.s32     %r14, 0;
    

    В случае процессорного кода такое поведение предотвращается объявлением переменной с префиксом volatile. Но даже если мы объявим count как int __device__ count (и соответствующим образом изменим код), добавление спецификатора volatile просто нарушит компиляцию (с ошибками вроде argument of type "volatile int *" is incompatible with parameter of type "void *")

    Я предлагаю посмотреть пример threadFenceReduction из CUDA SDK. Там они делают почти то же самое, что и вы, но блок для окончательного суммирования выбирается во время выполнения, а не предопределен, и while-цикл устранен, потому что спин-блокировка на глобальной переменной должна быть очень< /em> медленно.

    09.10.2011
  • threadFenceReduction отличается одним ключевым моментом: блоки, которые выполняются не последними, будут продолжать выполняться и завершаться. Это означает, что будет выполняться последний блок. В схеме OP он хочет, чтобы все потоки ждали, пока последний блок не достигнет барьера, но это может привести к взаимоблокировке. 10.10.2011
  • @Tom Я не говорю, что они делают точно то же самое, но забор позволяет добиться аналогичных результатов (не с точки зрения потока инструкций, а с точки зрения содержимого выходного массива) 10.10.2011
  • Не сказал, что вы делаете ;-) Это моя точка зрения, ОП пытается создать глобальный барьер, что является плохой идеей (см. ответ Джареда), но, глядя на его код, он может добиться желаемого эффекта так же, как пример threadFenceReduction . @anyoneelse читает это: threadfence не то же самое, что барьер! Ознакомьтесь с Руководством по программированию или поищите в Интернете информацию о заборе памяти для получения дополнительной информации. 10.10.2011
  • Новые материалы

    Как проанализировать работу вашего классификатора?
    Не всегда просто знать, какие показатели использовать С развитием глубокого обучения все больше и больше людей учатся обучать свой первый классификатор. Но как только вы закончите..

    Работа с цепями Маркова, часть 4 (Машинное обучение)
    Нелинейные цепи Маркова с агрегатором и их приложения (arXiv) Автор : Бар Лайт Аннотация: Изучаются свойства подкласса случайных процессов, называемых дискретными нелинейными цепями Маркова..

    Crazy Laravel Livewire упростил мне создание электронной коммерции (панель администратора и API) [Часть 3]
    Как вы сегодня, ребята? В этой части мы создадим CRUD для данных о продукте. Думаю, в этой части я не буду слишком много делиться теорией, но чаще буду делиться своим кодом. Потому что..

    Использование машинного обучения и Python для классификации 1000 сезонов новичков MLB Hitter
    Чему может научиться машина, глядя на сезоны новичков 1000 игроков MLB? Это то, что исследует это приложение. В этом процессе мы будем использовать неконтролируемое обучение, чтобы..

    Учебные заметки: создание моего первого пакета Node.js
    Это мои обучающие заметки, когда я научился создавать свой самый первый пакет Node.js, распространяемый через npm. Оглавление Глоссарий I. Новый пакет 1.1 советы по инициализации..

    Забудьте о Matplotlib: улучшите визуализацию данных с помощью умопомрачительных функций Seaborn!
    Примечание. Эта запись в блоге предполагает базовое знакомство с Python и концепциями анализа данных. Привет, энтузиасты данных! Добро пожаловать в мой блог, где я расскажу о невероятных..

    ИИ в аэрокосмической отрасли
    Каждый полет – это шаг вперед к великой мечте. Чтобы это происходило в их собственном темпе, необходима команда астронавтов для погони за космосом и команда технического обслуживания..


    © 2024 wedx.ru, WedX - журнал о программировании и компьютерных науках
    Для любых предложений по сайту: [email protected]