การแปลงผกผัน 2D CuFFT ผิดเมื่อเปรียบเทียบกับ fftw3 [ปิด]

ฉันกำลังพยายามสร้างคณิตศาสตร์ FFT โดยเฉพาะอย่างยิ่ง มันทำการแปลงไปข้างหน้า 2D สองครั้ง คูณมัน แล้วจึงทำการแปลงผกผัน ก่อนการแปลงผกผัน ทุกอย่างไปได้ดี ฉันได้ทำไปแล้วโดย fftw3 แต่ใน CuFFT มีบางอย่างผิดพลาด ค่าส่วนใหญ่คล้ายกัน แต่มีบางค่าที่ผิด ซึ่งมีความสำคัญสำหรับคณิตศาสตร์ในอนาคต โค้ดชิ้นนี้มีปัญหาอะไร

std::vector<complex> conv2dCUDA(complex *ui_anomaly, double *ds2, 
complex *u0, int anx, int any, double factor) {
    cufftComplex *b1, *b2;
    int size = 2 * anx * 2 * any;
    int memsize = size *  sizeof(cufftComplex);
    b1 = (cufftComplex *)calloc(size, sizeof(cufftComplex));
    b2 = (cufftComplex *)calloc(size, sizeof(cufftComplex));

    // filling the matrixes    

    cufftHandle plan;
    cufftComplex *ui, *g;
    checkCudaErrors(cudaMalloc((void**)&ui, memsize));
    checkCudaErrors(cudaMalloc((void**)&g,  memsize));
    checkCudaErrors(cufftPlan2d(&plan, 2 * anx, 2 * any, CUFFT_C2C));
    checkCudaErrors(cudaMemcpy(ui, (cufftComplex *)&b1[0], memsize, cudaMemcpyHostToDevice));
    checkCudaErrors(cudaMemcpy(g, (cufftComplex *)&b2[0], memsize, cudaMemcpyHostToDevice));
    checkCudaErrors(cufftExecC2C(plan, ui, ui, CUFFT_FORWARD));
    checkCudaErrors(cufftExecC2C(plan, g, g, CUFFT_FORWARD));
    int blockSize = 16;
    dim3 dimGrid(int(2 * any / blockSize) + 1, int(2 * anx / blockSize) + 1);
    dim3 dimBlock(blockSize, blockSize);

    ComplexMulAndScale<<<dimGrid, dimBlock>>>(ui, g, size, 1.0f);
    getLastCudaError("Kernel execution went wrong");

    checkCudaErrors(cudaMemcpy(b1, ui, memsize, cudaMemcpyDeviceToHost));
    std::cout << "After mult Cuda" << std::endl;
    for (auto i = 0; i < 2 * any; i++) {
       for (auto j = 0; j < 2 * anx; j++) {
            std::cout << b1[i * 2 * anx + j].x << " ";
       }
       std::cout << std::endl;
    }

    checkCudaErrors(cufftExecC2C(plan, ui, ui, CUFFT_INVERSE));
    cuComplex *inversed;
    inversed = (cuComplex*)malloc(memsize);
    checkCudaErrors(cudaMemcpy(inversed, ui, memsize, cudaMemcpyDeviceToHost));
    std::vector<complex> res(anx * any);
    for (auto i = 0; i < any; i++) {
        for (auto j = 0; j < anx; j++) {
            res[i * anx + j] = complex(inversed[i * anx * 2 + j].x * factor, inversed[i * anx * 2 + j].y * factor);
        }
    }
    std::cout << "CUDA"  << std::endl;
    for (auto i = 0; i < 2 * any; i++) {
        for (auto j = 0; j < 2 * anx; j++) {
            std::cout << inversed[i * 2 * anx + j].x << " ";
        }
        std::cout << std::endl;
    }
    checkCudaErrors(cudaFree(ui));
    checkCudaErrors(cudaFree(g));
    checkCudaErrors(cufftDestroy(plan));
    free(b1);
    free(b2);
    free(inversed);
    return res;
}

