PyCUDA точность кода умножения матриц

Я пытаюсь выучить CUDA и использовать PyCUDA для написания простого кода умножения матриц. Для двух случайно сгенерированных матриц 4x4 я получаю следующее решение:

Cuda:
[[ -5170.86181641 -21146.49609375  20690.02929688 -35413.9296875 ]
 [-18998.5         -3199.53271484  13364.62890625   7141.36816406]
 [ 31197.43164062  21095.02734375   1750.64453125  11304.63574219]
 [  -896.64978027  18424.33007812 -17135.00390625   7418.28417969]]

Python:
[[ -5170.86035156 -21146.49609375  20690.02929688 -35413.9296875 ]
 [-18998.5         -3199.53271484  13364.62695312   7141.36816406]
 [ 31197.43164062  21095.02929688   1750.64404297  11304.63574219]
 [  -896.64941406  18424.33007812 -17135.00390625   7418.28417969]]

Cuda-Python:
[[-0.00146484  0.          0.          0.        ]
 [ 0.          0.          0.00195312  0.        ]
 [ 0.         -0.00195312  0.00048828  0.        ]
 [-0.00036621  0.          0.          0.        ]]

Ошибка порядка 1e-3 и увеличивается по мере увеличения размера матриц. Я не уверен, является ли это ошибкой или нет. Мой вопрос в том, является ли такая "большая" ошибка нормальным явлением для int32 или я что-то делаю не так?

Вот исходный код:

matmul.py

import numpy as np
import time
# import pycuda stuff
import pycuda.driver as cuda
import pycuda.autoinit
from pycuda.compiler import SourceModule

BLOCK_SIZE = 16

n = 4
ni = np.int32(n)

# matrix A 
a = np.random.randn(n, n)*100
a = a.astype(np.float32)

# matrix B
b = np.random.randn(n, n)*100
b = b.astype(np.float32)

# matrix B
c = np.empty([n, n])
c = c.astype(np.float32)

# allocate memory on device
a_gpu = cuda.mem_alloc(a.nbytes)
b_gpu = cuda.mem_alloc(b.nbytes)
c_gpu = cuda.mem_alloc(c.nbytes)

# copy matrix to memory
cuda.memcpy_htod(a_gpu, a)
cuda.memcpy_htod(b_gpu, b)

# compile kernel
mod = SourceModule(open("kernels.cu", "r").read())

# get function
matmul = mod.get_function("matmul");


# set grid size
if n%BLOCK_SIZE != 0:
    grid=(n/BLOCK_SIZE+1,n/BLOCK_SIZE+1,1)
else:
    grid=(n/BLOCK_SIZE,n/BLOCK_SIZE,1)

# call gpu function
start = time.time()
matmul(ni, a_gpu, b_gpu, c_gpu, block=(BLOCK_SIZE,BLOCK_SIZE,1), grid=grid);
end = time.time()
print "Time: %.5f s"%(end-start)

# copy back the result
cuda.memcpy_dtoh(c, c_gpu)

print np.linalg.norm(c - np.dot(a,b))
print c
print np.dot(a,b)
print c - np.dot(a,b)

ядра.cu

__global__ void matmul(int n, const float *A, const float *B, float *C){

  int tx = threadIdx.x;
  int ty = threadIdx.y;

  int bx = blockIdx.x;
  int by = blockIdx.y;

  int row = by*blockDim.y + ty;
  int col = bx*blockDim.x + tx;

  if(row < n && col < n){
    float val = 0.0;
    for(int i=0; i<n; ++i){
      val += A[row*n + i]*B[n*i + col];
    }
    C[row*n + col] = val;
  }
}

person 0b1100001    schedule 15.01.2014    source источник
comment
относительная ошибка имеет порядок 1e-7, что приемлемо для вычислений с плавающей запятой одинарной точности (32 бита).   -  person Warren Weckesser    schedule 15.01.2014
comment
Спасибо! Я запутался между относительным и абсолютным порядком точности.   -  person 0b1100001    schedule 16.01.2014
comment
@maverick: я изучаю программирование cuda, и ваш пример выглядит аккуратно. Я попытался запустить код, но я получаю сообщение об ошибке. TypeError: ни один зарегистрированный преобразователь не смог создать значение C++ rvalue типа unsigned int из этого объекта Python типа float   -  person Sooraj    schedule 10.01.2019


Ответы (1)


Просто добавлю к тому, что сказал Уоррен. Я не думаю, что здесь что-то не так. Вы сравниваете результаты с плавающей запятой, сгенерированные двумя разными машинами (ЦП и ГП). Не гарантируется их битовая идентичность для операций на уровне, о котором вы думаете, отчасти потому, что порядок операций на графическом процессоре не обязательно совпадает с порядком операций на графическом процессоре. Когда вы увеличиваете размер матриц, вы увеличиваете количество значений, суммируемых вместе, и ваша абсолютная ошибка увеличивается, поскольку вы добавляете вместе кучу небольших битовых ошибок.

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

person Robert Crovella    schedule 15.01.2014