Реализация OpenCL FFT - бессмысленные выходные данные - предположительно правильный алгоритм

У меня возникли проблемы с модулем взаимной корреляции сигналов БПФ, который я создаю (использует теорему о круговой свертке и т. д. и т. д.). Я хотел бы просто подтвердить, что следующая схема гарантирует, что определенные уровни рекурсии вычисления бабочки БПФ будут завершены до начала следующего уровня и что буферы, содержащие данные, будут полностью записаны/исполнены. Таким образом, круговая корреляция/свертка включает БПФ, векторный внутренний продукт, а затем ОБПФ.

Из-за этой схемы у меня нет ядер, которые упорядочивают данные в обратном порядке битов. Ядро прямого БПФ производит БПФ с обратным порядком битов, и после внутренних произведений ОБПФ просто использует этот результат для вычисления решения в естественном порядке.

Я должен упомянуть, что у меня несколько графических процессоров.

В любом случае, вот псевдокодовое представление того, что происходит для каждого БПФ/ОБПФ (алгоритмы доступа/операции эквивалентны, за исключением сопряженных коэффициентов поворота, ядро ​​​​нормализации появится позже:

    for numDevices:
        data -> buffers
        buffers -> kernel arguments

    for fftRecursionLevels:
        for numDevices:
            recursionLevel -> kernel arguments
            deviceCommandQueue -> enqueueNDRangeKernel
            deviceCommandQueue ->  flush()

        for numDevices:
            deviceCommandQueue -> finish()

(EDIT: метод Radix-2 DIT, если это не ясно, извините.)

Могу ли я уйти с этим? Насколько я понимаю, finish() является блокирующей функцией, и этот последний цикл for не будет завершен до тех пор, пока каждое ядро ​​​​не завершит вычисления в своем глобальном диапазоне (здесь fftSize / 2, см. любую литературу по операциям бабочки Radix-2), и , для бонусных баллов некоторые ядра уже выполняются из-за flush(), пока я ставлю в очередь оставшиеся ядра.

В целом, я получаю некоторые странные/мусорные результаты, используя openCL/С++ для этого конкретного программного обеспечения. Я реализовал полный конвейер данных в python (алгоритм «топологически эквивалентен», если хотите, очевидно, что нет хоста --> буфера/инструкций устройства или операций на стороне устройства с/ метод python) и смоделировал, как должно работать ядро, и оно дает ИДЕНТИЧНЫЕ результаты, когда я использую модули scipy.fftpack и просто оперирую векторами данных сигнала.

Я думаю, некоторые фотографии не помешали бы. Именно это и происходит в обеих программах.

1) сгенерировать гауссовский вектор 2) нулевой гауссовский вектор до следующей следующей наивысшей степени длины 2 3) прямое БПФ, что приводит к результату естественного порядка (в w ) 4) график

Вот симуляция моего ядра на Python по сравнению с использованием только scipy.fftpack.fft(vector):

http://i.imgur.com/pGcYTrL.png

Они одинаковые. Теперь сравните это с любым из:

http://i.imgur.com/pbiYGpR.png

(Игнорируйте индексы по осям x, все они являются результатами БПФ естественного порядка)

Все они имеют одинаковый тип начальных данных (в данном случае гуассовский от 0 до N, с центром в N/2 и дополненный нулями до 2N). И все они должны выглядеть как зелено-синяя линия на первом изображении, но это не так. Мои глаза остекленели от того, как долго я смотрел на код хоста/устройства для этой второй программы, и я не вижу ни опечаток, ни неправильных алгоритмов. Я очень подозреваю, что что-то происходит на стороне устройства, о чем я не знаю, поэтому я пишу здесь. Ясно, что алгоритм ВЫГЛЯДИТ работать правильно (общая форма красного/красного примерно такая же, как у синего/зеленого, независимо от начальных данных. Я запускал алгоритм на разных начальных наборах, и он постоянно выглядит как синий/зеленый, но с этим бессмысленным шумом/ошибкой), но что-то не так.

Поэтому я обращаюсь к интернетам. Заранее спасибо.

РЕДАКТИРОВАТЬ: один из постеров ниже предположил, что трудно комментировать, не видя хотя бы кода на стороне устройства, так как есть вопросы о ограждении памяти, поэтому я разместил код ядра ниже.

//fftCorr.cl
//
//OpenCL Kernels/Methods for FFT Cross Correlation of Signals
//
//Copyright (C) 2013  Steve Novakov
//   
//This file is part of OCLSIGPACK.
//
//OCLSIGPACK is free software: you can redistribute it and/or modify
//it under the terms of the GNU General Public License as published by
//the Free Software Foundation, either version 3 of the License, or
//(at your option) any later version.
//
//OCLSIGPACK is distributed in the hope that it will be useful,
//but WITHOUT ANY WARRANTY; without even the implied warranty of
//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//GNU General Public License for more details.
//
//You should have received a copy of the GNU General Public License
//along with OCLSIGPACK.  If not, see <http://www.gnu.org/licenses/>.
//


#define PIE 3.14159265359f


