dot product พร้อม PyCUDA และหน่วยความจำที่ปักหมุด

ขณะนี้ฉันกำลังทำงานกับดอทโปรดัคที่มีหน่วยความจำที่ปักหมุดไว้โดยใช้ PyCUDA และฉันมีปัญหากับอาร์เรย์ขนาดใหญ่

ฉันทำงานกับ:

  • NVIDIA GTX1060
  • CUDA 9.1
  • PyCUDA 2017.1.1

รหัสคือ:

#!/usr/bin/env python

import numpy as np
import argparse
import math
import pycuda.autoinit
import pycuda.driver as drv
from pycuda.compiler import SourceModule

from time import time

dot_mod = SourceModule("""
__global__ void full_dot( double* v1, double* v2, double* out, int N ) {
    __shared__ double cache[ 1024 ];
    int i = blockIdx.x * blockDim.x + threadIdx.x;
    cache[ threadIdx.x ] = 0.f;
    while( i < N ) {
        cache[ threadIdx.x ] += v1[ i ] * v2[ i ];
        i += gridDim.x * blockDim.x;
    }
    __syncthreads(); // required because later on the current thread is accessing
                     // data written by another thread    
    i = 1024 / 2;
    while( i > 0 ) {
        if( threadIdx.x < i ) cache[ threadIdx.x ] += cache[ threadIdx.x + i ];
        __syncthreads();
        i /= 2; //not sure bitwise operations are actually faster
    }

#ifndef NO_SYNC // serialized access to shared data; 
    if( threadIdx.x == 0 ) atomicAdd( out, cache[ 0 ] );
#else // no sync, what most likely happens is:
      // 1) all threads read 0
      // 2) all threads write concurrently 16 (local block dot product)
    if( threadIdx.x == 0 ) *out += cache[ 0 ];
#endif                

}
""")


def main(args):
    dot = dot_mod.get_function("full_dot")
    N = args.number
    BLOCK_SIZE = 1024
    BLOCKS = int(math.ceil(N/BLOCK_SIZE))
    THREADS_PER_BLOCK = BLOCK_SIZE

    # Time use of pinned host memory:
    x = drv.aligned_empty((N), dtype=np.float64, order='C')
    x = drv.register_host_memory(x, flags=drv.mem_host_register_flags.DEVICEMAP)
    x_gpu_ptr = np.intp(x.base.get_device_pointer())

    # Time use of pinned host memory:
    y = drv.aligned_empty((N), dtype=np.float64, order='C')
    y = drv.register_host_memory(y, flags=drv.mem_host_register_flags.DEVICEMAP)
    y_gpu_ptr = np.intp(y.base.get_device_pointer())

    # Time use of pinned host memory:
    z = drv.aligned_empty((1), dtype=np.float64, order='C')
    z = drv.register_host_memory(z, flags=drv.mem_host_register_flags.DEVICEMAP)
    z_gpu_ptr = np.intp(z.base.get_device_pointer())

    z[:] = np.zeros(1)
    x[:] = np.zeros(N)
    y[:] = np.zeros(N)

    x[:] = np.random.rand(N)
    y[:] = x[:] 
    x_orig = x.copy()
    y_orig = y.copy()

    start = time()
    dot(x_gpu_ptr, y_gpu_ptr, z_gpu_ptr, np.uint32(N), block=(THREADS_PER_BLOCK, 1, 1), grid=(BLOCKS,1))
    times = time()-start
    print "Average kernel GPU dot product execution time with pinned memory:   %3.7f" % np.mean(times)

    start = time()
    ydot=np.dot(x_orig,y_orig)
    times = time()-start
    print "Average numpy dot product execution time:   %3.7f" % np.mean(times)

    print N,ydot,z[0]

if __name__ == "__main__":

    parser = argparse.ArgumentParser(description=' ')
    parser.add_argument('-n', dest='number', type=long, help="Number of samples ", required=True)

    args = parser.parse_args()

    main(args)

ฉันได้เขียนโค้ดนี้ซึ่งทำงานได้ดีเมื่ออาร์เรย์ตัวอย่างมีขนาดประมาณน้อยกว่า 1024*12 แต่สำหรับอาร์เรย์ขนาดใหญ่เช่นค่า 1024*1024 ให้ผลลัพธ์ที่ผิด

➜  ./test_dot_pinned.py -n 16384
Average kernel GPU dot product execution time with pinned memory:   0.0001669
Average numpy dot product execution time:   0.0000119
16384 5468.09590706 5468.09590706
 SIZE   np.dot()    GPU-dot-pinned

➜  ./test_dot_pinned.py -n 1048576
Average kernel GPU dot product execution time with pinned memory:   0.0002351
Average numpy dot product execution time:   0.0010922
1048576 349324.532564 258321.148593
 SIZE   np.dot()    GPU-dot-pinned

ขอบคุณทุกคน ฉันหวังว่าจะมีคนช่วยฉันได้


person acancio    schedule 18.12.2017    source แหล่งที่มา


คำตอบ (1)


pycuda ไม่บังคับใช้การซิงโครไนซ์ใด ๆ หลังจากการเปิดตัวเคอร์เนล โดยปกติ หากคุณทำสำเนาข้อมูลของอุปกรณ์ -> โฮสต์หลังจากเรียกใช้เคอร์เนล การดำเนินการจะบังคับให้มีการซิงโครไนซ์ กล่าวคือ จะบังคับให้เคอร์เนลดำเนินการให้เสร็จสิ้น

แต่คุณไม่มีการซิงโครไนซ์ดังกล่าวในโค้ดของคุณ เนื่องจากคุณใช้หน่วยความจำที่ปักหมุดไว้ เมื่อเวลาดำเนินการเคอร์เนลเพิ่มขึ้น (เนื่องจากขนาดงานใหญ่ขึ้น) ในที่สุดเมื่อคุณพิมพ์ z[0] คุณจะได้รับผลลัพธ์เพียงบางส่วนเท่านั้น เนื่องจากเคอร์เนลยังไม่เสร็จสิ้น ณ จุดนั้น

ผลข้างเคียงก็คือการวัดเวลาเคอร์เนลของคุณไม่แม่นยำ

คุณสามารถแก้ไขทั้งสองสิ่งนี้ได้โดยการบังคับให้เคอร์เนลดำเนินการก่อนที่คุณจะเสร็จสิ้นการวัดเวลา:

dot(x_gpu_ptr, y_gpu_ptr, z_gpu_ptr, np.uint32(N), block=(THREADS_PER_BLOCK, 1, 1), grid=(BLOCKS,1))
#add the next line of code:
drv.Context.synchronize()
times = time()-start
person Robert Crovella    schedule 18.12.2017
comment
ว้าว ขอบคุณมากสำหรับคำตอบที่รวดเร็ว มีประโยชน์มาก ขอแสดงความนับถืออย่างสูง. - person acancio; 19.12.2017