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
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é.
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.]]
«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)
.
cuda.elementwise
pour générer une fonction d'invocation du noyauL'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.
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.
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.
raw
à la variable à laquelle vous voulez accéder en spécifiant l'index_ind.size ()
représente le nombre d'indexVoici 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)
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])] ..
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.]]
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.]]
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