void DFT2(float2 * a,float2 * b, float2 *w){
    float2 tmp; 
    float2 bmul = ( (*w).x*((*b).x) - (*w).y*((*b).y),  (*w).x*((*b).y) + (*w).y*((*b).x) );
    tmp = (*a) - bmul; 
    (*a) += bmul; 
    (*b) = tmp; 
}
//
//
// Spin Factor Calc
// 
// Computes spin/twiddle factor for particular bit reversed index.
//
//

float2 spinFact(unsigned int N, unsigned int k)
{
    float phi = -2.0 * PIE * (float) k / (float)  N;
    // \bar{w}^offset_(groupDist)   

    float spinRe, spinIm;
    spinIm = sincos( phi, &spinRe); 

    return (float2) (spinRe, spinIm);   
}


float2 spinFactR(unsigned int N, unsigned int k)
{
    float phi = 2.0 * PIE * (float) k / (float)  N;
    // w^offset_(groupDist) 

    float spinRe, spinIm;
    spinIm = sincos( phi, &spinRe); 

    return (float2) (spinRe, spinIm);   
}


//
// Bit-Reversed Index Reversal, (that sounds confusing)
//
unsigned int BRIR( unsigned int index, unsigned int fftDepth)
{
    unsigned int rev = index;   

    rev = (((rev & 0xaaaaaaaa) >> 1 ) | ((rev & 0x55555555) << 1 ));
    rev = (((rev & 0xcccccccc) >> 2 ) | ((rev & 0x33333333) << 2 ));
    rev = (((rev & 0xf0f0f0f0) >> 4 ) | ((rev & 0x0f0f0f0f) << 4 ));
    rev = (((rev & 0xff00ff00) >> 8 ) | ((rev & 0x00ff00ff) << 8 ));
    rev = (((rev & 0xffff0000) >> 16) | ((rev & 0x0000ffff) << 16));

    rev >>= (32-fftDepth);  

    return rev;
}


//
//
// Index Bit Reversal Kernel, if Necessary/for Testing.
//
// Maybe I should figure out an in-place swap algorithm later.
//
//
__kernel void bitRevKernel(     __global float2 * fftSetX,
                                __global float2 * fftSetY,
                                __global float2 * fftRevX,
                                __global float2 * fftRevY, 
                                unsigned int fftDepth
                            )
{
    unsigned int glID = get_global_id(0);

    unsigned int revID = BRIR(glID, fftDepth);

    fftRevX[revID] = fftSetX[glID];
    fftRevY[revID] = fftSetY[glID];

}


//
//
// FFT Radix-2 Butterfly Operation Kernel
//
// This is an IN-PLACE algorithm. It calculates both bit-reversed indeces and spin factors in the same thread and
// updates the original set of data with the "butterfly" results.
// 
// recursionLevel is the level of recursion of the butterfly operation 
// # of threads is half the vector size N/2, (glID is between 0 and this value, non inclusive)
//
// Assumes natural order data input. Produces bit-reversed order FFT output.
//
//
__kernel void fftForwKernel(    __global float2 * fftSetX,
                                __global float2 * fftSetY,
                                unsigned int recursionLevel,
                                unsigned int totalDepth
                            )
{

    unsigned int glID = get_global_id(0);

    unsigned int gapSize = 1 << (recursionLevel - 1);
    unsigned int groupSize = 1 << recursionLevel;
    unsigned int base = (glID >> (recursionLevel - 1)) * groupSize;
    unsigned int offset = glID & (gapSize - 1 );

    unsigned int bitRevIdA = (unsigned int) base + offset;
    unsigned int bitRevIdB = (unsigned int) bitRevIdA + gapSize;

    unsigned int actualIdA = BRIR(bitRevIdA, totalDepth);
    unsigned int actualIdB = BRIR(bitRevIdB, totalDepth);


    float2 tempXA = fftSetX[actualIdA];
    float2 tempXB = fftSetX[actualIdB];

    float2 tempYA = fftSetY[actualIdA];
    float2 tempYB = fftSetY[actualIdB];                 

    float2 spinF = spinFact(groupSize, offset);

    // size 2 DFT   
    DFT2(&tempXA, &tempXB, &spinF); 
    DFT2(&tempYA, &tempYB, &spinF);


    fftSetX[actualIdA] = tempXA;
    fftSetX[actualIdB] = tempXB;

    fftSetY[actualIdA] = tempYA;
    fftSetY[actualIdB] = tempYB;    

}

По данным, представленным на картинке. Я запускаю «fftForwKernel», как описано в начале поста, а затем запускаю «bitRevKernel».


person Steve Novakov    schedule 16.06.2013    source источник


Ответы (1)


Таким образом, без кода, сообщающего что-либо, и исходя из предположения, что коды действительно «одинаковы», я склонен сказать, что при условии, что используемый алгоритм действительно кажется одинаковым для Python и OpenCL это, вероятно, проблема с синхронизацией. Если не проблема глобальной синхронизации (правильное распределение работы между вызовами ядра), то проблема с отсутствием ограждений глобальной памяти или даже ограждений локальной памяти для каждой локальной группы при вызове ядра.

