2010-09-14 18 views
1

Je suis en train d'écrire du code pour activer les réseaux de neurones sur CUDA, et je suis confronté à un problème. Je ne reçois pas la somme correcte des poids entrant dans un neurone donné.Coder un noyau CUDA dont plusieurs threads écrivent dans le même index?

Donc, voici le code du noyau, et je vais essayer de l'expliquer un peu plus clairement avec les variables.

__global__ void kernelSumWeights(float* sumArray, float* weightArray, int2* sourceTargetArray, int cLength) 
{ 
int nx = threadIdx.x + TILE_WIDTH*threadIdx.y; 
int index_in = (blockIdx.x + gridDim.x*blockIdx.y)*TILE_WIDTH*TILE_WIDTH + nx; 
if(index_in < cLength) 
{ 

    sumArray[sourceTargetArray[index_in].y] += fabs(weightArray[index_in]); 
    //__threadfence(); 
    __threadfence_block(); 

} 

} 

Tout d'abord, le nombre de connexions dans le réseau est cLength. Pour chaque connexion, il y a un neurone source et un neurone cible, ainsi qu'un poids pour cette connexion. SourceTargetArray contient cette information. Ainsi, l'index i de sourceTargetArray est l'index de neurones source de la connexion i, et l'index neuronal cible de la connexion i. Le weightArray contient les informations de poids (l'index i de weightArray correspond à la connexion i).

Comme vous pouvez le voir, SumArray est l'endroit où je stocke les sommes. Ainsi, le noyau incrémente le sumArray (à l'index de neurones cible de la connexion i) par la valeur absolue du poids de la connexion i. Intuitivement, pour toutes les connexions entrantes au neurone, additionnez tous les poids. C'est vraiment tout ce que j'essaie de faire avec ce noyau. Finalement, je vais normaliser les poids en utilisant cette somme.

Le problème est que c'est faux. Je l'ai fait en série, et la réponse est différente. La réponse diffère, généralement d'environ 12-15x (donc la bonne réponse sera 700.0 et ce que je reçois est quelque chose dans la gamme des années 50).

Vous pouvez voir que j'ai ajouté __threadfence() (et __threadfence_block() pour essayer de m'assurer que les écritures n'étaient pas effectuées en même temps par chaque thread). Je ne suis pas sûr si c'est le problème avec mon code. Je me suis assuré que le tableau de poids est identique à la version en série que j'ai testé, et que les informations source/cible sont identiques. Qu'est-ce que je fais mal?

EDIT: Pour référence, __threadfence() usaged est décrite dans le Guide de programmation CUDA v3.1 Annexe B.5 Fonctions de clôture de la mémoire

Répondre

3

Vous devez faire une réduction.

Somme des éléments affectés à chaque fil et placer le résultat dans un tableau, cache [threadsPerBlock] puis __Syncthreads

maintenant réduire les totaux sous résultant par addition de sous-totaux voisins successifs:

int cacheIndex = threadIdx.x; 
int i = blockDim.x/2; 
while (i != 0) 
{ 
    if (cacheIndex < i) 
     cache[cacheIndex] += cache[cacheIndex] + 1; 
     __syncthreads; 
     i /= 2; 
    } 
} 

Ci-dessous pont explique cette question en détail:

http://developer.download.nvidia.com/compute/cuda/1_1/Website/projects/reduction/doc/reduction.pdf

Exemple de code pour c'est son e:

http://www.nvidia.com/object/cuda_sample_data-parallel.html

Il est également très bien expliqué dans "CUDA par l'exemple" (ce qui est l'endroit où le fragment de code provient).

Il y a une grande mise en garde avec cette approche. Les ajouts ne se produiront pas dans le même ordre qu'ils le feraient avec le code série. L'ajout de flottants n'est pas commutatif, de sorte que des erreurs d'arrondi peuvent conduire à des résultats légèrement différents.

+0

Dans l'exemple de code ci-dessus, 'cache [cacheIndex] + 1' devrait être' cache [cacheIndex + 1] '. –

4

+= n'est pas atomistique => pas thread-safe. Utilisez atomicAdd.

Vous devriez également éviter d'écrire dans la même cellule mémoire. Le problème est que ces appels seront sérialisés, les threads feront la queue et attendront l'un l'autre. Si vous ne pouvez pas éviter cette opération, essayez de décomposer votre algorithme en deux phases: le calcul individuel et la fusion. La fusion parallèle peut être implémentée très efficacement.

+0

Je ne suis pas sûr de comprendre. atomicAdd est pour les entiers, j'utilise des flottants. De plus, lorsque vous parlez de «calcul individuel et fusion», à quoi correspond le calcul individuel dans mon scénario? La sommation? Je ne sais pas comment je pourrais éviter d'écrire dans la même cellule. – Paul

+0

@Paul Open B.11.1.1 du Guide de programmation de NVIDIA CUDA C Version 3.1 5/28/2010. Il existe une version 'float' de atomicAdd. Ok, dans votre cas, vous n'avez pas de calculs individuels. Le code que vous avez écrit n'est pas efficace. En savoir plus ici sur la façon de résumer efficacement: http://http.developer.nvidia.com/GPUGems3/gpugems3_ch39.html – Andrey

+0

AtomicAdd prend en charge les flottants mais uniquement sur les versions ultérieures de CUDA. Avant CUDA 2.0 seul AtomicAdd entier était supporté. –