Cómo aprender cuda en un mes…

…y otros cuentos

Cómo añadir un paso de compilación personalizada con NSIGHT para CUDA

dejar un comentario »

Problema común: he instalado NSIGHT y he creado un nuevo proyecto con un par de ficheros .cu que quiero compilar con CUDA pero no encuentro la manera de hacerlo.

La solución es fácil, lo que hay que hacer es indicarle que se debe compilar con un paso de compilación personalizada. El problema es que en la lista no aparece este paso si no lo activamos en el proyecto. Para eso hay que ir a Proyecto/Personalizaciones de compilación.

Ahí debemos marcar la casilla de la versión de CUDA que queramos activar, en mi caso CUDA 3.2 (.targets, .props). Siguiente paso es clic derecho en el fichero .cu y Propiedades. Ahí marcamos en General/Tipo de elemento la opción CUDA C/C++.

 Con esto indicamos que ese fichero se compilará con CUDA 3.2, además ahora en las propiedades del proyecto aparecerá un desplegable CUDA C/C++ con algunas opciones avanzadas que es mejor no tocar.

Escrito por cudagpu

22 junio 2011 a 11:57

Escrito en CUDA

Comunicación entre QThread con slot y signal

dejar un comentario »

Es habitual crear un hilo QThread que haga un proceso en segundo plano y vaya avisando de su estado. En mi caso tengo un QTextEdit que me sirve de log para escribir cualquier cosa. El problema es que QT tiene mecanismos de seguridad que impiden a un QThread comunicarse con un QObject creado en otro hilo, como es la interfaz.

Para eso se aprovecha el sistema de signals y slots que permiten la comunicación.

Emisor: clase QThread

Hay que definir una signal en la clase emisora (que será un hilo QThread), el código queda así:

class FileAccess : public QObject, public QThread{
 Q_OBJECT
 public:

  FileAccess(Logic *myLogic){}
  ~FileAccess(){}
  void run(){
    emit writeState("Running thread");
  }
 signals:
  void writeState(QString q);
};

Como se puede apreciar, hemos creado nuestra propia señal de nombre writeState que recibe una QString y se emite nada más entrar en la ejecución del hilo.

Conexión con el receptor

Esta señal debe conectarse con el receptor, en mi caso un QTextEdit, para eso se utiliza el método connect():

fa = new FileAccess(); //nuevo hilo
QObject::connect(fa, SIGNAL(writeState(QString)), myTextEdit, SLOT(append(QString)), Qt::QueuedConnection);
fa->start(); //lanzar hilo
fa->wait(); //esperar a que el hilo termine o hacer nuestras tareas

Destaco la segunda línea: conectamos la señal writeState del objeto fa con el slot append del objeto myTextEdit.

Escrito por cudagpu

6 junio 2011 a 11:09

Escrito en Qt

Evitar que se haga un salto de línea en Fortran

dejar un comentario »

Desde tierras muy lejanas dejo aquí esta información sobre cómo evitar que un WRITE (VWRITE) haga un salto de línea y poder concatenar en una misma línea varios formatos de Fortran (Fortran Format).

Basta con poner un $ como último carácter del formato Fortran:

*CFOPEN,'elements','txt',' '
 *VWRITE,e1(1,1), e2(1,1), e3(1,1), e4(1,1), e5(1,1),[...]
 (19(' ',F10.0),$)
 *VWRITE, e20(1,1)
 (1(' ',F10.0),' ')

Fuente

Escrito por cudagpu

3 junio 2011 a 11:41

Escrito en Otros cuentos

Empieza el proyecto BRUMA

dejar un comentario »

Llega un momento en la vida de todo desarrollador en que se da cuenta de que todo lo que había hecho hasta ese momento no sirve para nada. El proyecto ha terminado, ¡viva el proyecto!

Esta vez probablemente no va a haber desarrollo en CUDA, por lo que dejaré de aprender esta tecnología. Aunque lo hecho hasta ahora me servirá para un futuro, no he llegado nunca a ahondar y profundizar en este tipo de programación tanto como me gustaría. Quién sabe si más adelante me serán útiles estos conocimientos, dejaremos pasar el tiempo a ver qué pasa.

Este nuevo proyecto, codename BRUMA (BReast Ultrasound and Magnetic resonance Alignment) va a permitir deformar una resonancia magnética según los datos que se reciben de una cámara TOF. La deformación de la resonancia magnética la podrá ver el médico en el momento de la operación y se le mostrará en la posición y orientación en la que maneje un ecógrafo.

