[PYTHON] Traiter sur GPU en utilisant chainer.cuda.elementwise

introduction

Lors de la lecture du code des fonctions Chainer, vous pouvez voir des appels à cuda.elementwise ou cuda.reduce. Ce sont des méthodes pour exécuter votre propre traitement sur le GPU. On peut dire que c'est une méthode indispensable pour implémenter la fonction Chainer, et il semble nécessaire de devenir un Chainer intermédiaire, alors je l'ai étudiée. Cet article traite de «cuda.elementwise» et ne traite pas de «cuda.reduce».

Une description de «cuda.elementwise» peut être trouvée ci-dessous. http://docs.chainer.org/en/stable/cupy-reference/kernel.html En outre, Slide Share a un commentaire de M. Okuda de Preferred Networks. http://www.slideshare.net/ryokuta/cupy

Environnement confirmé

Que fait cuda.elementwise?

cuda.elementwise définit le noyau CUDA. Le noyau CUDA est un programme qui s'exécute sur un GPU prenant en charge CUDA. L'appel de cuda.elementwise donne à la valeur de retour une fonction d'invocation du noyau de fonction pour exécuter le noyau CUDA, et l'appel de la fonction d'invocation du noyau exécute le noyau CUDA sur le GPU.

La substance de la fonction d'appel du noyau est l'objet ElementwiseKernel, qui peut être appelé.

Premier exemple de code

Comme premier exemple, nous incrémenterons tous les éléments d'un tableau donné. Tout d'abord, importez les modules requis comme indiqué ci-dessous et définissez «xp» comme une référence à «cuda.cupy». L'exemple de code suivant suppose que vous exécutez le code suivant:

import numpy as np
import chainer
from chainer import cuda

xp = cuda.cupy

Ensuite, utilisez cuda.elementwise pour incrémenter les éléments du tableau.

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)

La sortie ressemble à ceci: Vous pouvez voir que tous les éléments sont incrémentés. Il faut un certain temps pour sortir après après la sortie d'avant, mais il semble que la compilation soit faite par nvcc dans les coulisses.

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

Explication de l'exemple de code

«cuda.elementwise» est utilisé en deux étapes comme suit. Cependant, dans le code réel, il arrive souvent que deux processus soient décrits ensemble, comme cuda.elementwise (...) (x).

L'explication de l'argument / valeur de retour de cuda.elementwise n'est pas cette méthode, mais [Document of cupy.ElementwiseKernel](http://docs.chainer.org/en/stable/cupy-reference/kernel.html# Il est décrit dans cupy.ElementwiseKernel). cuda.elementwise appelle en interne cupy.ElementwiseKernel et les deux arguments sont identiques (sauf que name est requis dans cuda.elementwise). Les arguments suivants sont requis pour cuda.elementwise. Il existe d'autres arguments optionnels, mais je ne les ai pas encore entièrement compris, donc je ne les expliquerai pas.

in_params

Spécifie la chaîne qui déclare les arguments d'entrée. L'argument nécessite un type et un nom d'argument. Si la chaîne de type est un caractère, ce sera ** type placeholder **. Le type représenté par type placeholder est le type de la variable transmise lors de l'exécution de la fonction d'appel du noyau. Ceci est utile lorsque vous souhaitez déclarer des variables du même type.

Dans l'exemple de code, in_params était comme suit.

'T x',

Ce qui suit est exprimé par cette chaîne de caractères.

out_params

Spécifie la chaîne qui déclare les arguments de sortie. Comme in_params, l'argument nécessite un type et un nom d'argument.

Dans l'exemple de code, out_params était comme suit.

'T y',

Ce qui suit est exprimé par cette chaîne de caractères.

operation

Spécifiez la chaîne de caractères qui définit le processus à exécuter. Dans l'exemple de code, il s'agissait d'un processus de substitution de «x + 1» par «y».

'y = x + 1;',

name

Le nom du processus. En regardant l'implémentation du module sous chainer.functions, il s'agit de "nom_fonction_fwd" pour le traitement en avant et "nom_fonction_bwd" pour le traitement en amont.

Exécuter la fonction d'appel du noyau

L'exécution de la fonction d'appel du noyau exécute le noyau CUDA défini. Passez la variable correspondant à in_params comme argument au moment de l'exécution. La variable correspondant à out_params peut être omise, mais elle peut aussi être explicitement passée en la spécifiant après la variable correspondant à in_params. La valeur de retour est l'argument spécifié dans out_params. Si plusieurs arguments sont spécifiés pour out_params, la valeur de retour sera ces tuples.

