Проблема барьера сжатия параллельного буфера OpenCL

В качестве школьного проекта мы работаем над параллельным трассировщиком лучей с OpenCL. Это наш первый проект с использованием OpenCL, поэтому у нас могут быть некоторые непонятки по этому поводу.

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

Итак, у нас есть буфер uint *prefix_sum, который содержит индексы, по которым каждый s_ray_state должен быть перемещен в буфер s_ray_state *ray_states, чтобы уменьшить количество лучей, отправляемых в ядро ​​затенения, и следующие итерации ядер трассировки/затенения.

К сожалению, приведенное ниже ядро ​​ray_sort, похоже, не работает правильно, мы проверили входные данные prefix_sum, которые на 100% верны, то же самое для буфера ray_states, но на выходе мы получаем нежелательные данные.

Мы запускаем одну рабочую группу (глобальный рабочий размер = локальный рабочий размер), лучи всегда перемещаются в буфере на меньший индекс, чем их исходный. Мы поставили барьеры и используем буфер s_ray_state *tmp, чтобы предотвратить параллельные выполнения для записи данных друг друга, но, похоже, это не работает, даже при удалении барьеров мы получим тот же результат.

Мы оба работали над этим 4 дня и уже обратились за помощью к другим ученикам, но, похоже, никто не может понять, что не так. Возможно, мы недостаточно понимаем барьеры/заборы памяти, чтобы быть уверенными, что это действительно может работать.

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

Код ниже должен работать? С нашим пониманием OpenCL он должен работать, и мы провели много исследований, но так и не получили четкого ответа.

kernel void ray_sort(
    global read_only uint *prefix_sum,
    global read_write struct s_ray_state *ray_states,
    global read_only uint *ray_states_size,
    local read_write struct s_ray_state *tmp
)
{
    int l_size = get_local_size(0);
    int l_id = get_local_id(0);
    int group_id = -1;
    int group_nb = *ray_states_size / l_size;
    int state_id;

    while (++group_id < group_nb)
    {
        state_id = group_id * l_size + l_id;
        tmp[l_id] = ray_states[state_id];
        barrier(CLK_LOCAL_MEM_FENCE);
        if (did_hit(tmp[l_id]))
            ray_states[prefix_sum[state_id]] = tmp[l_id];
        barrier(CLK_GLOBAL_MEM_FENCE);
    }
}

ray_states длина ray_states_size

prefix_sum содержит индексы, по которым каждый элемент ray_states должен быть перемещен в

tmp — локальный буфер размером local_work_size

local_work_size = global_work_size

did_hit() возвращает 1, если луч попадает на объект, иначе 0

Мы ожидаем, что элементы ray_states будут перемещены в индексы, содержащиеся в prefix_sum.

Пример: каждый ray_states[id] перемещается в индекс prefix_sum[id] в ray_states.

prefix_sum: 0 | 0 | 1 | 1 | 2 | 3 | 3 | 3 | 4

did_hit(ray_states[id]): 0 | 1 | 0 | 1 | 1 | 0 | 0 | 1 | 0

did_hit(output[id]): 1 | 1 | 1 | 1 | X | X | X | X | X

Xs может быть чем угодно


person elXor    schedule 21.03.2018    source источник
comment
Привет, elXor, я чувствую, что вам нужно отредактировать вопрос, чтобы уточнить, что мы получаем нежелательные данные на выходе. -- т. е. мы ожидаем (чего-то конкретного) и получаем (чего-то конкретного) -- чтобы люди могли сфокусироваться на проблеме.   -  person Leon Bambrick    schedule 22.03.2018
comment
@LeonBambrick Я чувствую, что результат, который мы получаем, не имеет значения для вопроса, мы просто ожидаем, что элементы из ray_states будут реорганизованы в индексы, содержащиеся в prefix_sum. Мы просто получаем что-то, что не реорганизовано так, как ожидается (элементы не перемещены в правильные inidices).   -  person elXor    schedule 22.03.2018


Ответы (1)