BRUMA tiene tres grandes retos:

  1. Conseguir deformar una textura 3D de forma que los niveles de gris se interpolen correctamente y tener así una deformación de la resonancia acorde con la deformación a la que está sometida en realidad
  2. Captación del mapa de profunidad que viene de la cámara TOF y registro del mapa con la resonancia para su posterior deformación
  3. Tracking del ecógrafo y muestra de la resonancia en la posición y orientación del mismo

Y estas van a ser las tareas a las que me voy a dedicar ahora. Cambio y corto.

Escrito por cudagpu

24 marzo 2011 a 15:02

Escrito en Otros cuentos

Crear una ventana de carga en un hilo y comunicarla con un Widget de QT

con un comentario

Desde hace tiempo tengo un botón que lanza un proceso externo (concretamente ANSYS) y normalmente tarda varios minutos. Esto provoca que la interfaz se congele hasta que el proceso termina, con la consiguiente falta de información, bloqueo de ventanas…

Crear un hilo con QT

Como parece que ahora la aplicación tiene que servir para otras personas, era hora de hacer eso bien y tener una pantallita típica de “Running…” con su botón de Abort y todo esto sin bloquear la interfaz, claro. Para eso tenemos que usar hilos y QT tiene sus propios hilos: QThread.

La creación de una clase que sea hilo simplemente debe heredar de QThread, aquí hay una muestra básica:

class MiHilo: public QThread{
  private:
  //mis variables
  public:
  void run(){ //método que se ejecutará al hacer start
    //hacer cosas
  }
};

int main(){
  MiHilo th;
  th.start(); //lanza el hilo
  th.wait(); //espera a que el hilo termine
}

Aunque lo mejor es leerse la documentación de QThread para ver todos los métodos.

Mostrar una ventana de carga

Ahora lo interesante es tener una ventanita que indique que el hilo está haciendo cosas y que bloquee la ejecución del programa principal hasta que se cierre esa ventana, ya sea por acción del usuario o porque el proceso lanzado ha terminado. Para sacar una ventana modal (bloqueante) lo más sencillo es lo siguiente:

th.start();

loadingBox.setText("Process is running...");
loadingBox.setWindowTitle("Please wait");
loadingBox.setWindowModality(Qt::ApplicationModal);
loadingBox.setStandardButtons(QMessageBox::Abort);
loadingBox.setIcon(QMessageBox::Warning);

int r=loadingBox.exec();

th.wait();

Esta ventana tiene además un botón de Abort con el que podemos hacer lo que queramos al mirar el valor de la variable r. La ventana, al ser modal, no permite que el programa principal ejecute el wait() hasta que se haya cerrado, en caso contrario, el programa principal seguiría su ejecución de forma independiente.

Cerrar la ventana con un evento

Siguiente paso: que la ventana se cierre cuando el proceso termine. Aunque pueda parecer sencillo, es más complicado de lo que parece puesto que QT impide enviar eventos directamente entre hilos distintos. Por lo tanto, la clase MiHilo no podrá mandar un close() directamente a loadingBox. Según parece se puede montar un sistema con signals y slots que permitan mandar una señal de cierre, pero no lo he conseguido…

Lo que sí he conseguido es enviar un evento desde un hilo distinto al de la aplicación principal mediante postEvent. Esta función permite mandar un evento a la cola de eventos del objeto receptor definido, entonces, al finalizar la ejecución del programa que está lanzandose en el hilo, se pasa lo siguiente:

QApplication::postEvent(&padre->loadingBox,new QCloseEvent()); //padre es la clase que contiene el objeto loadingBox y que el hilo debe conocer

Y, aunque hay un new, el evento se borra automáticamente al procesarse, si lo borramos después con un delete tendremos errores difíciles de entender.

Todo esto nos ha permitido lanzar un proceso en un hilo, mostrar una ventana modal que interrumpa el programa principal pero sin bloquear la interfaz y cerrarla al terminar la ejecución del proceso.

Escrito por cudagpu

28 febrero 2011 a 10:38

Escrito en Qt

Mezclando BOOST, uBLAS y CLAPACK para resolver sistemas de ecuaciones

dejar un comentario »

