このBlogは移転しました。今後は aish.dev を御覧ください。

Python/CFFIでCUDAしてみる

ふと思い立って、CUDAでCPUからGPUにデータを転送する時の速度を測ってみた。

普通にCUDA SDKのサンプルで測定しても良いが、PythonCFFI で実行してみよう。

コードはこんな感じで書ける。

import time
import sys
from cffi import FFI
ffi=FFI()

ffi.cdef(r"""
typedef unsigned int cudaError_t;
cudaError_t cudaGetLastError();
char* cudaGetErrorString(cudaError_t error);

cudaError_t cudaDeviceSynchronize();
cudaError_t cudaMalloc(void **p, size_t s);
cudaError_t cudaFree(void *p);
cudaError_t cudaMallocHost(void **p, size_t s);
cudaError_t cudaFreeHost(void *p);
cudaError_t cudaMemcpy(void *dst, const void *src, size_t count, unsigned int kind);

""")

cudaMemcpyHostToHost          =   0
cudaMemcpyHostToDevice        =   1
cudaMemcpyDeviceToHost        =   2
cudaMemcpyDeviceToDevice      =   3
cudaMemcpyDefault             =   4

cuda = ffi.dlopen("/usr/local/cuda/lib64/libcudart.so")

def check():
    err = cuda.cudaGetLastError()
    if err:
        print(ffi.string(cuda.cudaGetErrorString(ret)))
    assert err == 0

def run(host, dev, n, size):
    for i in range(n):
        ret = cuda.cudaMemcpy(dev[0], host[0], size, cudaMemcpyHostToDevice)
        check()

size = int(sys.argv[1])

pinned = ffi.new("void **")
cuda.cudaMallocHost(pinned, size)
check()

ffi.memmove(pinned[0], b'a'*size, size)

dev = ffi.new("void **")
cuda.cudaMalloc(dev, size)
check()

run(pinned, dev, 1, 1) # warm up

buf = ffi.new("char []", b'a'*size)
host = ffi.new("char **", buf)

N = 1000
for s in range(1, size, size//20):
    f = time.time()
    run(pinned, dev, N, s)
    p = time.time()-f

    f = time.time()
    run(host, dev, N, s)
    n= time.time()-f

    print(f'{s}, {p:0.5f}, {n:0.5f}')

AWSのp2.xlargeとp3.2xlargeで実行してみた。

p2.xlarge
Tesla K80
E5-2686 v4 @ 2.30GHz
p3.2xlarge   
Tesla V100-SXM2-16GB
E5-2686 v4 @ 2.30GHz

f:id:atsuoishimoto:20180130100853p:plain

ざっくり、Pinnedメモリのほうが15%くらい速い。また、転送速度に関してはp2.xlargeでもp3.2xlargeでも大差はないようだ。

ちなみに、手元にあったi7-6700 CPU @ 3.40GHz/GeForce GTX 1070 と E5-1650 v4 @ 3.60GHz/GeForce GTX 1080 Ti のデスクトップは、EC2のインスタンスより2割程度高速だった。

また、~100KBぐらいまでの転送量を見てみると、50KBぐらいまではPinnedメモリより普通のメモリのほうが速いようだ。

f:id:atsuoishimoto:20180130100921p:plain

これは、データの転送よりも、転送の前処理・後処理に時間がかかってしまっているのかもしれない。ストリームを使って並列化するなどすればまた違ってくるかもしれないので、あとで実験してみたい。