[PYTHON] Prozess auf GPU mit chainer.cuda.elementwise

Einführung

Wenn Sie den Code für Chainer-Funktionen lesen, werden möglicherweise Aufrufe von "cuda.elementwise" oder "cuda.reduce" angezeigt. Dies sind Methoden zum Ausführen Ihrer eigenen Verarbeitung auf der GPU. Man kann sagen, dass es eine unverzichtbare Methode zur Implementierung der Chainer-Funktion ist und es notwendig erscheint, ein Zwischen-Chainer zu werden, also habe ich es untersucht. Dieser Artikel befasst sich mit "cuda.elementwise" und behandelt "cuda.reduce" nicht.

Eine Beschreibung von cuda.elementwise finden Sie unten. http://docs.chainer.org/en/stable/cupy-reference/kernel.html Darüber hinaus hat Slide Share einen Kommentar von Herrn Okuda von Preferred Networks. http://www.slideshare.net/ryokuta/cupy

Bestätigte Umgebung

Was macht cuda.elementwise?

cuda.elementwise definiert den CUDA-Kernel. Der CUDA-Kernel ist ein Programm, das auf einer GPU ausgeführt wird, die CUDA unterstützt. Durch Aufrufen von "cuda.elementwise" erhält der Rückgabewert eine Funktion zum Aufrufen des CUDA-Kernels, und durch Aufrufen der Funktion zum Aufrufen des Kernels wird der CUDA-Kernel auf der GPU ausgeführt.

Die Substanz der Kernel-Aufruffunktion ist das ElementwiseKernel-Objekt, das aufgerufen werden kann.

Erster Beispielcode

Als erstes Beispiel werden wir alle Elemente eines bestimmten Arrays inkrementieren. Importieren Sie zunächst die erforderlichen Module wie unten gezeigt und definieren Sie "xp" als Referenz auf "cuda.cupy". Der nachfolgende Beispielcode setzt voraus, dass Sie den folgenden Code ausführen:

import numpy as np
import chainer
from chainer import cuda

xp = cuda.cupy

Verwenden Sie dann cuda.elementwise, um die Array-Elemente zu erhöhen.

x = xp.asarray([[1, 2, 3], [4, 5, 6]], dtype=np.float32)

y = cuda.elementwise(
'T x',
'T y',
'y = x + 1;',
'sample1_fwd',
)(x)

print(y)

Die Ausgabe sieht folgendermaßen aus: Sie können sehen, dass alle Elemente inkrementiert sind. Die Ausgabe nach der Ausgabe von vor dauert einige Zeit, aber es scheint, dass die Kompilierung von nvcc hinter den Kulissen erfolgt.

[[ 2.  3.  4.]
 [ 5.  6.  7.]]

Erläuterung des Beispielcodes

"cuda.elementwise" wird in zwei Stufen wie folgt verwendet. Im eigentlichen Code werden jedoch häufig zwei Prozesse zusammen beschrieben, z. B. "cuda.elementwise (...) (x)".