std::vector<complex> conv2d(complex *ui_anomaly, double *ds2, complex *u0, int anx, int any, double factor) {
    std::vector<complex> b1(anx * 2 * 2 * any, complex(0., 0.)), b2(anx * 2 * 2 * any, complex(0., 0.));

    // filling matrixes

    // forward fft 1 in-place
    fftw_plan p;
    p = fftw_plan_dft_2d(2 * any, 2 * anx, (fftw_complex *) (&b1[0]), (fftw_complex *) (&b1[0]),
                     FFTW_FORWARD, FFTW_ESTIMATE);
    fftw_execute(p);
    fftw_destroy_plan(p);
    // forward fft 2 in-place
    p = fftw_plan_dft_2d(2 * any, 2 * anx, (fftw_complex *) (&b2[0]), (fftw_complex *) (&b2[0]),
                     FFTW_FORWARD, FFTW_ESTIMATE);
    fftw_execute(p);
    fftw_destroy_plan(p);
    std::vector<complex> out(2 * anx * 2 * any, complex(0., 0.));

    for (auto i = 0; i < 2 * any * 2 * anx; i++) {
        out[i] = b1[i] * b2[i];
    }
    std::cout << "After mult fftw" << std::endl;
    for (auto i = 0; i < 2 * any; i++) {
        for (auto j = 0; j < 2 * anx; j++) {
            std::cout << out[i * 2 * anx + j].real() << " ";
       }
       std::cout << std::endl;
    }
    // inverse fft in-place
    p = fftw_plan_dft_2d(2 * (int) any, 2 * (int) anx, (fftw_complex *) (&out[0]), (fftw_complex *) (&out[0]),FFTW_BACKWARD, FFTW_ESTIMATE);
    fftw_execute(p);
    fftw_destroy_plan(p);

    std::vector<complex> res(anx * any);
    for (auto i = 0; i < any; i++) {
        for (auto j = 0; j < anx; j++) {
            res[i * anx + j] = out[i * anx * 2 + j] * factor;
       }
    }
    std::cout << "FFTW" << std::endl;
    for (auto i = 0; i < 2 * any; i++) {
        for (auto j = 0; j < 2 * anx; j++) {
            std::cout << out[i * 2 * anx + j].real() << " ";
        }
        std::cout << std::endl;
    }
    return res;
}

นี่คือรหัสของฉัน เอาต์พุตควรอยู่ในทั้งสองฟังก์ชัน

After mult fftw
8.34304e-08 -5.99259e-07 -4.7876e-07 5.30254e-07 9.55877e-07 4.28985e-07 
-1.56375e-07 1.19699e-07 2.39276e-07 -1.68662e-08 -7.56988e-08 -3.69897e-07 
-2.66505e-07 -2.33361e-07 -5.21763e-07 -5.29126e-07 1.8915e-07 1.68158e-07 
-9.01859e-07 -2.37453e-07 -3.50661e-08 -4.11154e-07 4.14802e-07 -7.9879e-07 
2.09404e-07 6.52034e-08 1.8915e-07 4.97805e-07 3.32612e-07 -2.33361e-07 
-1.95738e-07 -3.69897e-07 -1.63577e-07 1.07737e-07 2.39276e-07 2.50198e-07 
FFTW
-1.57349e-06 -7.5964e-06 -1.57349e-06 1.68876e-06 5.82335e-22 1.68876e-06 
2.37158e-06 6.35275e-22 2.37158e-06 -1.18579e-06 1.05879e-22 -1.18579e-06 
-1.57349e-06 -7.5964e-06 -1.57349e-06 1.68876e-06 1.97573e-22 1.68876e-06 
3.14928e-06 2.37158e-06 3.14928e-06 -4.94164e-07 5.82335e-22 -4.94164e-07 
2.11758e-22 -8.47033e-22 -1.05879e-22 5.29396e-22 1.41851e-23 1.05879e-22 
3.14928e-06 2.37158e-06 3.14928e-06 -4.94164e-07 1.05879e-22 -4.94164e-07 

After mult Cuda
8.34303e-08 -5.99259e-07 -4.78761e-07 5.30254e-07 9.55877e-07 4.28985e-07 
-1.56375e-07 1.19699e-07 2.39276e-07 -1.68662e-08 -7.56988e-08 -3.69897e-07 
-2.66505e-07 -2.33361e-07 -5.21763e-07 -5.29126e-07 1.8915e-07 1.68158e-07 
-9.01859e-07 -2.37453e-07 -3.50661e-08 -4.11154e-07 4.14802e-07 -7.9879e-07 
2.09404e-07 6.52034e-08 1.8915e-07 4.97805e-07 3.32612e-07 -2.33361e-07 
-1.95738e-07 -3.69897e-07 -1.63577e-07 1.07737e-07 2.39276e-07 2.50198e-07 
CUDA
-1.57349e-06 -7.5964e-06 -1.57349e-06 1.68876e-06 3.33981e-13 1.68876e-06 
2.37158e-06 2.84217e-13 2.37158e-06 -1.18579e-06 1.10294e-13 -1.18579e-06 
-1.57349e-06 -7.5964e-06 -1.57349e-06 1.68876e-06 -9.03043e-14 1.68876e-06 
3.14928e-06 2.37158e-06 3.14928e-06 -4.94164e-07 4.62975e-13 -4.94164e-07 
-3.2685e-13 -1.03562e-13 -3.59548e-13 -2.13163e-13 4.27658e-15 -2.43358e-14 
3.14928e-06 2.37158e-06 3.14928e-06 -4.94164e-07 3.49288e-13 -4.94164e-07 

อย่างที่เห็น ทั้งการส่งต่อ fft และการคูณดำเนินไปในทางที่ถูกต้อง แต่ในกรณีของการผกผัน fft ด้วย cuda smth นั้นผิดพลาด

ป.ล. ขออภัยสำหรับรูปแบบโค้ดที่ไม่ดี


