J'essaie d'implémenter un noyau OpenCL général de multiplication de matrices, qui soit conforme aux normes suivantes C = α*A*B + β*C
.
Le noyau
J'ai fait quelques recherches en ligne et j'ai décidé d'utiliser un noyau modifié de ce site web comme point de départ. La principale modification que j'ai apportée est que l'allocation de la mémoire locale comme espace de travail est désormais dynamique. Voici le noyau que j'ai écrit :
__kernel
void clkernel_gemm(const uint M, const uint N, const uint K, const float alpha,
__global const float* A, __global const float* B, const float beta,
__global float* C, __local float* Asub, __local float* Bsub) {
const uint row = get_local_id(0);
const uint col = get_local_id(1);
const uint TS = get_local_size(0); // Tile size
const uint globalRow = TS * get_group_id(0) + row; // Row ID of C (0..M)
const uint globalCol = TS * get_group_id(1) + col; // Row ID of C (0..N)
// Initialise the accumulation register
float acc = 0.0f;
// Loop over all tiles
const int numtiles = K / TS;
for (int t = 0; t < numtiles; t++) {
const int tiledRow = TS * t + row;
const int tiledCol = TS * t + col;
Asub[col * TS + row] = A[tiledCol * M + globalRow];
Bsub[col * TS + row] = B[globalCol * K + tiledRow];
barrier(CLK_LOCAL_MEM_FENCE);
for(int k = 0; k < TS; k++) {
acc += Asub[k * TS + row] * Bsub[col * TS + k] * alpha;
}
barrier(CLK_LOCAL_MEM_FENCE);
}
C[globalCol * M + globalRow] = fma(beta, C[globalCol * M + globalRow], acc);
}
La taille des carreaux (TS) est désormais une valeur définie dans le code d'appel, qui se présente comme suit :
// A, B and C are 2D matrices, their cl::Buffers have already been set up
// and values appropriately set.
kernel.setArg(0, (cl_int)nrowA);
kernel.setArg(1, (cl_int)ncolB);
kernel.setArg(2, (cl_int)ncolA);
kernel.setArg(3, alpha);
kernel.setArg(4, A_buffer);
kernel.setArg(5, B_buffer);
kernel.setArg(6, beta);
kernel.setArg(7, C_buffer);
kernel.setArg(8, cl::Local(sizeof(float) * nrowA * ncolB));
kernel.setArg(9, cl::Local(sizeof(float) * nrowA * ncolB));
cl::NDRange global(nrowA, ncolB);
cl::NDRange local(nrowA, ncolB);
status = cmdq.enqueueNDRangeKernel(kernel, cl::NDRange(0), global, local);
Le problème
Le problème que je rencontre est que les tests unitaires (écrits avec gtest de Google) que j'ai écrits échouent de manière aléatoire, mais seulement pour ce noyau particulier. (J'ai 20 autres noyaux dans les mêmes .cl
qui réussissent les tests 100% du temps)
J'ai un test qui multiplie une matrice flottante 1x4 {0.0, 1.0, 2.0, 3.0}
avec une version transposée de lui-même {{0.0}, {1.0}, {2.0}, {3.0}}
. Le résultat attendu est {14.0}
.
Cependant, je ne parviens à obtenir ce résultat correct que dans 75 % des cas.
Parfois, j'obtiens 23.0 (GTX 970), 17.01 (GTX 750) ou tout simplement -nan
et 0,0 (pour les trois appareils). Ce qui est curieux, c'est que les résultats incorrects respectifs semblent être propres aux appareils ; je n'arrive pas, par exemple, à obtenir 23.0 sur le CPU Intel ou la GTX 750.
Je suis déconcerté car si j'ai commis une erreur algorithmique ou mathématique, cette erreur devrait être cohérente ; au lieu de cela, je n'obtiens des résultats incorrects que de manière aléatoire.
Qu'est-ce que je fais de travers ?
Ce que j'ai essayé
- J'ai vérifié que les données introduites dans les noyaux sont correctes.
- J'ai essayé d'initialiser les deux
__local
à 0.0, mais cela rend tous les résultats erronés (mais franchement, je ne sais pas vraiment comment l'initialiser correctement). - J'ai écrit un programme de test qui n'exécute que ce noyau afin d'exclure toute condition de course interagissant avec le reste de mon programme, mais le bogue se produit toujours.
Autres points à noter
- J'utilise le wrapper C++ récupéré directement à partir de la page Github .
- Pour utiliser le wrapper, j'ai défini
CL_HPP_MINIMUM_OPENCL_VERSION 120
yCL_HPP_TARGET_OPENCL_VERSION 120
. - Je compile les noyaux avec l'option
-cl-std=CL1.2
drapeau. - Tous
cl::Buffer
sont créés à l'aide de seulement lesCL_MEM_READ_WRITE
drapeau. - Je teste cela sur Ubuntu 16.04, Ubuntu 14.04 et Debian 8.
- J'ai testé cette méthode sur des processeurs Intel avec la fonction Intel OpenCL Runtime 16.1 pour Ubuntu installé. Le moteur d'exécution indique qu'il prend en charge jusqu'à OpenCL 1.2.
- Je l'ai testé sur les Nvidia GTX 760 et 970. Nvidia ne supporte que l'OpenCL 1.2.
- Les trois plates-formes présentent le même problème à une fréquence variable.