Die Erklärung des Argument- / Rückgabewerts von cuda.elementwise ist nicht diese Methode, sondern [Document of cupy.ElementwiseKernel](http://docs.chainer.org/en/stable/cupy-reference/kernel.html# Es ist in cupy.ElementwiseKernel) beschrieben. cuda.elementwise ruft intern cupy.ElementwiseKernel auf und beide Argumente sind gleich (außer dass name in cuda.elementwise erforderlich ist). Die folgenden Argumente sind für "cuda.elementwise" erforderlich. Es gibt andere optionale Argumente, aber ich habe sie noch nicht vollständig verstanden, daher werde ich sie nicht erklären.

in_params

Gibt die Zeichenfolge an, die die Eingabeargumente deklariert. Das Argument erfordert einen Typ und einen Argumentnamen. Wenn die Typzeichenfolge aus einem Zeichen besteht, handelt es sich um einen Platzhalter vom Typ **. Der durch den Typ-Platzhalter dargestellte Typ ist der Typ der Variablen, die beim Ausführen der Kernel-Aufruffunktion übergeben wird. Dies ist nützlich, wenn Sie Variablen desselben Typs deklarieren möchten.

Im Beispielcode war in_params wie folgt.

'T x',

Das Folgende wird durch diese Zeichenfolge ausgedrückt.

out_params

Gibt die Zeichenfolge an, die die Ausgabeargumente deklariert. Wie in_params erfordert das Argument einen Typ und einen Argumentnamen.

Im Beispielcode lautete out_params wie folgt.

'T y',

Das Folgende wird durch diese Zeichenfolge ausgedrückt.

operation

Geben Sie die Zeichenfolge an, die den auszuführenden Prozess definiert. Im Beispielcode wurde "y" durch "x + 1" ersetzt.

'y = x + 1;',

name

Der Name des Prozesses. Bei der Implementierung des Moduls unter chainer.functions ist es "Funktionsname_fwd" für die Vorwärtsverarbeitung und "Funktionsname_bwd" für die Rückwärtsverarbeitung.

Führen Sie die Kernel-Aufruffunktion aus

Durch Ausführen der Kernel-Aufruffunktion wird der definierte CUDA-Kernel ausgeführt. Übergeben Sie die Variable in_params zum Zeitpunkt der Ausführung als Argument. Die Variable, die out_params entspricht, kann weggelassen werden, sie kann jedoch auch explizit übergeben werden, indem sie nach der Variablen angegeben wird, die in_params entspricht. Der Rückgabewert ist das in out_params angegebene Argument. Wenn für out_params mehrere Argumente angegeben werden, sind diese Tupel der Rückgabewert.

Übergeben Sie Werte an out_params

Übergeben wir auch einen Wert an out_params. Übergeben Sie einfach den Wert für out_params an das Argument der Kernel-Aufruffunktion.

x = xp.asarray([[1, 2, 3], [4, 5, 6]], dtype=np.float32)
y = xp.asarray([[1, 1, 1], [2, 2, 2]], dtype=np.float32)

y = cuda.elementwise(
'T x',
'T y',
'y += x;',
'sample2_fwd',
)(x, y)

print(y)

Das Ausführungsergebnis ist wie folgt und x wird zum ursprünglichen y hinzugefügt.

[[ 2.  3.  4.]
 [ 6.  7.  8.]]

Broadcasting

Das Array wird automatisch gesendet.

x = xp.asarray([1, 2, 3], dtype=np.float32)
y = xp.asarray([[1, 1, 1], [2, 2, 2]], dtype=np.float32)

y = cuda.elementwise(
'T x',
'T y',
'y += x;',
'sample3_fwd',
)(x, y)

print(y)

Ausführungsergebnis:

[[ 2.  3.  4.]
 [ 3.  4.  5.]]

Wenn die Größe des Arrays nicht übereinstimmt und Sie nicht senden können, tritt ein Fehler auf.

x = xp.asarray([1, 2], dtype=np.float32)
y = xp.asarray([[1, 1, 1], [2, 2, 2]], dtype=np.float32)

y = cuda.elementwise(
'T x',
'T y',
'y += x;',
'sample4_fwd',
)(x, y)

print(y)

Ausführungsergebnis:

Traceback (most recent call last):
  File "elementwise_sample.py", line 61, in <module>
    )(x, y)
  File "cupy\core\elementwise.pxi", line 508, in cupy.core.core.ElementwiseKernel.__call__ (cupy\core\core.cpp:34118)
  File "cupy\core\elementwise.pxi", line 334, in cupy.core.core._broadcast (cupy\core\core.cpp:31734)
  File "cupy\core\core.pyx", line 1504, in cupy.core.core.broadcast.__init__ (cupy\core\core.cpp:50697)
ValueError: Broadcasting failed

Indexing

Sie möchten häufig einen Index angeben, wenn Sie mit einem Array arbeiten. Sie können den Index wie folgt angeben.

Hier ist ein Beispiel, das die Elemente eines Arrays umkehrt.

x = xp.asarray([1, 2, 3, 4], dtype=np.float32)
y = xp.zeros_like(x, dtype=np.float32)

y = cuda.elementwise(
'raw T x',
'T y',
'y = x[_ind.size() - i - 1];',
'sample5_fwd',
)(x, y)

print(y)

Das Ausführungsergebnis ist wie folgt.

[ 4.  3.  2.  1.]

Sie können sich den obigen Code so vorstellen, dass der folgende Code mit Numpy auf einer GPU ausgeführt wird.

x = np.asarray([1, 2, 3, 4], dtype=np.float32)
y = np.zeros_like(x, dtype=np.float32)
i = np.arange(4)

y = x[4 - i - 1]

Beachten Sie, dass Sie "y" an die Kernel-Aufruffunktion übergeben müssen. Wenn Sie "y" nicht wie unten gezeigt übergeben, wird der Fehler "Wert Fehler: Schleifengröße ist unentschlossen" angezeigt. Dies scheint zu passieren, weil Sie den Index nicht nur mit rohen Argumenten dimensionieren können.

x = xp.asarray([1, 2, 3, 4], dtype=np.float32)
y = xp.zeros_like(x, dtype=np.float32)

y = cuda.elementwise(
'raw T x',
'T y',
'y = x[_ind.size() - i - 1];',
'sample6_fwd',
)(x)