Я мог бы быть совсем не в себе, но мне кажется, что с did_hit(ray_states[state_id]) вы читаете тот же самый кусок глобальной памяти, который вы поместили в буфер локальной памяти tmp всего двумя строками выше. Это не было бы проблемой, за исключением того, что вы используете этот буфер как для ввода, так и для вывода.

Как я это вижу, что на самом деле происходит на оборудовании, так это:

    tmp[l_id] = ray_states[state_id];
    tmp[l_id] = ray_states[state_id];
    tmp[l_id] = ray_states[state_id];
    tmp[l_id] = ray_states[state_id];
    tmp[l_id] = ray_states[state_id];

       ... local-work-size times

    barrier(CLK_LOCAL_MEM_FENCE);

    if (did_hit(ray_states[state_id]))
        ray_states[prefix_sum[state_id]] = tmp[l_id];
    if (did_hit(ray_states[state_id]))
        ray_states[prefix_sum[state_id]] = tmp[l_id];
    if (did_hit(ray_states[state_id]))
        ray_states[prefix_sum[state_id]] = tmp[l_id];
    if (did_hit(ray_states[state_id]))
        ray_states[prefix_sum[state_id]] = tmp[l_id];

      ... again local-work-size times

Учитывая, что порядок параллельного выполнения WItem не определен (аппаратное обеспечение может выбрать любой порядок, который он хочет), это приведет к случайным результатам. Можете ли вы попробовать это вместо этого:

    if (did_hit(tmp[l_id]))
        ray_states[prefix_sum[state_id]] = tmp[l_id];

Кстати, если ray_states_size — простое целое число, вы можете передать его напрямую, указав аргумент «uint ray_states_size». Не надо там возиться с буферами.

РЕДАКТИРОВАТЬ 1: мое предложение будет работать только в том случае, если prefix_sum[state_id] не имеет дубликатов в каждом идентификаторе локального рабочего размера, иначе все равно будет гонка данных. Так, например. если для state_id-s 1 и 3 массив prefix_sum[state_id] имеет 0, а ваш локальный размер WG >= 4, будет гонка данных.

Кроме того, есть ли действительно веская причина, по которой вы должны использовать один и тот же буфер для ввода и вывода? мне кажется, было бы намного проще, если бы у вас были отдельные входные/выходные буферы.

EDIT2: я только что заметил, что вы сказали: «лучи всегда перемещаются в буфере с меньшим индексом, чем их исходный» (извините, я пропустил это). Это хорошо, но недостаточно — всегда ли они перемещаются в меньший индекс, чем индекс любого другого луча в той же локальной рабочей группе? если да, хорошо, но есть еще одна гонка данных, о которой я упоминал.

person mogu    schedule 22.03.2018
comment
Я исправил проблему с did_hit(), о которой вы упомянули (как в реализации, так и в моем вопросе), к сожалению, по-прежнему ничего не изменил. - person elXor; 22.03.2018
comment
Я не знал, что вы можете передавать ядро ​​​​по значению, даже в некоторых примерах, которые я видел, они передавались по указателю, посмотрю на это, спасибо. prefix_sum имеет дубликаты, но did_hit(state_id) может вернуть только 1 для одного из них, ни больше, ни меньше. Я не могу использовать 2 разных буфера, так как это займет слишком много памяти на графическом процессоре, который у нас есть в школе. Я пробовал с 2 буферами, но это тоже не сработало.. - person elXor; 22.03.2018
comment
Лучи всегда перемещаются на меньший индекс, чем их исходный, или не перемещаются вообще. Они могут быть перемещены в тот же индекс, что и другие лучи в той же локальной рабочей группе, но разве tmp local не должен заботиться об этом? - person elXor; 22.03.2018
comment
локальный tmp даст вам действительную удобочитаемую копию, поэтому последующая запись в ray_states безопасна. Но вопрос в том, что в итоге написано. Если условие did_hit(tmp[l_id]) оценивается как истинное для любых двух разных WItems локальной рабочей группы, может ли prefix_sum[state_id] для этих WItems быть одинаковым? Если да, у вас есть проблема: в конечном итоге сохраняется то, что пишет последний из этих двух WItem, но вы не знаете, какой из них выполняется последним. - person mogu; 23.03.2018