Si bien anteriormente me entusiasmaron Newmat y MTL4, ninguna de ellas era capaz de resolver lo que necesitaba. La primera no resuelve sistemas y la segunda tardaba muchsímimo y hasta petaba en grandes sistemas (22k ecuaciones). Pero seguí investigando para utilizar las famosas librerías de resolución LAPACK o suery su versión para C: CLAPACK. Sin embargo, esta biblioteca no permite el almacenamiento eficiente de matrices dispersas/simétricas y para eso existe otra biblioteca llamada BLAS y, concretamente, la que viene ya incluida en BOOST: uBLAS.

Este post relata cómo, utilizando el almacenamiento de matrices de uBLAS y un binding sobre BOOST de CLAPACK y el propio CLAPACK, se resuelven sistemas de ecuaciones extremadamente grandes en poco tiempo.

Lo primero es tener instalado y preparado BOOST, con esto ya tenemos uBLAS a punto. Seguidamente podemos probar que esté correcto con un programita simple como este:

#include "stdafx.h"
#include <boost/numeric/ublas/matrix_sparse.hpp>
#include <boost/numeric/ublas/lu.hpp>
#include <boost/numeric/ublas/io.hpp>

int main(){

 using namespace boost::numeric::ublas;
 mapped_matrix<double> m (5, 5, 3 * 3);
 for (unsigned i = 0; i < m.size1 (); ++ i)
  for (unsigned j = 0; j < m.size2 (); ++ j)
   m (i, j) = 5 * i + j*5.2;

 m(3,3)=10;

 std::cout << m << std::endl;

 matrix<double> inv(5,5);

 permutation_matrix<std::size_t> pm(m.size1());
 lu_factorize(m,pm);
 inv.assign(identity_matrix<double>(m.size1()));
 lu_substitute(m,pm,inv);

 std::cout << inv << std::endl;

 system("PAUSE");
 return 0;
}

Lo que hace es rellenar una matriz y luego invertirla. Es la forma más rápida de invertir una matriz utilizando uBLAS que he encontrado.

Matrices dispersas

Existen tres maneras de guardar matrices dispersas con uBLAS, yo he utilizado la compressed_matrix, me pareció la más eficiente. Aquí un ejemplo:

boost::numeric::ublas::compressed_matrix<float> miMatrizDispersa(ancho,alto); 

El acceso para lectura y escritura es con miMatrizDispersa(i,j), todo muy fácil, rápido y sencillo.

Añadir el binding de LAPACK

Por sí misma, la biblioteca uBLAS no resuelve sistemas, podría utilizarse para invertir una matriz y multiplicarla por un vector, resolviendo así el sistema, pero esto es extremadamente ineficiente y es muy buena idea utilizar CLAPACK. Pero para ello tenemos que añadir el binding uBLAS-LAPACK. Esto es muy sencillo:

  • Colocarse en el directorio donde tengamos BOOST, típicamente: C:/Archivos de programa/boost/boost_1_44/boost/numeric/
  • Crear la carpeta bindings/
  • Utilizar un cliente de SubVersion para bajar todo el árbol de:

svn co http://svn.boost.org/svn/boost/sandbox/numeric_bindings-v1/boost/numeric/bindings

  • Colocarse en el directorio: C:/Archivos de programa/boost/boost_1_44/libs/numeric/
  • Crear la carpeta bindings/
  • Utilizar un cliente de SubVersion para bajar todo el árbol de:

svn co http://svn.boost.org/svn/boost/sandbox/numeric_bindings-v1/libs/numeric/bindings

Tendremos ya los bindings instalados y fácilmente comprobable. Pero antes…

Instalar y vincular CLAPACK

Debemos bajar CLAPACK para Visual Studio y colocarlo donde queramos, por ejemplo en C:/CLAPACK-3.1.1-VisualStudio. Importante bajarse las cosas ya compiladas a ser posible. Seguidamente añadimos en Visual Studio los directorios include y lib/Win32 como directorios adicionales de inclusión y biblioteca respectivamente.

Luego en el proyecto tenemos que añadir las bibliotecas adicionales: clapackd.lib y BLASd.lib en el vinculador.

Resolver un sistema de ecuaciones

Nos falta una cosa importante para que toda esta mezcla funcione, hay que añadir lo siguiente al principio del fichero donde se incluyan las cosas importantes:

#define BOOST_NUMERIC_BINDINGS_USE_CLAPACK //¡muy importante para que todo funcione!

#include <boost/numeric/bindings/traits/ublas_matrix.hpp>
#include <boost/numeric/bindings/lapack/gesv.hpp>

#include <boost/numeric/ublas/matrix_sparse.hpp>
#include <boost/numeric/ublas/vector.hpp>