Как вы организуете звонки? Предоставленный псевдокод кажется неоднозначным в отношении того, как именно вы разделяете рекурсивные БПФ. Я предполагаю, что вы делаете «правильные вещи» для... Ну, я даже не уверен, делаете ли вы DIT или DIF или любой другой из огромного разнообразия потоков данных, доступных для алгоритмов FFT. Насколько я знаю, может быть, вы делаете бабочки без должной памяти их, или вы можете так строго синхронизировать свои шаги БПФ, что бабочки являются частью совершенно другого вызова ядра, чем рекурсивные шаги БПФ, и мои предложения полностью пусты. и недействительным.

(Я бы превратил это в комментарий, но мне не хватает репутации, чтобы сделать это, поэтому приношу свои извинения за то, что это было опубликовано как «ответ»)

person user    schedule 16.06.2013
comment
Нет проблем, так что это Radix-2 DIT. Как уже говорилось, ядро ​​работает с глобальной длиной N/2. Каждый отдельный рабочий элемент (размера 1) на самом деле является n-м 2-точечным ДПФ на определенном уровне рекурсии. Дело в том, что на каждом этапе осуществляется доступ только к 2 элементам. Я бы съел свою шляпу, если бы существовала семантическая разница между кодом OCL и Python. Я буквально скопировал свое ядро ​​в новый файл и изменил синтаксис на Pythonese. Могу ли я спросить, зачем мне вообще нужно ограждение памяти, если доступ осуществляется только к 2 элементам на поток? Если это действительно потенциальная проблема, то я опубликую свое ядро. - person Steve Novakov; 17.06.2013
comment
... Тем не менее, я хотел бы упомянуть, что большое количество ядер DIT, которые я видел в Интернете, будь то от отдельных лиц (например: bealto.com/gpu-fft2_opencl-1.html) или руководства от NVIDIA, самих AMD, не имеют ограждения памяти. - person Steve Novakov; 17.06.2013
comment
Меня также смущает ваш комментарий о строгой синхронизации ядер. Если действительно метод синхронизации ядра, который я опубликовал, верен, то операции выполняются в одном и том же буфере памяти на стороне устройства. У меня есть другие функции с несколькими ядрами openCL (не связанные с БПФ), работающие в этой конкретной части программного обеспечения, созданные аналогичным образом, и очевидно, что это так (они работают правильно) - person Steve Novakov; 17.06.2013
comment
1) то, что я имел в виду под их строгой синхронизацией, заключалось в том, что вы явно разделили каждую стадию на отдельные вызовы ядра, что приводит к установке точки синхронизации в конце ядра (например, если бы вы выполняли столько работы по реверсированию битов, сколько могли в то же самое ядро, которое вы делаете бабочки). И... ну, вы их разделили. Так что они уже глобально синхронизированы. Но, помимо того, что это немного странно (на мой взгляд), что вы делаете DIT и переходите от нормального порядка к обратному биту (я думал, что для этого нужен DIF?) ... Я не вижу ничего очевидного. . продолжение - person user; 17.06.2013
comment
Я ничего не вижу, где вы ловите, если ваш глобальный размер меньше, чем общий объем данных, которые у вас есть (но... ваш размер данных выглядит достаточно маленьким, чтобы на любом современном графическом процессоре это не должно быть практической проблемой здесь.. .. и у вас сильно шелушится хвост, так что, вероятно, это не проблема). В настоящее время в моем распоряжении нет компьютера с поддержкой OpenCL, чтобы протестировать его, но почему бы вам не попробовать запустить его на последовательности [0,1,2,3] или на чем-то подобном простом. Кроме того, я только что заметил это... что вы делаете с внутренними и внешними циклами for num_devices? Я отмахнулся, но выглядит странно. - person user; 17.06.2013
comment
Кроме того, это может звучать глупо, но эй, просто проверка... Я вижу данные абсолютного значения на графике результатов OCL. Но похоже, что с таким же успехом вы могли бы построить абсолютное значение реальных частей БПФ Гаусса. Вы уверены, что проблема в том, куда вы смотрите (т. е. в том, что на графиках отображаются правильные данные)? Опять же, я не могу просто проверить код GPU прямо сейчас, но, думаю, стоит проверить мелочи. - person user; 17.06.2013
comment
Мод|Z| для всех из них. В.р.т. циклы for, извините, я исправил, они не должны быть вложенными. Предположим, что у меня много наборов преобразований, и для максимального параллелизма я удостоверяюсь, что у каждого устройства есть один набор. Как я уже сказал, я пробовал маленькие и большие наборы как гауссовых, так и других случайных функций. Когда набор небольшой (N = 4-16), он выглядит правильно, но странный зашумленный вывод начинает происходить при больших значениях N. - person Steve Novakov; 18.06.2013
comment
Добавлю, что используется только регистровая память, а конфликтов ч/б потоков при доступе к глобальной памяти нет. Насколько я понимаю, это означает, что синхронизация памяти не требуется. - person Steve Novakov; 18.06.2013