Transmettre les valeurs à out_params

Passons également une valeur à out_params. Passez simplement la valeur correspondant à out_params à l'argument de la fonction d'invocation du noyau.

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)

Le résultat de l'exécution est le suivant, et x est ajouté au y d'origine.

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

Broadcasting

Le tableau sera diffusé automatiquement.

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)

Résultat de l'exécution:

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

Si la taille du tableau ne correspond pas et que vous ne pouvez pas diffuser, une erreur se produit.

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)

Résultat de l'exécution:

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

Vous souhaitez souvent spécifier un index lorsque vous travaillez avec un tableau. Vous pouvez spécifier l'index en procédant comme suit.

Voici un exemple qui inverse les éléments d'un tableau.

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)

Le résultat de l'exécution est le suivant.

[ 4.  3.  2.  1.]

Vous pouvez considérer le code ci-dessus comme exécutant le code suivant à l'aide de Numpy sur un GPU.

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]

Notez que vous devez passer y à la fonction d'invocation du noyau. Si vous ne passez pas y comme indiqué ci-dessous, vous obtiendrez l'erreur Erreur de valeur: la taille de la boucle est indécise. Cela semble se produire car vous ne pouvez pas dimensionner l'index avec uniquement des arguments bruts.

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)

Indexation un peu plus compliquée

Pensez à obtenir x [t [i]] (i = 0, 1, 2, ...) lorsque x est un tableau à deux dimensions et t est un tableau à une dimension. .. Cela peut être écrit comme suit.

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)

Résultat de l'exécution:

[ 1.  6.  8.]

ʻInt ind [] = {i, t}; `génère un index indiquant [(0, t [0]), (1, t [1]), (2, t [2])] ..

pour boucle

Vous pouvez utiliser la syntaxe C (nvcc? Pour être exact) telle que for et while. À titre d'exemple, calculez la valeur cumulée pour chaque colonne de x. Assurez-vous que «y [i, j]» est cumulatif de «x [0, j]» à «x [i, j]».

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)

Résultat de l'exécution:

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

Fonction CUDA

Vous pouvez également utiliser les fonctions CUDA. Cependant, on ne sait pas combien de soutien il a. Utilisons ʻajout atomique` comme exemple.


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)

Résultat de l'exécution:

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

finalement

En comprenant cuda.elementwise, je pense que vous approfondirez votre compréhension de Chainer. cuda.elementwise et cuda.reduce sont couramment utilisés dans Chainer, et si vous voulez en savoir plus, vous devriez vous y référer.

Recommended Posts

Traiter sur GPU en utilisant chainer.cuda.elementwise
Retour en utilisant le processus gaussien
Remarques sur l'utilisation d'Alembic
Régression de processus gaussien utilisant GPy
estimation personnelle en temps réel (apprentissage en utilisant le GPU localement)
Essayez d'utiliser OpenCV sur Windows
[Django] Remarques sur l'utilisation de django-debug-toolbar
Remarques sur l'optimisation à l'aide de Pytorch
Installez Chainer 1.6 (GPU) sur Windows 7.
Installez Caffe sur Ubuntu 14.04 (GPU)
Diffusion sur LINE en utilisant python
Essayez d'utiliser Pillow sur iPython (partie 1)
Essayez d'utiliser Pillow sur iPython (partie 2)
Utiliser jupyter sur une instance GPU sur AWS
Essayez d'utiliser ArUco avec Raspberry Pi
Traitement des insertions de table DB à l'aide de sqlalchemy
Remarques sur l'installation de Python à l'aide de PyEnv
Essayez d'utiliser Pillow sur iPython (partie 3)
Utilisation d'une console série sur Ubuntu 20.04
Notes sur l'utilisation de rstrip avec python.
Installer Python sur CentOS à l'aide de Pyenv
Étude sur Tokyo Rent en utilisant Python (3-3)
Remarques sur l'utilisation de matplotlib sur le serveur
[Pour les débutants] Surveillance des processus à l'aide de cron
Exécutez Yocto sur Ubuntu en utilisant QEMU.
Installez Python sur CentOS en utilisant pyenv
(Débutant) Remarques sur l'utilisation de pyenv sur Mac