Почему при одновременном использовании numba.cuda и CuPy так медленно передаются данные с графического процессора? - PullRequest
0 голосов
/ 10 июля 2020

Я прочитал пример из документа Cupy о том, как использовать cupy и numba вместе и использовать cuda для ускорения кода. https://docs-cupy.chainer.org/en/stable/reference/interoperability.html

И я пишу аналогичный код для его проверки:

import cupy
from numba import cuda
import numpy as np
import time

@cuda.jit('void(float32[:], float32[:], float32[:])')
def add(x, y, out):
        start = cuda.grid(1)
        stride = cuda.gridsize(1)
        for i in range(start, x.shape[0], stride):
                out[i] = x[i] + y[i]


a = cupy.arange(10000000)
b = a * 2
out = cupy.zeros_like(a)

print("add function time consuming:")
s = time.time()
add(a, b, out)
e = time.time()
print(e-s)
s = time.time()
print("out[2]:")
print(out[2])
e = time.time()
print("the time of transfering out[2] out of GPU:")
print(e-s)

s = time.time()
new_OUT = a + b
print("new out[2] which only use cupy:")
print(new_OUT[2])
e = time.time()
print("the total time of running cupy addition and transfering new out[2] out of GPU:")
print(e-s)

Результат:

add function time consuming:
0.0019025802612304688
out[2]:
6
the time of transfering out[2] out of GPU:
1.5608515739440918
new out[2] which only use cupy:
6
the total time of running cupy addition and transfering new out[2] out of GPU:
0.002993345260620117

Как может вызов out [2] такой медленный в первом случае?

Я пишу некоторые функции, которые должны иметь дело с некоторыми массивами и матрицей. Функции работают нормально, но после запуска этих функций, когда мне нужно внести некоторые изменения, даже вызвать что-то вроде out.shape, это очень медленно (мои матрицы и массивы очень огромны).

Я не уверен что здесь происходит, поскольку cupy также использует cuda, поэтому, когда я вызываю a + b, он должен работать на GPU, но когда я вызываю out[2], чтобы проверить значение out [2], времени почти не требуется. Но расход сверхвысокий для первого случая.

1 Ответ

2 голосов
/ 10 июля 2020

Есть как минимум 2 вещи, о которых нужно знать для понимания вывода вашего кода:

  1. В CUDA запуск ядра обычно настроен для указания сетки конфигурация (количество блоков, количество потоков на блок). При запуске ядра Numba CUDA конфигурация сетки обычно отображается в квадратных скобках непосредственно перед аргументами ядра:

    kernel_name[grid_configuration](kernel_arguments)
    

    В numba CUDA синтаксически допустимо опускать квадратные скобки и конфигурацию сетки, которая имеет неявное значение конфигурации сетки [1,1] (один блок, состоящий из одного потока). Ваше ядро ​​работает с более или менее произвольной конфигурацией сетки, потому что оно использует grid-stride l oop. Однако это не означает, что конфигурация сетки не имеет значения. Это имеет значение для производительности. Конфигурация сетки [1,1] даст мрачную производительность и никогда не должна использоваться при запуске ядра CUDA, где производительность имеет значение. Таким образом, мы можем исправить это, изменив вызов вашего ядра, например:

    add[1024,256](a, b, out)
    

    , который запустит сетку из 1024 блоков, каждый с 256 потоками.

  2. В CUDA ядро ​​запускается асинхронно . Это означает, что код хоста, запускающий ядро, инициирует запуск, но не будет ждать, пока ядро ​​завершит . То же самое относится к запускам ядра Numba CUDA. Поэтому измерения времени самого запуска ядра обычно сбивают с толку. Для целей синхронизации это можно отрегулировать, заставив поток ЦП ждать в пределах временной области, пока ядро ​​не будет завершено. В numba CUDA мы можем выполнить sh это с помощью:

    cuda.synchronize()
    
...