#include <boost/numeric/ublas/io.hpp>

Con este define y los includes que necesitemos ya podemos rellenar la matriz y resolver así:

ublas::matrix<float, ublas::column_major> A(ancho,alto); //importante el column_major para compatibilidad con LAPACK
ublas::matrix<float, ublas::column_major> b(alto,1); //ponemos una matriz alto x 1 para hacerlo más sencillo, pero seguramente con un vector
//rellenar A y b
[...]
bindings::lapack::gesv(A,b); //la solución se  encontrará en b

Así hemos conseguido finalmente resolver el sistema Ax=b y sorprenderá la velocidad con que lo hace CLAPACK, el cuello de botella de aquí es la inserción de datos en una matriz dispersa.

Escrito por cudagpu

5 octubre 2010 a 11:41

Escrito en Otros cuentos

Resolución de sistemas de ecuaciones con MTL4

con un comentario

Si bien hablé en otro post sobre la biblioteca Newmat, he seguido buscando alternativas pues Newmat no maneja matrices dispersas. He encontrado tres bibliotecas interesantes: SparseLib++, CSparse y MTL4.

De ellas decidí probar MTL4 para guardar una matriz de 1.400 millones de elementos y resolver un sistema también muy grande. He tenido bastantes problemillas al usarlo con Visual Studio 2005, pero al final parece que funciona.

Primeramente, MTL2 lo he descartado por obsoleto y he tirado directamente a MTL4, bajando todo el código del servidor subversion que proporcionan. Las instrucciones están muy bien detalladas y básicamente vienen a decir:

#include <boost/numeric/mtl/mtl.hpp>
#include <boost/numeric/itl/itl.hpp>

Y ahora dejo unas muestras de código para hacer matrices, invertirlas…

Matrices densas

dense2D<float>   A(ancho, alto);
A[0][4]=55; //ponemos a 55 el valor de la posición 0,4
float v=A[0][4]; //sacamos el valor 0,4 para guardarlo en v
std::cout << "A is \n" << A; //imprimir una matriz es muy fácil

Matrices dispersas

Éstas son algo distintas en su manejo, no se pueden rellenar de la misma manera, necesitan un inserter.

compressed2D<double>   B(ancho, alto);
if(true){
  matrix::inserter<compressed2D<double>> ins(B); //crear un inserter sobre B
  ins[0][4] << 55;  //ponemos a 55 el valor de la posición 0,4
}
float v=B[0][4]; //la extracción es igual
std::cout << "B is \n" << B;

Nótese el if(true), eso lo he hecho porque la creación del inserter debe ser dentro de un bloque. Esto es así porque hasta que el inserter no se destruya, la matriz no podrá ser accesible y dará una excepción al intentar hacer B[0][4] o cualquier otro acceso. Se puede meter dentro de un bucle o función para que el inserter se destruya una vez hecho su trabajo.

Resolver un sistema

MTL sólo permite resolver sistemas con matrices densas, una pena. Se deben seguir estos pasos:

dense_vector<float> solucion(tam);
dense2D<float> A(tam,tam);
dense_vector<float> b(tam);

solucion=lu_solve(A,b);

Obviamente, teniendo en cuenta que todos los tamaños permitan la resolución del sistema.

Escrito por cudagpu

1 octubre 2010 a 12:46

Escrito en Otros cuentos

Newmat, buena biblioteca para el manejo de matrices en C++

con 4 comentarios

Puede que no sea la mejor, pero he encontrado una gran biblioteca para el manejo de matrices en C++. Todo ha venido porque hasta ahora estaba usando matrices simples 2D y nos ha surgido el problema de que uno de los modelos FEM tenía tantos nodos que una de las matrices que teníamos que usar para simular una deformación llegaba a ocupar 8GB en memoria, lo cual, obviamente, mata la aplicación.

Me habían mencionado la biblioteca LAPACK, pero no he visto que permita almacenar matrices dispersas o simétricas (como es mi caso), aunque sus funciones sí que son capaces de manejar este tipo de matrices, asumo que ignorando aquellas posiciones simétricas o algo así.

He seguido buscando y me he topado con Newmat:

Esta biblioteca C++ está orientada a científicos e ingenieros que necesitan manipular una serie de tipos de matrices usando operaciones estándar. Se ha hecho énfasis en el tipo de operaciones necesarias y cálculos estadísticos como mínimos cuadrados, resolución de sistemas de ecuaciones lineales y eigenvalores.