print(y)

Etwas komplizierteres Indizieren

Betrachten Sie das Erhalten von "x [t [i]]" (i = 0, 1, 2, ...), wenn "x" ein zweidimensionales Array und "t" ein eindimensionales Array ist. .. Dies kann wie folgt geschrieben werden.

x = xp.asarray([[1, 2, 3], [4, 5, 6], [7, 8, 9]], dtype=np.float32)
t = xp.asarray([0, 2, 1], dtype=np.int32)

y = cuda.elementwise(
'raw T x, S t',
'T y',
'int ind[] = {i, t}; y = x[ind];',
'sample7_fwd',
)(x, t)

print(y)

Ausführungsergebnis:

[ 1.  6.  8.]

"int ind [] = {i, t};" erzeugt einen Index, der [(0, t [0]), (1, t [1]), (2, t [2])] anzeigt. ..

für Schleife

Sie können die C-Syntax (nvcc? Um genau zu sein) wie for und while verwenden. Berechnen Sie als Beispiel den kumulativen Wert für jede Spalte von x. Stellen Sie sicher, dass "y [i, j]" von "x [0, j]" bis "x [i, j]" kumulativ ist.

x = xp.asarray([[1, 2, 3], [4, 5, 6], [7, 8, 9]], dtype=np.float32)
y = xp.zeros_like(x)

y = cuda.elementwise(
'raw T x, int32 c',
'raw T y',
'''
int ind[] = {0, i};
y[ind] = x[ind];
for (int j = 1; j < c; j++) {
    int ind[] = {j, i};
    int prev_ind[] = {j - 1, i};
    y[ind] = y[prev_ind] + x[ind];
}
''',
'sample8_fwd',
)(x, x.shape[0], y, size=x.shape[1])

print(y)

Ausführungsergebnis:

[[  1.   2.   3.]
 [  5.   7.   9.]
 [ 12.  15.  18.]]

CUDA-Funktion

Sie können auch CUDA-Funktionen verwenden. Es ist jedoch nicht bekannt, wie viel Unterstützung es hat. Verwenden wir als Beispiel "atomicAdd".


x = xp.zeros((3, 3), dtype=np.float32)
t = xp.asarray([0, 1, 2], dtype=np.int32)

y = cuda.elementwise(
'S t',
'raw T x',
'int ind[] = {i, t}; atomicAdd(&x[ind], 1);',
'sample9_fwd',
)(t, x)

print(y)

Ausführungsergebnis:

[[ 1.  0.  0.]
 [ 0.  1.  0.]
 [ 0.  0.  1.]]

Schließlich

Wenn Sie "cuda.elementwise" verstehen, werden Sie Ihr Verständnis von Chainer vertiefen. cuda.elementwise und cuda.reduce werden häufig in Chainer verwendet. Wenn Sie mehr wissen möchten, sollten Sie sich auf sie beziehen.

Recommended Posts

Prozess auf GPU mit chainer.cuda.elementwise
Rückkehr nach dem Gaußschen Verfahren
Hinweise zur Verwendung von Alembic
Gaußsche Prozessregression mit GPy
Echtzeit-Personalschätzung (Lernen mit lokaler GPU)
Versuchen Sie es mit OpenCV unter Windows
[Django] Hinweise zur Verwendung der Django-Debug-Symbolleiste
Hinweise zur Optimierung mit Pytorch
Installieren Sie Chainer 1.6 (GPU) unter Windows 7.
Installieren Sie Caffe unter Ubuntu 14.04 (GPU)
Online-Übertragung mit Python
Versuchen Sie es mit Pillow auf iPython (Teil 1)
Versuchen Sie es mit Pillow auf iPython (Teil 2)
Verwenden Sie jupyter für eine GPU-Instanz in AWS
Versuchen Sie es mit ArUco mit Raspberry Pi
Verarbeitung von DB-Tabelleneinfügungen mit sqlalchemy
Hinweise zur Installation von Python mit PyEnv
Versuchen Sie es mit Pillow auf iPython (Teil 3).
Verwenden einer seriellen Konsole unter Ubuntu 20.04
Hinweise zur Verwendung von rstrip mit Python.
Installieren Sie Python unter CentOS mit Pyenv
Studie über die Miete in Tokio mit Python (3-3)
Hinweise zur Verwendung von matplotlib auf dem Server
[Für Anfänger] Prozessüberwachung mit cron
Führen Sie Yocto unter Ubuntu mit QEMU aus.
Installieren Sie Python unter CentOS mit pyenv
(Anfänger) Hinweise zur Verwendung von pyenv auf dem Mac