person aleks    schedule 28.08.2019    source แหล่งที่มา
comment
คุณรู้ได้อย่างไรว่า fftw เป็นสิ่งที่ถูกต้อง? คุณเคยคำนวณฟุตด้วยมือหรือไม่?   -  person huseyin tugrul buyukisik    schedule 28.08.2019
comment
@huseyin ฉันทำด้วยมือและได้รับการทดสอบกับตัวอย่างบางส่วนด้วย   -  person aleks    schedule 28.08.2019
comment
คุณได้พิมพ์เฉพาะค่าจริงในโดเมนที่ซับซ้อนหลังจากการคูณและมาตราส่วนตามจุดเท่านั้น บางทีรหัสเคอร์เนลของคุณเสียซึ่งคุณไม่ได้แสดง อย่างไรก็ตาม คุณควรระบุโค้ดให้ครบถ้วน ไม่ใช่แค่ 2 ฟังก์ชัน ให้ข้อมูล (มีไม่มาก) รวมถึงรูทีนหลักที่เรียกใช้ทั้งสองฟังก์ชัน ทุกสิ่งที่คนอื่นต้องการเพื่อเรียกใช้โค้ดของคุณ สิ่งนี้เรียกว่า ตัวอย่างที่ทำซ้ำได้น้อยที่สุด และ SO คาดว่าจะเป็นเช่นนั้นสำหรับคำถามเช่นนี้ ดูรายการที่ 1 ที่นี่   -  person Robert Crovella    schedule 28.08.2019
comment
นี่ดูเหมือนเป็นปัญหาที่ชัดเจนสำหรับฉัน ความแม่นยำ fftw เริ่มต้นคือ 64 บิต ในขณะที่คุณกำลังใช้ cufft รูปแบบ 32 บิต ลองใช้ cufftDoubleComplex (64 บิต) แทน cufftComplex (32 บิต) และอ่านวิธีดำเนินการคำนวณ cufft 64 บิต (iirc, cufftExecC2C กลายเป็น cufftExecZ2Z อาจมีการเปลี่ยนแปลงอื่นๆ เช่นกัน) แก้ไข: เพิ่งสังเกตเห็นว่า @francis ตอบด้วยความคิดเห็นที่คล้ายกัน ฉันเห็นด้วย   -  person Michael    schedule 29.08.2019


คำตอบ (1)


เมื่อใช้ FFTW สัญญาณที่บิดเบี้ยวจะมีตัวเลขจำนวนมากประมาณ 1e-6 และตัวเลขสองสามตัวประมาณ 1e-22 มีแนวโน้มว่าควรจะเป็นศูนย์ แต่ไม่ใช่เนื่องจากศูนย์เหล่านี้คำนวณโดยใช้ตัวเลขทศนิยมที่มีความแม่นยำสองเท่า ตัวเลขที่มีความแม่นยำสองเท่ามีความแม่นยำประมาณ 15 หลัก ดังนั้นจึงอาจเกิดข้อผิดพลาดใกล้กับ 1e(-6-15)=1e-21 ได้

เมื่อใช้ผ้าพันแขน ตัวเลขเหล่านี้ที่ควรเป็นศูนย์จะมีค่าประมาณ 1e-13 ราวกับว่ามีการคำนวณโดยใช้เลขทศนิยมที่มีความแม่นยำเพียงตัวเดียว เป็นกรณี: ประเภท cuComplex และ cufftComplex เป็น single precision complex ในขณะที่ fftw_complex เป็นคอมเพล็กซ์ที่มีความแม่นยำสองเท่า แม้ว่า complex มีแนวโน้มว่าจะใช้ค่าเริ่มต้นเป็น double precision แต่ก็สามารถระบุได้ชัดเจนว่าเป็น double complex

หากต้องการให้ได้ตัวเลขใกล้ 1e-22 โปรดลองประเภท cufftDoubleComplex และ cuDoubleComplex. blockSize ที่ใช้ในการคูณหลายครั้งอาจต้องลดลงเหลือ 8 อย่างไรก็ตาม แม้ว่ามีแนวโน้มว่าจะได้ตัวเลขขนาด 1e-22 แต่ก็มีแนวโน้มว่าตัวเลขเหล่านี้ยังคงแตกต่างจากตัวเลขของ FFTW อันที่จริง เนื่องจากอัลกอริธึมอาจแตกต่างกัน การดำเนินการต่างๆ จึงสามารถดำเนินการได้ และความแม่นยำก็คือว่าสิ่งใดๆ ในผลลัพธ์ประมาณ 1e-22 ก็ไม่แตกต่างจาก 0 อย่างมีนัยสำคัญ

อย่างไรก็ตาม การเปลี่ยนตัวเลขความแม่นยำสองเท่าอาจเพิ่มเวลาในการคำนวณและเพิ่มพื้นที่หน่วยความจำอย่างชัดเจน หากความแม่นยำหกหลักในผลลัพธ์ของการบิดนั้นดีเพียงพอสำหรับการใช้งานของคุณ การใช้ DFT ที่ซับซ้อนที่มีความแม่นยำเดี่ยวอาจเป็นวิธีที่ถูกต้อง

person francis    schedule 28.08.2019