Por lo que he bajado el código y he empezado a trastear con ello, ha compilado perfectamente en Visual Studio 2008. Crear un nuevo proyecto y añadir los .cpp y .h que se indican en estas instrucciones. Me ha sorprendido que todo funcionara a la primera, en estos casos siempre te falta tal o cual dependencia…

Además, se ejecuta un ejemplo de uso, lo que viene muy bien para comprobar el funcionamiento y ver algo de código siempre es la mejor manera de conocer una herramienta. Hasta me he atrevido a hacer una matriz simétrica e invertirla para ver el funcionamiento, no puede ser más fácil:


SymmetricMatrix SM(2);

SM.Row(1) << 11;
SM.Row(2) << -1 << 22;
SM=SM.i();

cout << "MI MATRIZ PREFERIDA ("<<SM.Storage()<<")\n\n"<<setw(10) << setprecision(5) <<SM<<endl;

El funcionamiento está claro, se crea una matriz simétrica de 2×2 y se rellena. Al ser simétrica, en la fila 1 basta con rellenar el primer valor. Además, con SM.Storage() se puede ver cuántas posiciones en memoria se han reservado, y lo hace optimizando la memoria puesto que para la matriz simétrica va a dar 3 y no 4. Si la imprimiéramos la matriz tendría este aspecto:

11 -1
-1 22

Al invertirla e imprimirla con el cout el resultado es:

 0.09129 0.00415
0.00415 0.04564

¡Fácil y sencillo! Y ahora a darse una vuelta por la documentación y ver todo lo que esta biblioteca permite hacer.

Escrito por cudagpu

24 septiembre 2010 a 12:07

Escrito en Otros cuentos

NSIGHT para depurar CUDA en GPU

dejar un comentario »

NVIDIA ha publicado (por fin) una herramienta que permitirá depurar CUDA en tiempo real. La depuración de CUDA sobre GPU es muy delicada y no es posible ver el estado de las variables, colocar puntos de ruptura o conocer cómo está funcionando el kernel lanzado.

Pero ahora NVIDIA lanza NSIGHT, una herramienta con la que podremos hacer todas estas tareas y además se integra con Visual Studio sin problemas. Sin embargo tiene algunas peculiaridades que he sufrido.

Sólo funciona con Visual Studio 2008 SP1, aunque dicen que pronto será compatible con VS2010, veremos si vale la pena este nuevo entorno, pero tiene muy buena pinta.

Es necesario (a menos que tengas una configuración de dos GPU en SLI), tener dos máquinas, una de ellas con VS2008 SP1 y NSIGHT instalado y otra de ellas con NSIGHT Monitor instalado y GPU G92 o superior, la segunda será la máquina donde se lanzarán las ejecuciones.

Ambas máquinas deben tener Windows Vista/7 (bye bye XP) y, por supuesto, estar conectadas entre sí. Es MUY RECOMENDABLE actualizar los controladores gráficos de NVIDIA a la última versión.

Estoy en estos momentos intentando lanzar mi primera depuración, en otro post haré un breve tutorial de instalación y manejo.

Escrito por cudagpu

28 julio 2010 a 10:50

Escrito en CUDA

¿Por qué un unsigned char no sirve para un bucle de 256 iteraciones?

dejar un comentario »

Necesito iterar sobre una matriz de 256x256x256 elementos, y para ello tengo 3 bucles anidados, de 0 a 256, lo típico para recorrerlo todo. Muchos pensareis, para los índices puede utilizarse un unsigned char ya que sus valores están entre 0 y 255, perfecto, llega justo: ¡pues no!

Y lo peor es que lo tenía en un bucle de un kernel CUDA y lo que hace es petar la gráfica y Windows, obligándote a reiniciar la máquina…

La explicación es sencilla, el bucle es tal que así:

for(unsigned char i=0; i<256; i++)

Por lo tanto la variable i empieza en 0, 1, 2… 255. Y ahí debería ser la última iteración, pero, y aquí viene lo interesante, cuando se haga i++ la variable sufre un overflow y vuelve a cero. Con lo que el bucle vuelve a empezar y nunca termina.

Así pues, niños, los unsigned char sirven únicamente para bucles de hasta 255 iteraciones, para todo lo demás: unsigned short.

Escrito por cudagpu

19 julio 2010 a 12:06

Escrito en Otros cuentos

Seguir

Get every new post delivered to your Inbox.