UNIVERSIDAD DE EXTREMADURA Escuela Politécnica MÁSTER UNIVERSITARIO EN INICIACIÓN A LA INVESTIGACIÓN EN TECNOLOGÍA (MUIT) ESPECIALIDAD EN: TECNOLOGÍAS INFORMÁTICAS Y DE LAS COMUNICACIONES (TINC) Trabajo Fin de Máster MUIT-TINC Implementación de algoritmos de análisis hiperespectral en tarjetas gráficas programables (GPUs) Sergio Sánchez Martínez Julio 2010 UNIVERSIDAD DE EXTREMADURA Escuela Politécnica MÁSTER UNIVERSITARIO EN INICIACIÓN A LA INVESTIGACIÓN EN TECNOLOGÍA (MUIT) ESPECIALIDAD EN: TECNOLOGÍAS INFORMÁTICAS Y DE LAS COMUNICACIONES (TINC) Trabajo Fin de Máster MUIT-TINC Implementación de algoritmos de análisis hiperespectral en tarjetas gráficas programables (GPUs) Autor: Sergio Sánchez Martínez Fdo: Director: Antonio J. Plaza Miguel Fdo: Tribunal Calificador Presidente: Juan Manuel Sánchez Pérez Fdo: Secretario: Javier Plaza Miguel Fdo.: Vocal: Juan Antonio Gómez Pulido Fdo.: CALIFICACIÓN: FECHA: Implementación de algoritmos de análisis hiperespectral en tarjetas gráficas programables (GPUs) Resumen El análisis de imágenes hiperespectrales en observación remota de la tierra constituye una línea de investigación muy activa, con numerosas contribuciones en la literatura científica reciente. Debido a la resolución espacial disponible en sensores de observación remota de la tierra, la mayor parte de los píxels registrados por el sensor constituyen una mezcla de diferentes substancias puras a nivel sub-píxel. Para solucionar este problema, una de las técnicas más ampliamente utilizadas es el desmezclado espectral, que comprende dos etapas: 1) extracción de firmas espectrales puras (endmembers), y 2) estimación de la abundancia de dichos endmembers a nivel sub-píxel. Ambas etapas son muy costosas desde el punto de vista computacional, lo cual supone un importante inconveniente en aplicaciones que requieren una respuesta en tiempo casi real, tales como monitorización y seguimiento de incendios, prevención y seguimiento de desastres naturales, vertidos químicos y otros tipos de contaminación ambiental, etc. En el presente trabajo, hemos desarrollado una implementación paralela del algoritmo pixel purity index (PPI), un conocido método de extracción de endmembers disponible en software comercial como el paquete Research Systems ENVI de Kodak. La implementación paralela propuesta en este trabajo ha sido desarrollada en una tarjeta gráfica programable (GPU) de tipo Nvidia Tesla C1060. Nuestra implementación paralela proporciona resultados muy prometedores, tanto desde el punto de vista de su precisión a la hora de identificar endmembers como desde el punto de vista del rendimiento paralelo obtenido, proporcionando factores de aceleración (speedups) de 200x utilizando una única tarjeta NVidia Tesla C1060. Estos resultados suponen un incremento muy notable del rendimiento computacional del algoritmo con respecto a su ejecución serie en un PC de última generación. Palabras clave: Hiperespectral, Endmember, PPI, Pixel Purity Index, GPU, CUDA, ENVI. Trabajo Fin de Máster 3 Sergio Sánchez Martínez Implementación de algoritmos de análisis hiperespectral en tarjetas gráficas programables (GPUs) Abstract Remotely sensed hyperspectral imaging is an active area of research in the literature. Due to available spatial resolution, most of the pixels collected by hyperspectral imaging instruments are in fact a mixture of different underlying substances. One of the most widely used approaches for analyzing hyperspectral images is spectral unmixing, which comprises two stages: 1) extraction of pure spectral signatures (endmembers), and 2) estimation of endmember fractional abundances at sub-pixel levels. Both stages are computationally complex, which is a serious drawback in applications which require a response in near real-time, such as forest fire monitoring and tracking, disaster management and prevention, oil spill detection, etc. In this work, we develop an efficient parallel implementation of the pixel purity index (PPI), a well-known algorithm for endmember extraction available in commercial software such as Kodak's Research Systems ENVI. The parallel implementation has been conducted in a commodity graphics processing unit (GPU) of NVidia Tesla C1060 type. Our parallel implementation of the PPI exhibits very promising results in terms of endmember extraction accuracy and parallel performance, with speedups approaching 200x using just one NVidia Tesla C1060 card. This represents a tremendous increase of performance with regards to the serial version of the same algorithm, implemented in a latest-generation desktop PC. Keywords Hyperspectral, Endmember, PPI, Pixel Purity Index, GPU, CUDA, ENVI. Trabajo Fin de Máster 4 Sergio Sánchez Martínez Implementación de algoritmos de análisis hiperespectral en tarjetas gráficas programables (GPUs) Índice de contenido 1 Motivaciones y Objetivos.................................................................................................10 1.1Motivaciones..............................................................................................................10 1.2Objetivos.....................................................................................................................11 2 Introducción......................................................................................................................14 2.1Concepto de imagen hiperespectral............................................................................14 2.2Sensores hiperespectrales considerados.....................................................................17 2.3Técnicas de análisis hiperespectral y necesidad de paralelismo.................................19 Técnicas basadas en el modelo lineal de mezcla........................................................20 Necesidad de paralelismo...........................................................................................21 El papel de las GPUs...................................................................................................22 3 Tarjetas gráficas programables GPUs..............................................................................23 3.1Las GPUs como un dispositivo de procesamiento de datos en paralelo....................23 Vortex shaders y píxel shaders....................................................................................24 Pipeline clásico de procesamiento en una GPU..........................................................25 Evolución del uso de GPUs en aplicaciones científicas.............................................27 3.2CUDA: una nueva arquitectura para el calculo en la GPU........................................29 Pipeline unificado.......................................................................................................29 Modelo de programación CUDA................................................................................30 3.3Procesamiento.............................................................................................................32 Un coprocesador multihilo..........................................................................................32 Hilos, Bloques y Grids................................................................................................33 Modelo de memoria....................................................................................................34 3.4Implementación hardware y modelo de ejecución.....................................................35 Nvidia Tesla C1060.....................................................................................................35 Modelo de ejecución...................................................................................................37 Especificaciones generales..........................................................................................39 4 Método..............................................................................................................................43 4.1Pixel Purity Index (PPI)..............................................................................................43 5 Procesamiento paralelo en GPU.......................................................................................47 5.1Generación de los skewers.........................................................................................47 5.2Proyección..................................................................................................................49 5.3Agrupación de resultados...........................................................................................52 5.4CUDA Occupancy Calculator....................................................................................53 5.5CUDA Visual Profiler.................................................................................................57 6 Resultados........................................................................................................................63 7 Conclusiones y líneas futuras...........................................................................................79 8 Apéndice...........................................................................................................................82 8.1Programación con CUDA...........................................................................................82 Compilación................................................................................................................82 Reserva y liberación de memoria de la GPU..............................................................83 Ejecutando código en la GPU.....................................................................................84 Trabajo Fin de Máster 5 Sergio Sánchez Martínez Implementación de algoritmos de análisis hiperespectral en tarjetas gráficas programables (GPUs) Modificadores de funciones........................................................................................84 Ejecución del kernel....................................................................................................85 Ejemplo de kernel.......................................................................................................85 Herramientas de sincronización..................................................................................88 9 Bibliografía.......................................................................................................................89 10 Publicaciones del Candidato..........................................................................................92 Trabajo Fin de Máster 6 Sergio Sánchez Martínez Implementación de algoritmos de análisis hiperespectral en tarjetas gráficas programables (GPUs) Índice de figuras Figura 2.1:Concepto de imagen hiperespectral....................................................................15 Figura 2.2:Adquisición de una imagen hiperespectral por el sensor AVIRIS......................16 Figura 2.3:Tipos de pixeles en imagenes hiperespectrales...................................................17 Figura 2.4:Evolución de la relación SNR del sensor AVIRIS de NASA.............................19 Figura 2.5:Interpretación gráfica del modelo lineal de mezcla............................................20 Figura 3.1: Operaciones en coma flotante por segundo para CPU y GPU..........................23 Figura 3.2:Ancho de banda CPU y GPU..............................................................................24 Figura 3.3:Pipeline clásico de procesamiento en una GPU..................................................26 Figura 3.4:La GPU proporciona más transistores para el procesamiento de datos..............27 Figura 3.5:Pipeline clásico frente a pipeline unificado........................................................30 Figura 3.6:Pila de software de CUDA..................................................................................31 Figura 3.7:Operaciones de memoria gather (reunión) y scatter (dispersión).......................31 Figura 3.8:Shared memory...................................................................................................32 Figura 3.9: Grid, bloques e hilos..........................................................................................34 Figura 3.10:Acceso a diferentes espacios de memoria.........................................................35 Figura 3.11:Arquitectura de la GPU Tesla C1060 de NVidia..............................................36 Figura 3.12:Conjunto de multiprocesadores SIMD con memoria compartida on-chip.......37 Figura 4.1:Funcionamiento del algoritmo PPI.....................................................................46 Figura 5.1:Almacenamiento de los Skewers........................................................................49 Figura 5.2:Almacenamiento de los resultados parciales......................................................50 Figura 5.3:Agrupación de resultados....................................................................................53 Figura 5.4:CUDA Occupancy Calculator.............................................................................54 Figura 5.5:CUDA Occupancy Calculator:Gráfica asociada al número de hilos por bloque55 Figura 5.6:CUDA Occupancy Calculator:Gráfica asociada al número de registros por hilo ..............................................................................................................................................56 Figura 5.7: CUDA Occupancy Calculator:Gráfica asociada a la cantidad de memoria compartida usada por cada bloque.......................................................................................57 Figura 5.8:CUDA Visual Profiler.........................................................................................59 Figura 5.9:CUDA Visual Profiler: Propiedades de la sesión................................................60 Figura 5.10:CUDA Visual Profiler: resultados parte 1.........................................................60 Figura 5.11:CUDA Visual Profiler: resultados parte 1.........................................................61 Figura 5.12:CUDA Visual Profiler: Gráfica resumen..........................................................61 Figura 5.13:CUDA Visual Profiler: Gráfica de tiempos......................................................62 Figura 6.1:Regiones y asignación de abundancias para la primera imagen sintética...........64 Figura 6.2:Mapas de abundancia empleados en la generación de la primera imagen sintética. a) Mapa de abundancia para el endmember suelo. b) Mapa de abundancia para el endmember vegetación.........................................................................................................65 Figura 6.3: Segunda imagen sintética. a) Distribución en la imagen de los cinco píxeles puros (endmembers), denotados como r1, r2, r3, r4 y r5. b-f) Mapas de abundancia empleados en la generación de la segunda imagen sintética................................................66 Figura 6.4:Ubicación de la imagen real AVIRIS Cuprite sobre una fotografía aérea de la región minera Cuprite, Nevada, Estados Unidos.................................................................67 Figura 6.5:Precisión estricta PPI en imagen sintética 1.......................................................68 Trabajo Fin de Máster 7 Sergio Sánchez Martínez Implementación de algoritmos de análisis hiperespectral en tarjetas gráficas programables (GPUs) Figura 6.6:Precisión estricta PPI en imagen sintética 2.......................................................69 Figura 6.7:Precisión ponderada PPI en imagen sintética 1..................................................70 Figura 6.8:Precisión ponderada PPI en imagen sintética 2..................................................71 Figura 6.9:Resultados gráficos de PPI para la imagen sintética 1........................................73 Figura 6.10:Resultados gráficos de PPI para la imagen sintética 2......................................75 Figura 6.11:Resultados visuales para las versiones C y ENVI en imagen real....................76 Figura 6.12:Resultados visuales para las versiones CUDA y ENVI en imagen real...........77 Figura 6.13:Ejemplo de materiales detectados.....................................................................78 Figura 8.1:Vectores operandos (v_A, v_B) y resultado (v_C) para suma de vectores.........86 Figura 8.2:Partición lógica del problema en bloques...........................................................86 Figura 8.3:Comparación de código de función C y kernel CUDA......................................88 Trabajo Fin de Máster 8 Sergio Sánchez Martínez Implementación de algoritmos de análisis hiperespectral en tarjetas gráficas programables (GPUs) Índice de tablas Tabla 3.1: Lecturas y escrituras en los diferentes tipos de memoria....................................34 Tabla 3.2:Especificaciones técnicas de la GPU Tesla C1060 de NVidia.............................36 Tabla 3.3:Recursos y limitaciones según la GPU que utilicemos para programar CUDA. .40 Tabla 3.4:Capacidad de cómputo y número de multiprocesadores de cada GPU de Nvidia ..............................................................................................................................................42 Tabla 5.1:CUDA Occupancy Calculator:Parámetros introducidos......................................54 Tabla 5.2:CUDA Occupancy Calculator: Resultados calculados en función de los parámetros............................................................................................................................55 Tabla 6.1: Comparación de resultados CPU y GPU para PPI en imagen sintética 1...........72 Tabla 6.2: Comparación de resultados CPU y GPU para PPI en imagen sintética 2...........74 Tabla 6.3:Comparación de resultados para las versiones C (CPU), CUDA (GPU) y ENVI (CPU) para 15360 iteraciones..............................................................................................75 Trabajo Fin de Máster 9 Sergio Sánchez Martínez Implementación de algoritmos de análisis hiperespectral en tarjetas gráficas programables (GPUs) 1 Motivaciones y Objetivos 1.1 Motivaciones El presente trabajo se ha desarrollado dentro de las líneas de investigación actuales del Grupo Hypercomp de la Universidad de Extremadura, y consiste en la implementación eficiente de un algoritmo para extracción de referencias espectrales puras o endmembers en imágenes hiperespectrales haciendo uso de GPUs para acelerar los cómputos relativos a la extracción de endmembers. En concreto el algoritmo implementado es: Píxel Purity Index (PPI). Las aplicaciones potenciales de este algoritmo son múltiples, destacando aplicaciones de detección de minerales, aplicaciones militares tales como detección de material armamentístico camuflado, anomalías, identificación de agentes contaminantes en aguas y atmósfera, etc. Conviene destacar los algoritmos de análisis hiperespectral generalmente necesitan grandes cantidades de tiempo a la hora de proporcionar resultados, debido a la complejidad computacional de los mismos (en el caso de PPI, el algoritmo deben ejecutar un gran número de iteraciones) y también debido al gran tamaño de las imágenes a procesar, lo cual requiere que la transferencia de datos entre el procesador y el dispositivo hardware especializado utilizado como coprocesador deba optimizarse. Al requerir tantas iteraciones, y tener dentro de estas operaciones no triviales, los algoritmos de análisis hiperespectral generalmente se traducen en un consumo de CPU elevado, por lo que la utilización de arquitecturas especializadas como coprocesadores puede resultar una alternativa altamente interesante. Hasta la fecha, las técnicas tradicionales en la literatura para abordar este problema han optado por soluciones basadas en el uso de clusters y sistemas multiprocesador. La computación cluster, a pesar de su adaptabilidad al problema del tratamiento de datos hiperspectrales (especialmente, cuando dichos datos se encuentran almacenados en un repositorio de datos en tierra), presenta problemas en cuanto al procesamiento de los datos en tiempo real dado el alto coste y elevados requerimientos en cuanto a espacio, peso y consumo (denominado payload en misiones de observación remota de la tierra). Por otra Trabajo Fin de Máster 10 Sergio Sánchez Martínez Implementación de algoritmos de análisis hiperespectral en tarjetas gráficas programables (GPUs) parte, la instalación de un cluster suele llevar asociada la disponibilidad de un número elevado de ordenadores interconectados entre sí para que compartan el procesamiento de datos a través de sus procesadores, lo cual hace incrementar la velocidad de ejecución y procesamiento de las aplicaciones; sin embargo, cada nodo (u ordenador) lleva ligado un precio y una serie de requerimientos en cuanto a espacio y consumo que alejan esta aproximación de las características requeridas en cuanto a payload en misiones reales de observación remota de la tierra. Para solucionar estos problemas relativos a coste, consumo y peso, y además ofrecer además mejoras sustanciales en cuanto al tiempo de procesamiento, en el presente TFM proponemos una alternativa basada en un nuevo modelo de tratamiento de imágenes hiperespectrales basado en la utilización de GPUs. Conviene destacar que, con una sola GPU, pueden llegar a obtenerse mejoras notables a la hora de procesar cálculos de tipo científico, como es el caso de los algoritmos de tratamiento de imágenes hiperespectrales, a un coste razonable (no más de 400 euros) y además ocupando un espacio mínimo. No obstante, no todas las tarjetas GPU disponibles en el mercado se ajustan a nuestros requerimientos. Finalmente, indicar que en este trabajo se ha intentado ir un poco más allá, y para trabajar de forma totalmente innovadora se ha utilizado la arquitectura CUDA incorporada en las tarjetas gráficas de NVIDIA de las series 8 (o superiores), Quadro y Tesla, siendo algunas de las tarjetas de la primera gama las que han sido objeto de estudio en este TFM. Para la realización de este trabajo se ha utilizado una maquina con un procesador Intel Core i7 920 una GPU NVidia Tesla C1060 y el sistema operativo Linux Ubuntu 9.04. Por otra parte otra de las actividades desarrolladas en el presente trabajo consiste en la realización de un estudio cuantitativo y comparativo del algoritmo implementado tanto en C++ para su ejecución en la CPU como en CUDA para su ejecución en la GPU así como un análisis de la precisión de los resultados obtenidos. 1.2 Objetivos Este trabajo pretende desarrollar sobre una GPU la implementación de un algoritmo para la extracción de endmembers en imágenes hiperespectrales y establecer un estudio cualitativo y comparativo de los resultados obtenidos tras la ejecución. Concretamente el Trabajo Fin de Máster 11 Sergio Sánchez Martínez Implementación de algoritmos de análisis hiperespectral en tarjetas gráficas programables (GPUs) algoritmo es: Píxel Purity Index (PPI) . La consecución del objetivo general anteriormente mencionado se lleva a cabo en la presente memoria abordando una serie de objetivos específicos, los cuales se enumeran a continuación: • Establecer un estudio sobre la utilidad y el funcionamiento del algoritmo PPI para extraer el grado de paralelismo inherente que presenta y utilizarlo para su implementación. • Realizar un estudio sobre el lenguaje de programación CUDA para poder implementar los algoritmos. • Implementar el algoritmo para su ejecución en la GPU y optimizar el tiempo de ejecución. • Implementar una serie de aplicaciones que nos permitan conocer la precisión que se ha logrado en la ejecución del algoritmo para dos imágenes sintéticas de las cuales se conocen previamente sus características. • Realizar un estudio comparativo sobre los resultados obtenidos. Teniendo presentes los anteriores objetivos concretos, procedemos a describir la organización del resto de esta memoria, estructurada en una serie de capítulos cuyos contenidos se describen a continuación: 2) Introducción. En este capítulo introductorio se describen los conceptos fundamentales relacionados con análisis hiperespectral y procesamiento de datos, enfatizando la necesidad de técnicas de procesamiento paralelo en este campo y proponiendo la utilización de las GPUs como procesador paralelo para este tipo de técnicas. 3) GPU. Este capítulo está dedicado a la GPU, hablaremos de cómo han ido evolucionando las GPUs programables, de los recursos que ofrecen y su capacidad de computo. Introduciremos una serie de conceptos para lograr entender cómo se lleva a cabo el procesamiento en una GPU mediante CUDA. Este capítulo también describe el hardware de la tarjeta Tesla C1060 con la que se ha realizado el trabajo. 4) Método. Este capítulo describe y muestra la utilidad del algoritmo Trabajo Fin de Máster 12 Sergio Sánchez Martínez Implementación de algoritmos de análisis hiperespectral en tarjetas gráficas programables (GPUs) implementado para el análisis hiperespectral así como su funcionamiento y filosofía. 5) Procesamiento paralelo en GPU. En este capítulo se muestra como se lleva a cabo el procesamiento paralelo en la GPU para el caso que nos ocupa, centrándonos en cada una de las fases del algoritmo por separado. 6) Resultados. En este capítulo se describen las imágenes que se han utilizado para probar el algoritmo, se muestran las diferentes formas de calcular la precisión, se muestran los resultados conseguidos y finalmente se comparan. 7) Conclusiones y líneas futuras. Este capítulo está dedicado a resumir las principales aportaciones realizadas por la presente memoria y a mostrar las conclusiones derivadas. Además, el capítulo sugiere un conjunto de líneas de trabajo que pueden ser abordadas en futuros trabajos. La memoria concluye con una serie de referencias bibliográficas utilizadas en el estudio. 8) Apéndice. En este capítulo se expone una guía de programación básica de CUDA y se muestra el código de los algoritmos implementados tanto en C+ + como en CUDA. También se muestra el código del algoritmo utilizado para calcular la precisión de los resultados. Trabajo Fin de Máster 13 Sergio Sánchez Martínez Implementación de algoritmos de análisis hiperespectral en tarjetas gráficas programables (GPUs) 2 Introducción El presente capítulo se organiza de la siguiente forma. En primer lugar, describimos el concepto de imagen hiperespectral, detallando las particularidades y características propias de este tipo de imágenes de alta dimensionalidad. A continuación, describimos algunas características genéricas de los sensores de adquisición de este tipo de datos, haciendo énfasis en los sensores utilizados en el presente trabajo. Seguidamente, se muestra una visión general de las técnicas de desmezclado disponibles en la actualidad, con particular énfasis en el modelo lineal de mezcla, utilizado en el presente trabajo para abordar el problema de la caracterización sub-píxel de una imagen hiperespectral a partir de la identificación de los píxels espectralmente más puros en la misma. El capítulo concluye destacando la necesidad de paralelismo en este campo y mostrando el papel de las GPU para su tratamiento. 2.1 Concepto de imagen hiperespectral El asentamiento de la tecnología hiperespectral en aplicaciones de observación remota de la tierra ha dado como resultado el desarrollo de instrumentos de medida de muy elevada resolución en los dominios espacial y espectral. Los sensores hiperespectrales adquieren imágenes digitales en una gran cantidad de canales espectrales muy cercanos entre sí, obteniendo, para cada porción de la escena o píxel, una firma espectral característica de cada material. El resultado de la toma de datos por parte de un sensor hiperespectral sobre una determinada escena puede ser representado en forma de cubo de datos, con dos dimensiones para representar la ubicación espacial de un píxel, y una tercera dimensión que representa la singularidad espectral de cada píxel en diferentes longitudes de onda [ 1 ]. La Figura 2.1 muestra la estructura de una imagen hiperespectral donde el eje X es el indicador de las líneas, el eje Y es el indicador de las muestras y el eje Z es el número de bandas, es decir, la longitud de onda de esa banda (canal). Trabajo Fin de Máster 14 Sergio Sánchez Martínez Implementación de algoritmos de análisis hiperespectral en tarjetas gráficas programables (GPUs) Figura 2.1:Concepto de imagen hiperespectral Como puede apreciarse en la Figura 2.1, el resultado de la toma de datos por parte de un sensor hiperespectral sobre una determinada escena puede ser representado en forma de cubo de datos, con dos dimensiones para representar la ubicación espacial de un píxel, y una tercera dimensión que representa la singularidad espectral de cada píxel en diferentes longitudes de onda. En concreto, la capacidad de observación de los sensores denominados hiperespectrales permite la obtención de una firma espectral detallada para cada píxel de la imagen, dada por los valores de reflectancia adquiridos por el sensor en diferentes longitudes de onda, lo cual permite una caracterización muy precisa de la superficie de nuestro planeta. Como ejemplo ilustrativo, la Figura 2.2 muestra el procedimiento de análisis hiperespectral mediante un sencillo diagrama, en el que se ha considerado como ejemplo el sensor AVIRIS (Airborne Visible Infra-Red Imaging Spectrometer), desarrollado por NASA/Jet Propulsión Laboratory, el cual cubre el rango de longitudes de onda entre 0.4 y 2.5 nm utilizando 224 canales y resolución espectral de aproximadamente 10 nm. Trabajo Fin de Máster 15 Sergio Sánchez Martínez Implementación de algoritmos de análisis hiperespectral en tarjetas gráficas programables (GPUs) Figura 2.2:Adquisición de una imagen hiperespectral por el sensor AVIRIS Como puede apreciarse en la Figura 2.2, la capacidad de observación de este sensor permite la obtención de una firma espectral detallada para cada píxel de la imagen, dada por los valores de reflectancia adquiridos por el sensor en diferentes longitudes de onda, lo cual permite una caracterización muy precisa de la superficie de nuestro planeta. Conviene destacar que, en este tipo de imágenes, es habitual la existencia de mezclas a nivel de subpíxel, por lo que a grandes rasgos podemos encontrar dos tipos de píxeles en estas imágenes: píxel puros y píxel mezcla [ 2 ]. Se puede definir un píxel mezcla como aquel en el que cohabitan diferentes materiales. Este tipo de píxel son los que constituyen la mayor parte de la imagen hiperespectral, en parte, debido a que este fenómeno es independiente de la escala considerada ya que tiene lugar incluso a niveles microscópicos [ 3 ]. La Figura 2.3 muestra un ejemplo del proceso de adquisición de píxeles puros (a nivel macroscópico) y mezcla en imágenes hiperespectrales. Trabajo Fin de Máster 16 Sergio Sánchez Martínez Implementación de algoritmos de análisis hiperespectral en tarjetas gráficas programables (GPUs) Figura 2.3:Tipos de pixeles en imagenes hiperespectrales El desarrollo tecnológico introducido por la incorporación de sensores hiperespectrales en plataformas de observación remota de la tierra de última generación ha sido particularmente notable durante los últimos años. En este sentido, conviene destacar que dos de las principales plataformas de tipo satélite que se encuentran en funcionamiento en la actualidad: Earth Observing-1 de NASA (http://eo1.gsfc.nasa.gov) y ENVISAT de la Agencia Espacial Europea (http://envisat.esa.int), llevan incorporados sensores de este tipo, permitiendo así la posibilidad de obtener imágenes hiperespectrales de la práctica totalidad del planeta de manera casi continua. A pesar de la gran evolución en los instrumentos de observación remota de la tierra, la evolución en las técnicas de análisis de los datos proporcionados por dichos sensores no ha sido tan notoria. En particular, la obtención de técnicas de análisis hiperespectral avanzadas, capaces de aprovechar totalmente la gran cantidad de información espacial y espectral presente en imágenes hiperespectrales, constituye un objetivo de gran interés para la comunidad científica. A continuación, describimos en detalle las características del sensor hiperespectral utilizado en el presente estudio. 2.2 Sensores hiperespectrales considerados En la actualidad, existe una amplia gama de sensores hiperespectrales de observación remota de la tierra. Dichos sensores pueden clasificarse según el modo en que son transportados (plataforma de transporte) en el momento de la toma de datos [ 4 - 6 ]. La mayor parte de los sensores hiperespectrales actuales son aerotransportados (siendo el Trabajo Fin de Máster 17 Sergio Sánchez Martínez Implementación de algoritmos de análisis hiperespectral en tarjetas gráficas programables (GPUs) ejemplo más claro de este tipo de instrumentos el sensor AVIRIS, considerado en el presente trabajo). AVIRIS es un sensor hiperespectral aerotransportado con capacidades analíticas en las zonas visible e infrarroja del espectro [ 8 - 10 ]. Este sensor está en funcionamiento desde 1987. Fue el primer sistema de adquisición de imágenes capaz de obtener información en una gran cantidad de bandas espectrales estrechas y casi contiguas. AVIRIS es un instrumento único en el mundo de la teledetección, pues permite obtener información espectral en 224 canales espectrales contiguos, cubriendo un rango de longitudes de onda entre 0.4 y 2.5 m, siendo el ancho entre las bandas muy pequeño, aproximadamente 0.01 m. En 1989, AVIRIS se convirtió en un instrumento aerotransportado. Desde ese momento, se realizan varias campañas de vuelo cada año para tomar datos mediante AVIRIS. El sensor ha realizado tomas de datos en Estados Unidos, Canadá y Europa, utilizando para ello dos plataformas: • Un avión ER-2 perteneciente a NASA/Jet Propulsion Laboratory. El ER-2 puede volar a un máximo de 20 km sobre el nivel del mar, a una velocidad máxima de aproximadamente 730 km/h. • Un avión denominado Twin Otter, capaz de volar a un máximo de 4 km sobre el nivel del mar, a velocidades de 130 km/h. Algunas de las características más relevantes en cuanto al diseño interno del sensor AVIRIS son las siguientes: • El sensor utiliza un explorador de barrido que permite obtener un total de 614 píxeles por cada oscilación. • La cobertura de la parte visible del espectro es realizada por un espectrómetro EFOS- A, compuesto por un array de 32 detectores lineales. • La cobertura en el infrarrojo es realizada por los espectrómetros EFOS-B, EFOS-C y EFOS-D, compuestos todos ellos por arrays de 64 detectores lineales. • La señal medida por cada detector se amplifica y se codifica utilizando 12 bits. Esta señal se almacena en una memoria intermedia donde es sometida a una etapa de pre Trabajo Fin de Máster 18 Sergio Sánchez Martínez Implementación de algoritmos de análisis hiperespectral en tarjetas gráficas programables (GPUs) procesado, siendo registrada a continuación en una cinta de alta densidad de 10.4 GB a velocidad de 20.4 MB/s. • El sensor dispone de un sistema de calibración a bordo, que utiliza una lámpara halógena de cuarzo que proporciona la radiación de referencia necesaria para comprobar el estado de los diferentes espectrómetros. • A lo largo de los últimos años, el sensor ha ido mejorando sus prestaciones en cuanto a la relación señal-ruido, como se muestra en la Figura 2.4 que describe la evolución de la relación SNR del sensor a lo largo de los últimos años. Figura 2.4:Evolución de la relación SNR del sensor AVIRIS de NASA 2.3 Técnicas de análisis hiperespectral y necesidad de paralelismo La mayoría de las técnicas de análisis hiperespectral desarrolladas hasta la fecha presuponen que la medición obtenida por el sensor en un determinado píxel viene dada por la contribución de diferentes materiales que residen a nivel sub-píxel. El fenómeno de la mezcla puede venir ocasionado por una insuficiente resolución espacial del sensor, pero lo cierto es que este fenómeno ocurre de forma natural en el mundo real, incluso a niveles microscópicos, por lo que el diseño de técnicas capaces de modelar este fenómeno de manera adecuada resulta imprescindible. No obstante, las técnicas basadas en este modelo son altamente costosas desde el punto de vista computacional. A continuación, detallamos las características genéricas de las técnicas basadas en este modelo y hacemos énfasis en la Trabajo Fin de Máster 19 Sergio Sánchez Martínez Implementación de algoritmos de análisis hiperespectral en tarjetas gráficas programables (GPUs) necesidad de técnicas paralelas para optimizar su rendimiento computacional. Técnicas basadas en el modelo lineal de mezcla El modelo lineal de mezcla expresa los píxeles mezcla [ 11 ] como una combinación lineal de firmas asociadas a componentes espectralmente puros (llamados endmembers) en la imagen [ 12 ]. Este modelo ofrece resultados satisfactorios cuando los componentes que residen a nivel sub-píxel aparecen espacialmente separados, situación en la que los fenómenos de absorción y reflexión de la radiación electromagnética incidente pueden ser caracterizados siguiendo un patrón estrictamente lineal. En la actualidad, el modelo lineal de mezcla es el más utilizado en análisis hiperespectral, debido a su sencillez y generalidad. Figura 2.5:Interpretación gráfica del modelo lineal de mezcla El modelo lineal de mezcla puede interpretarse de forma gráfica en un espacio bidimensional utilizando un diagrama de dispersión entre dos bandas poco correlacionadas de la imagen, tal y como se muestra en la Figura 2.5. En la misma, puede apreciarse que todos los puntos de la imagen quedan englobados dentro del triángulo formado por los tres puntos más extremos (elementos espectralmente más puros). Los vectores asociados a dichos puntos constituyen un nuevo sistema de coordenadas con origen en el centroide de la nube de puntos, de forma que cualquier punto de la imagen puede expresarse como Trabajo Fin de Máster 20 Sergio Sánchez Martínez Implementación de algoritmos de análisis hiperespectral en tarjetas gráficas programables (GPUs) combinación lineal de los puntos más extremos, siendo estos puntos los mejores candidatos para ser seleccionados como endmembers [ 13 ]. El paso clave a la hora de aplicar el modelo lineal de mezcla consiste en identificar de forma correcta los elementos extremos de la nube de puntos N-dimensional. En la literatura reciente se han propuesto numerosas aproximaciones al problema de identificación de endmembers en imágenes hiperespectrales. Por ejemplo, el método Píxel Purity Index (PPI) [ 9 ] se basa en la generación repetitiva de vectores unitarios con orientación aleatoria en la nube de puntos de forma que todos los puntos de la imagen hiperespectral se proyectan sobre cada vector unitario, identificando los puntos extremos en la dirección definida por dicho vector e incrementando un contador asociado a dichos puntos. Tras la ejecución de un número amplio de iteraciones, se obtiene como resultado una imagen de pureza, formada por los índices asociados a cada uno de los píxeles de la imagen, a partir de la cual se extrae un conjunto final de endmembers utilizando técnicas de análisis y visualización interactiva. Por su parte, el método N-FINDR utiliza un procedimiento totalmente automático para extraer endmembers basado en identificar los vértices del simplex de mayor volumen que puede formarse en la nube de puntos. Necesidad de paralelismo Conviene destacar que las técnicas de análisis hiperespectral anteriormente descritas se basan en la realización de operaciones matriciales que resultan muy costosas desde el punto de vista computacional [ 14 ]. Sin embargo, el carácter repetitivo de estas operaciones las hace altamente susceptibles de ser implementadas en diferentes tipos de arquitecturas paralelas, permitiendo así un incremento significativo de su rendimiento en términos computacionales y dotando a dichas técnicas de la capacidad de producir una respuesta rápida. Esta tarea es clave para la explotación de dichas técnicas en aplicaciones que requieren una respuesta en tiempo casi real. Las técnicas de computación paralela han sido ampliamente utilizadas para llevar a cabo tareas de procesamiento de imágenes de gran dimensionalidad, facilitando la obtención de tiempos de respuesta muy reducidos y pudiendo utilizar diferentes tipos de arquitecturas [ 15 - 17 ]. En la actualidad, es posible obtener arquitecturas paralelas de bajo Trabajo Fin de Máster 21 Sergio Sánchez Martínez Implementación de algoritmos de análisis hiperespectral en tarjetas gráficas programables (GPUs) coste mediante la utilización de GPUs de última generación que cuentan con múltiples procesadores. El papel de las GPUs La extracción de endmembers es una tarea crucial en la explotación de datos hiperespectrales. En la última década se han desarrollado muchos algoritmos para la extracción automática de endmembers desde un conjunto de datos hiperespectrales, incluidos PPI, N-FINDR, análisis de componentes vértices, o IEA. Estas técnicas tratan los datos hiperespectrales no como una imagen sino como una lista desordenada de dimensiones espectrales, donde las coordenadas espaciales se pueden cambiar de forma aleatoria sin que esto afecte al proceso de búsqueda de endmembers. A pesar de que el desarrollo espacial y espectral es una promesa para el análisis científico de imágenes terrestres, introduce un nuevo reto de procesamiento particularmente para conjunto de datos de alta dimensionalidad. Desde un punto de vista computacional cada algoritmo muestra un patrón de acceso a los datos regular y que muestra un paralelismo inherente a muchos niveles: a nivel de vectores de píxeles, a nivel de información espectral e incluso a nivel de tarea. Como resultado se asocian con sistemas paralelos compuestos por CPUs (por ejemplo clusters Beowulf). Desafortunadamente estos sistemas son caros y difíciles de adaptar a bordo de escenarios de procesamiento de sensación remota. Un nuevo desarrollo en el campo de la computación surge con los procesadores gráficos programables (GPUs). Guiadas por la creciente demanda de la industria de los videojuegos, las GPUs han evolucionado como sistemas programables altamente paralelos. Sin embargo la arquitectura de las GPUs no encaja necesariamente con todos los tipos de computación paralela. En especial, el siempre creciente requerimiento computacional introducido por el estado actual de los algoritmos de imágenes hiperespectrales pueden beneficiarse de este hardware y tomar ventaja de su poco peso y bajo coste de sus unidades, lo que lo hace llamativo para el procesamiento de datos a bordo por un coste mucho más bajo del que tienen otros dispositivos hardware como las FPGAs. Trabajo Fin de Máster 22 Sergio Sánchez Martínez Implementación de algoritmos de análisis hiperespectral en tarjetas gráficas programables (GPUs) 3 Tarjetas gráficas programables GPUs En este capítulo hablamos de cómo han ido evolucionando las GPUs programables, de los recursos que ofrecen y su capacidad de computo. Introduciremos una serie de conceptos para lograr entender cómo se lleva a cabo el procesamiento en una GPU mediante CUDA. Describiremos el hardware de la tarjeta Tesla C1060 con la que se ha realizado el trabajo y finalmente trataremos el lenguaje CUDA en si dando unas nociones básicas para llevar a cabo cualquier proyecto con este lenguaje. 3.1 Las GPUs como un dispositivo de procesamiento de datos en paralelo Desde un tiempo a esta parte, las GPUs programables han evolucionado como un elemento con una gran carga de trabajo, como podemos apreciar en las Figuras 3.1 y 3.2 donde se muestra una comparativa de la evolución de la capacidad de computo y del ancho de banda de las CPUs y las GPUs. Con múltiples núcleos y con un gran ancho de banda de memoria, hoy por hoy las GPUs ofrecen prestaciones muy elevadas para procesamiento gráfico y científico [ 19 - 22 ]. Figura 3.1: Operaciones en coma flotante por segundo para CPU y GPU Trabajo Fin de Máster 23 Sergio Sánchez Martínez Implementación de algoritmos de análisis hiperespectral en tarjetas gráficas programables (GPUs) Figura 3.2:Ancho de banda CPU y GPU Antes de examinar en detalle la arquitectura de una GPU Tesla C1060 de NVidia (http://www.nvidia.com), consideramos relevante explicar cómo han funcionado las operaciones en un pipeline de una GPU clásica a lo largo de los años. No obstante, para entender este proceso, también debemos introducir los conceptos de vertex shaders y los píxel shaders. Vortex shaders y píxel shaders Los shaders son pequeños programas que se encargan del procesamiento de vértices (vertex shaders) y de píxeles (píxel shaders). La principal ventaja es que pueden ser programados por el desarrollador, otorgando una flexibilidad que hasta antes de la aparición de los shaders era algo impensable. Recursos como las operaciones condicionales o los saltos se utilizan de forma similar que en los lenguajes más conocidos. Sin los shaders, muchos de los efectos eran realizados en conjunto con la unidad de procesamiento central, disminuyendo en gran medida el rendimiento y limitando el avance a nivel gráfico de los mismos. Un vertex shader es una función que recibe como parámetro un vértice. Sólo trabaja con un vértice a la vez, y no puede eliminarlo, sólo transformarlo. Para ello, modifica propiedades del mismo para que repercutan en la geometría del objeto al que pertenece. Trabajo Fin de Máster 24 Sergio Sánchez Martínez Implementación de algoritmos de análisis hiperespectral en tarjetas gráficas programables (GPUs) Con esto se pueden lograr ciertos efectos específicos, como los que tienen que ver con la deformación en tiempo real de un elemento; por ejemplo, el movimiento de una ola. Donde toma una gran importancia es en el tratamiento de las superficies curvas. En cambio, un píxel shader básicamente especifica el color de un píxel. Este tratamiento individual de los píxeles permite que se realicen cálculos principalmente relacionados con la iluminación del elemento del cual forman parte en la escena, y en tiempo real. La incorporación de los píxel shaders y vertex shaders permite a los programadores una mayor libertad a la hora de diseñar gráficos en tres dimensiones, ya que puede tratarse a cada píxel y cada vértice por separado. De esta manera, los efectos especiales y de iluminación pueden crearse mucho más detalladamente, sucediendo lo mismo con la geometría de los objetos. Pipeline clásico de procesamiento en una GPU Cuando revisamos las arquitecturas hardware, el flujo de datos, y las operaciones pipeline, a menudo es bueno empezar por el nivel más alto, donde los datos llegan desde la CPU a la GPU, y el proceso se desarrolla hacia abajo a través de múltiples fases de procesamiento hasta que un píxel es dibujado definitivamente en la pantalla. Para situarnos, las GPUs han utilizado diseños pipeline tradicionales, como los que aparecen ilustrados en la Figura 3.3. Trabajo Fin de Máster 25 Sergio Sánchez Martínez Implementación de algoritmos de análisis hiperespectral en tarjetas gráficas programables (GPUs) Figura 3.3:Pipeline clásico de procesamiento en una GPU Después de que la GPU recibe los datos vertex (vértices) desde el host (CPU), la fase vertex se ejecuta en primer lugar. La función de fijado transforma la imagen y el hardware de luminosidad operado en esta fase se lleva a cabo; entonces los píxeles shaders programables, y el control de flujo dinámico de los modelos shaders entran en juego. El siguiente paso en el pipeline clásico es la configuración, donde los vértices son ensamblados dentro de primitivas como triángulos, líneas o puntos. Las primitivas son convertidas por la fase de “rasterización” en fragmentos de píxeles (o simplemente fragmentos), pero no son considerados píxeles completos en esta fase. Los fragmentos están sometidos a muchas otras operaciones como sombreado, Z-testing, la posible mezcla en el buffer frame, y el antialiasing. Los fragmentos son finalmente considerados píxeles cuando han sido escritos en el buffer frame. A continuación, la siguiente fase es la de píxel shader, que debería ser denominada técnicamente como fase fragment shader, pero utilizamos la notación estándar en la literatura [ 19 - 22 ] debido a su aceptación. En el pasado, los fragmentos sólo podían haber tenido valores de color aplicados de textura simple. Hoy en día, la capacidad de sombreado de un píxel programado de la GPU permite numerosos efectos de sombreado para ser aplicados mientras se trabaja de acuerdo con métodos complejos de multitextura. Específicamente, los fragmentos sombreados (con color y valores Z) desde esta fase píxel Trabajo Fin de Máster 26 Sergio Sánchez Martínez Implementación de algoritmos de análisis hiperespectral en tarjetas gráficas programables (GPUs) son enviados al ROP (Raster Operations). La fase ROP es donde se chequea el buffer Z para asegurar que sólo los fragmentos visibles son procesados rápidamente, y los fragmentos visibles, si son parcialmente transparentes, son mezclados con el buffer de frame existente, junto con los píxeles y aplicándoles antialiased. El píxel procesado final es enviado a la memoria buffer para ser escaneado y visualizado en el monitor [ 19 ]. Evolución del uso de GPUs en aplicaciones científicas La principal razón que justifica la gran popularidad de las arquitecturas GPU en aplicaciones científicas es el hecho de que la GPU está especializada para cómputo intensivo, computación paralela elevada (exactamente sobre lo que trata el renderizado de gráficos) y por tanto se designan más transistores dedicados al procesamiento de datos, que a la recolección de datos y control de flujo como se muestra en la Figura 3.4. Figura 3.4:La GPU proporciona más transistores para el procesamiento de datos Más específicamente, la GPU está especialmente pensada para direccionar problemas que pueden ser expresados como computaciones de datos paralelos (el mismo programa es ejecutado en muchos elementos de datos en paralelo) con gran intensidad aritmética (el ratio de operaciones aritméticas respecto a operaciones de memoria). Como el mismo programa es ejecutado para cada elemento de datos, hay menos requisitos para un flujo de control sofisticado; y como es ejecutado en muchos elementos de datos y tiene gran intensidad aritmética, la latencia de acceso a memoria puede ser ocultada con cálculos, en vez de datos muy grandes de caché [ 20 ]. Trabajo Fin de Máster 27 Sergio Sánchez Martínez Implementación de algoritmos de análisis hiperespectral en tarjetas gráficas programables (GPUs) El procesamiento de datos paralelos asocia los datos a elementos de proceso paralelos. Muchas aplicaciones que procesan grandes conjuntos de datos como arrays pueden usar un modelo de programación de datos paralelos para acelerar los cálculos. En renderizado 3D los conjuntos de píxeles y vértices se asignan a hilos paralelos. De la misma manera, aplicaciones de procesamiento de imágenes y multimedia como postprocesado de imágenes renderizadas, codificación y decodificación de vídeo, escalado de imágenes, visión estéreo, y patrones de reconocimiento pueden asociar bloques de la imagen y píxeles a hilos de procesamiento paralelo. De hecho, muchos algoritmos fuera del campo del renderizado como el procesamiento de señales, simulaciones físicas finanzas o biología, se aceleran con el procesamiento de datos en paralelo. Hasta la fecha, sin embargo, a pesar de acceder a todo el poder de computación contenido en al GPU y usarlo eficientemente para aplicaciones científicas, seguía siendo difícil obtener las siguientes pautas: • La GPU solamente podía ser programada a través de la API (Application Programming Interface) gráfica; esto provocaba que la curva de aprendizaje para un desarrollador principiante fuese muy elevada, ya que tenía que trabajar con una API inadecuada, que no estaba adaptada a la aplicación científica. • La DRAM de la GPU podía ser leída de manera general (los programas de GPU pueden obtener elementos de datos de cualquier parte de la DRAM) pero no se podía escribir de manera general (los programas de GPU no pueden esparcir la información a cualquier parte de la DRAM), eliminando mucha de la flexibilidad de programación ya disponible en la CPU. • Algunas aplicaciones tenían en problema del “cuello de botella”, debido al ancho de banda de la memoria DRAM, utilizando escasamente el poder computacional de la GPU. En este sentido, una de las principales motivaciones del presente TFM es demostrar que dichas limitaciones en la actualidad pueden superarse mediante la utilización de la arquitectura CUDA para procesamiento de datos científicos en la GPU. Dicho aspecto será abordado en detalle en el siguiente subapartado del presente capítulo de la memoria. Trabajo Fin de Máster 28 Sergio Sánchez Martínez Implementación de algoritmos de análisis hiperespectral en tarjetas gráficas programables (GPUs) 3.2 CUDA: una nueva arquitectura para el calculo en la GPU CUDA viene del inglés Compute Unified Device Architecture y es una nueva arquitectura hardware y software, diseñada para dar y manejar procesamientos en la GPU como un elemento de computación de datos paralelos sin la necesidad de mapearlos a una API de gráficos. Está disponible para las versiones GeForce 8 Series, Quadro FX 5600/4600, Tesla y Fermi. El mecanismo de multitarea del sistema operativo es responsable de manejar el acceso a la GPU mediante CUDA, y las aplicaciones gráficas funcionan de forma simultánea. A continuación describimos el pipeline unificado del que disponen las actuales GPUs de NVIDIA y que puede ser explotado de forma eficiente mediante CUDA, así como la arquitectura completa de la Tesla C1060. El apartado finaliza describiendo los diferentes modelos de programación en CUDA. Pipeline unificado A partir del modelo de pipeline clásico, con sus flujos de datos empezando en lo más alto, donde los vértices con varios atributos, índices, comandos, y texturas son pasados a la GPU desde la CPU. Las fases de procesamiento mayores siguen una manera lineal segura incluyendo vertex shading, píxel shading, operaciones raster, (que son operaciones a través de las cuales un área espacial queda dividida en celdas regulares, en las que cada una de las cuales presentan unos atributos o valor, como pueden ser la altitud, reflectancia, etc.) y escritura de píxeles en el buffer frame. Con este pipeline unificado y la arquitectura “shader”, el diseño de la GPU Tesla C1060 reduce significativamente el número de fases del pipeline y cambia el flujo secuencial para estar más orientado a bucle. Las entradas son alimentadas en la parte alta del núcleo shader unificado, y las salidas son escritas en registros y entonces vuelven otra vez a la parte alta del núcleo shader para la próxima operación. Como resultado, en el diagrama GPU unificado generalizado que se muestra en la Figura 3.5, los flujos de datos bajan secuencialmente por el pipeline a través de diferentes tipos “shader”. La figura de la derecha representa un núcleo “shader” unificado con uno o más procesadores “shader” unificados estandarizados. Trabajo Fin de Máster 29 Sergio Sánchez Martínez Implementación de algoritmos de análisis hiperespectral en tarjetas gráficas programables (GPUs) Figura 3.5:Pipeline clásico frente a pipeline unificado Como puede apreciarse en la Figura 3.5, los datos vienen de la parte superior izquierda del diseño unificado (como vértices), y son llevados al núcleo shader para su procesamiento, y los resultados son enviados de vuelta a la parte superior del núcleo shader, donde son llevados otra vez, procesados otra vez, mandados de vuelta a la parte superior, y así hasta que todas las operaciones shader son ejecutadas y el fragmento de píxel se pasa al subsistema ROP [ 19 ]. Modelo de programación CUDA Antes de profundizar en el modelo de programación empleado por CUDA, destacamos que la pila del software de CUDA se compone de varias capas, tal y como muestra la Figura 3.6. En concreto, dichas capas son un controlador de hardware, una API y su runtime, y dos librerías matemáticas de alto nivel para uso común, CUFFT y CUBLAS. El hardware ha sido diseñado para soportar controladores ligeros y capas runtime, dando como resultado una ejecución óptima. En este sentido, la API de CUDA es una extensión del lenguaje de programación C, lo cual hace que tenga una curva de aprendizaje mínima. Trabajo Fin de Máster 30 Sergio Sánchez Martínez Implementación de algoritmos de análisis hiperespectral en tarjetas gráficas programables (GPUs) Figura 3.6:Pila de software de CUDA Por otra parte, CUDA ofrece un direccionamiento de carácter general para la memoria DRAM como ilustra la Figura 3.7. Este modelo de direccionamiento permite obtener mayor flexibilidad en la programación, en el sentido de que ofrece tanto la operación de reparto de datos como la de obtención de estos. Desde una perspectiva de programación, esto se traduce en la habilidad de leer y escribir datos en cualquier lugar de la DRAM, exactamente igual que en la CPU [ 20 ]. Figura 3.7:Operaciones de memoria gather (reunión) y scatter (dispersión) CUDA proporciona una memoria compartida on-chip a modo de caché de datos que permite accesos muy rápidos de lectura y de escritura. Mediante ésta los hilos pueden compartir datos. Como se muestra en la Figura 3.8, las aplicaciones pueden beneficiarse de esta memoria minimizando los accesos a memoria DRAM lo que les hace menos Trabajo Fin de Máster 31 Sergio Sánchez Martínez Implementación de algoritmos de análisis hiperespectral en tarjetas gráficas programables (GPUs) dependientes del ancho de banda de la DRAM que es mucho menor que el de la shared memory. Figura 3.8:Shared memory 3.3 Procesamiento En la siguiente sección presenta a la GPU como un coprocesador multihilo para la CPU, seguidamente se exponen los diferentes mecanismos de los que se hace uso para llevar a cabo el procesamiento en la GPU, estos son los hilos de ejecución, los bloques en los que se organizan estos hilos y los llamados grid en los que se estructuran estos bloques. La sección finaliza con el modelo de memoria utilizado por CUDA. Un coprocesador multihilo Cuando se programa con CUDA, la GPU se ve como un dispositivo de cálculo (device) capaz de ejecutar un gran número de hilos en paralelo. Éste opera como un coprocesador de la CPU principal, o host. En otras palabras, los datos paralelos, cálculo intensivo de porciones de aplicaciones ejecutándose en el host son cargados en el device. De forma más precisa, una parte de una aplicación que se ejecuta muchas veces, pero con datos diferentes e independientes, puede ser aislada en una función que es ejecutada en el device como muchos hilos independientes. Como dicha función es compilada, la instrucción obtenida a partir del device (en nuestro caso, la GPU) y el programa resultante, Trabajo Fin de Máster 32 Sergio Sánchez Martínez Implementación de algoritmos de análisis hiperespectral en tarjetas gráficas programables (GPUs) llamado kernel, se descargan en el device. Tanto el host como el device mantienen su propia DRAM, referidas como memoria del host y memoria del device respectivamente. Una puede copiar datos de una memoria a la otra a través de llamadas a una API optimizada, que usa el Acceso Directo a Memoria (DMA) del device [ 20 ]. Hilos, Bloques y Grids El tratamiento por lotes de los hilos que ejecuta el kernel está organizado como un grid de bloques de hilos, ilustrado en la Figura 3.9. Un bloque de hilos es un lote de hilos que pueden cooperar juntos compartiendo datos eficientemente a través de la memoria compartida y sincronizar sus ejecuciones para coordinar los accesos a memoria. De forma precisa, uno puede especificar puntos de sincronización en el kernel, donde los hilos en un bloque están suspendidos hasta que todos ellos alcancen el punto de sincronización. Cada hilo es identificado por su identificador de hilo (thread ID), que es el número de hilo dentro de un bloque [ 20 ]. Hay un número máximo de hilos que un bloque puede contener (512 hilos concretamente). Sin embargo, los bloques de misma dimensión y tamaño que ejecutan el mismo kernel pueden ser tratados por lotes de forma conjunta, en un grid de bloques, así que el número total de hilos puede ser lanzado en una única invocación del kernel es mucho más grande. Esto se debe al gasto de reducir la cooperación entre hilos, porque los hilos en diferentes bloques del mismo grid no pueden comunicarse ni sincronizarse con los de los demás. Este modelo permite a los kernels ejecutarse eficientemente sin recompilación en varios devices con diferentes capacidades paralelas: un device puede ejecutar todos los bloques de un grid secuencialmente si tiene poca capacidad, o en paralelo si tiene mucha, o normalmente una combinación de ambas [ 20 ]. La Figura 3.9 muestra como cada kernel se ejecuta como un grid de bloques de hilos. Trabajo Fin de Máster 33 Sergio Sánchez Martínez Implementación de algoritmos de análisis hiperespectral en tarjetas gráficas programables (GPUs) Figura 3.9: Grid, bloques e hilos Modelo de memoria Un hilo que se ejecute en el device tiene solo acceso a la DRAM del device y a la memoria on-chip a través de los siguientes espacios de memoria (Tabla 3.1) [ 20 ], como se muestra en la Figura 3.10. Registro de hilo Lectura Escritura Memoria Memoria compartida de local de hilo bloque ✓ ✓ ✓ ✓ ✓ ✓ Memoria global de grid Memoria constante de grid Memoria de texturas de grid ✓ ✓ ✓ × ✓ × Tabla 3.1: Lecturas y escrituras en los diferentes tipos de memoria Los espacios de memoria global, constante y de textura pueden ser leídos o escritos por el host y perduran durante las distintas ejecuciones del kernel en la misma aplicación. Estos espacios se optimizan para diferentes usos de la memoria. Además la memoria de texturas ofrece diferentes modos de direccionamiento, así como de filtrado de datos, para diferentes formatos de datos. Trabajo Fin de Máster 34 Sergio Sánchez Martínez Implementación de algoritmos de análisis hiperespectral en tarjetas gráficas programables (GPUs) Figura 3.10:Acceso a diferentes espacios de memoria 3.4 Implementación hardware y modelo de ejecución En esta sección se presenta la arquitectura hardware de la GPU utilizada en el trabajo y se explica el modelo de ejecución que sigue CUDA. Nvidia Tesla C1060 A lo largo de la presente memoria hemos cubierto muchos de los puntos básicos de la programación de GPUs de NVidia, así que ahora podemos echar un vistazo a los aspectos específicos de la arquitectura Tesla C1060 [ 19 ], la tarjeta que estamos usando para realizar este TFM. La Figura 3.11 describe la arquitectura hardware de dicha tarjeta. El significado de las siglas que aparecen en el esquema es el siguiente: TPC (Texture/Processor Cluster), SM (Streaming Multiprocessor), SP (Streaming Processor), Tex (Texture), ROP (Raster Operation Processor). Trabajo Fin de Máster 35 Sergio Sánchez Martínez Implementación de algoritmos de análisis hiperespectral en tarjetas gráficas programables (GPUs) Figura 3.11:Arquitectura de la GPU Tesla C1060 de NVidia A continuación la Tabla 3.2 muestra las características técnicas de la GPU: Specification Description Form Factor 10.5" x 4.376", Dual Slot # of Streaming Processor Cores 240 Frequency of processor cores 1.3 GHz Single Precision floating point 933 performance (peak) Double Precision floating point 78 performance (peak) Floating Point Precision IEEE 754 single & double Total Dedicated Memory 4 GDDR3 Memory Speed 800MHz Memory Interface 512-bit Memory Bandwidth 102 GB/sec Max Power Consumption 187.8 W System Interface PCIe x16 Auxiliary Power Connectors 6-pin & 8-pin Thermal Solution Active fan sink Tabla 3.2:Especificaciones técnicas de la GPU Tesla C1060 de NVidia Trabajo Fin de Máster 36 Sergio Sánchez Martínez Implementación de algoritmos de análisis hiperespectral en tarjetas gráficas programables (GPUs) Modelo de ejecución La GPU se implementa como un conjunto de multiprocesadores como se puede ver en la Figura 3.12. Cada multiprocesador tiene una arquitectura SIMD: en cada ciclo de reloj, cada procesador del multiprocesador ejecuta la misma instrucción, pero opera en datos distintos. Los espacios de memoria local y global son implementados como regiones de la memoria del device. Cada procesador tiene acceso a la memoria compartida del multiprocesador en el que está integrado. Cada multiprocesador accede a la cache de textura mediante una unidad de textura que implementa los diferentes modos de direccionamiento y filtros de datos [ 20 ]. Figura 3.12:Conjunto de multiprocesadores SIMD con memoria compartida on-chip Un grid de bloques de hilos se ejecuta en el device mediante la programación de los bloques en cada multiprocesador. Cada multiprocesador procesa los bloques por lotes, un lote tras otro. Un bloque solo se procesa en un multiprocesador, así el espacio de memoria compartida reside en la memoria on-chip compartida por lo que se puede beneficiar de la velocidad que proporciona dicha memoria. Trabajo Fin de Máster 37 Sergio Sánchez Martínez Implementación de algoritmos de análisis hiperespectral en tarjetas gráficas programables (GPUs) El numero de bloques que cada multiprocesador puede procesar en un lote depende de cuantos registros necesita cada hilo y de cuanta memoria compartida necesita cada bloque en un kernel ya que estos recursos se deben compartir entre todos los hilos de los bloques del lote. Si no hay una cantidad suficiente de registros o de memoria compartida disponible en un multiprocesador para procesar al menos un bloque, el kernel fallará en su ejecución. Los bloques que se procesan en un multiprocesador en un lote se llaman activos. Cada bloque activo se divide en grupos de hilos SIMD llamados warps. Cada uno de estos warps contiene el mismo número de hilos, este número se llama warp size, y se ejecuta en un multiprocesador en modo SIMD. Los warps activos (los warps de los bloques activos) se dividen en el tiempo: un programador de hilos cambia de un warp a otro para maximizar el uso de recursos computacionales del multiprocesador. Se llama half warp a la primera o a la segunda mitad de un warp. La forma en la que un bloque se divide en warps es siempre la misma; cada warp contiene hilos consecutivos, incrementando sus identificadores de hilo (thread ID). El primer warp contiene al hilo 0. El orden de emisión de los warps de un bloque no esta definido y no existe un mecanismo de sincronización entre bloques para coordinar los accesos a memoria global o compartida. El orden de emisión de los bloques en un grid de bloques de hilos tampoco está definido y no hay un mecanismo de sincronización entre bloques, así que los hilos de dos bloques diferentes pertenecientes al mismo grid no se pueden comunicar de una forma segura a través de la memoria global durante la ejecución de un grid. Si una instrucción no atómica ejecutada por un warp escribe en la misma posición ya sea de memoria global o compartida por más de un hilo de warp, el número de de escrituras serializadas que ocurran en esa posición así como el orden de las escrituras es indefinido, pero se garantiza que al menos una escritura tenga éxito. Si una instrucción atómica ejecutada por un warp lee, modifica o escribe en una posición de memoria ya sea global o compartida por más de un hilo del warp, cada lectura, modificación o escritura en la posición ocurre de forma serializada aunque el orden es indefinido. Trabajo Fin de Máster 38 Sergio Sánchez Martínez Implementación de algoritmos de análisis hiperespectral en tarjetas gráficas programables (GPUs) Especificaciones generales A continuación en la Tabla 3.3 se listan una serie de especificaciones generales que se deben tener en cuenta a la hora de programar una GPU con CUDA para lograr una ejecución más eficiente en función de la capacidad de computo (Compute capability) de la GPU. Trabajo Fin de Máster 39 Sergio Sánchez Martínez Implementación de algoritmos de análisis hiperespectral en tarjetas gráficas programables (GPUs) Parámetro Valor según gener. GPU CUDA Compute Capabilities 1.0 y 1.1 1.2 y 1.3 Fermi Multiprocesadores / GPU Limitación Impacto 16 30 16 HW. Escalabilidad 8 8 32 HW. Escalabilidad 32 32 32 SW. Throughput 8 8 8 SW. Throughput Hilos / Bloque 512 512 512 SW. Paralelismo Hilos / Multiprocesador 768 1024 1536 SW. Paralelismo Registros de 32 bits / Multiproc. 8192 16384 4096 HW. Working Set Memoria compartida / Multiproc. 16384 16384 16 K 48K HW. Working Set Procesadores / Multiprocesador Hilos / Warp Bloques de hilos / Multiprocesador Tabla 3.3:Recursos y limitaciones según la GPU que utilicemos para programar CUDA A continuación en la Tabla 3.4 se muestra la capacidad de computo y el número de multiprocesadores de cada GPU de Nvidia. Trabajo Fin de Máster 40 Sergio Sánchez Martínez Implementación de algoritmos de análisis hiperespectral en tarjetas gráficas programables (GPUs) Número de Multiprocesadore s (1 Multiprocesador = 8 Procesadores) Compute Capability 2x30 1.3 GeForce GTX 285, GTX 280 30 1.3 GeForce GTX 260 24 1.3 GeForce 9800 GX2 2x16 1.1 GeForce GTS 250, GTS 150, 9800 GTX, 9800 GTX+, 8800 GTS 512 16 1.1 GeForce 8800 Ultra, 8800 GTX 16 1.0 GeForce 9800 GT, 8800 GT, GTX 280M, 9800M GTX 14 1.1 GeForce GT 130, 9600 GSO, 8800 GS, 8800M GTX, GTX 260M, 9800M GT 12 1.1 GeForce 8800 GTS 12 1.0 GeForce 9600 GT, 8800M GTS, 9800M GTS 8 1.1 GeForce 9700M GT 6 1.1 GeForce GT 120, 9500 GT, 8600 GTS, 8600 GT, 9700M GT, 9650M GS, 9600M GT, 9600M GS, 9500M GS, 8700M GT, 8600M GT, 8600M GS 4 1.1 GeForce G100, 8500 GT, 8400 GS, 8400M GT, 9500M G, 9300M G, 8400M GS, 9400 mGPU, 9300 mGPU, 8300 mGPU, 8200 mGPU, 8100 mGPU 2 1.1 GeForce 9300M GS, 9200M GS, 9100M G, 8400M G 1 1.1 Tesla S1070 4x30 1.3 Tesla C1060 30 1.3 Tesla S870 4x16 1.0 Tesla D870 2x16 1.0 Tesla C870 16 1.0 Quadro Plex 2200 D2 2x30 1.3 Quadro Plex 2100 D4 4x14 1.1 GeForce GTX 295 Trabajo Fin de Máster 41 Sergio Sánchez Martínez Implementación de algoritmos de análisis hiperespectral en tarjetas gráficas programables (GPUs) Quadro Plex 2100 Model S4 4x16 1.0 Quadro Plex 1000 Model IV 2.16 1.0 Quadro FX 5800 30 1.3 Quadro FX 4800 24 1.3 Quadro FX 4700 X2 2x14 1.1 Quadro FX 3700 M 16 1.1 Quadro FX 5600 16 1.0 Quadro FX 3700 14 1.1 Quadro FX 3600M 12 1.1 Quadro FX 4600 12 1.0 Quadro FX 2700 M 6 1.1 Quadro FX 1700, FX 570, NVS 320M, FX 1700M, FX 1600M, FX 770M, FX 570M 4 1.1 Quadro FX 370, NVS 290, NVS 140M, NVS 135M, FX 360M 2 1.1 Quadro FX 370M, NVS 130M 1 1.1 Tabla 3.4:Capacidad de cómputo y número de multiprocesadores de cada GPU de Nvidia Trabajo Fin de Máster 42 Sergio Sánchez Martínez Implementación de algoritmos de análisis hiperespectral en tarjetas gráficas programables (GPUs) 4 Método En este apartado se describe el método de extracción de endmembers considerado en el presente estudio e implementado de forma eficiente utilizando tarjetas gráficas programables de la gama Nvidia. La implementación paralela de este algoritmo en GPUs será descrita en la próxima sección del presente documento. 4.1 Pixel Purity Index (PPI) Uno de los algoritmos más satisfactorios para la extracción automática de endmembers ha sido el método Píxel Purity Index (PPI) [ 9 ] desarrollado por Boardman, Kruse, y Green en 1993, y que se incorporo al sistema de búsqueda ENVI de Kodak. El algoritmo procede mediante la generación de un gran número de vectores aleatorios Ndimensionales llamados “skewers”. Cada punto de la imagen se proyecta en cada skewer y los puntos que correspondan a los extremos en la dirección de un skewer se identifican y se almacenan en una lista. Como se generan más skewers, la lista crece, y el número de veces que un píxel dado se almacena en la lista también se incrementa. Los píxeles con mayores incrementos son considerados como endmembers finales. El algoritmo PPI pertenece al conjunto de los métodos interactivos y es el más representativo. Su objetivo es localizar los puntos espectralmente más puros de la imagen hiperespectral, basándose en la suposición de que los puntos más extremos del conjunto de puntos son los mejores candidatos para ser utilizados como endmembers. Los parámetros de entrada del algoritmo son el número de iteraciones a realizar y el valor umbral para seleccionar píxeles puros. El funcionamiento del algoritmo se describe en los siguientes pasos: 1) En primer lugar, el algoritmo asigna un índice de pureza a todos los píxeles de la imagen. El contador de cada punto se inicializa al valor 0. 2) Seguidamente, se genera un vector unitario aleatorio, que recibe el nombre de skewer o “divisor”. El objetivo de este vector es particionar el conjunto de puntos, como veremos a continuación. 3) El tercer paso consiste en proyectar todos los puntos de la imagen Trabajo Fin de Máster 43 Sergio Sánchez Martínez Implementación de algoritmos de análisis hiperespectral en tarjetas gráficas programables (GPUs) hiperespectral sobre el vector unitario antes generado, identificando los puntos extremos en la dirección definida por el vector unitario. El índice de pureza de los puntos extremos se incrementa en una unidad. 4) Los pasos 2-3 del algoritmo se repiten tantas veces como el usuario especifique en el parámetro de entrada, número de iteraciones. 5) Tras la ejecución de un número amplio de iteraciones, se obtiene como resultado una imagen de pureza formada por los índices asociados a cada uno de los píxeles de la imagen. 6) Utilizando el valor umbral especificado como parámetro, se seleccionan los puntos de la imagen cuyo índice de pureza asociado supera dicho valor umbral. Estos puntos son etiquetados como “puros”. La figura ilustra el procedimiento seguido hasta este punto, suponiendo que se realizan tres iteraciones y que se seleccionan como endmembers aquellos puntos que han sido seleccionados como extremos una o más veces. 7) Los píxeles seleccionados se cargan en una herramienta interactiva denominada N-Dimensional Visualizar, la cual permite realizar diagramas de dispersión de los primeros autovectores obtenidos tras la aplicación de una transformación MNF sobre los datos originales. 8) Utilizando la herramienta anteriormente descrita, el usuario selecciona manualmente aquellos puntos o agrupaciones de puntos que aparecen como extremos en proyecciones sucesivas, identificando un conjunto final de endmembers. En el caso de seleccionar una agrupación de puntos, los endmembers se obtienen a partir del cálculo del espectro promedio en cada una de las regiones seleccionadas. A continuación se exponen los pasos del algoritmo de una forma más analítica[ 24 ]: 1. Las entradas del algoritmo son un cubo hiperespectral de datos con dimensiones; el número máximo de endmembers que se van a extraer, número de vectores aleatorios que se van a generar durante el proceso, Trabajo Fin de Máster 44 N E ; el K ; y un Sergio Sánchez Martínez Implementación de algoritmos de análisis hiperespectral en tarjetas gráficas programables (GPUs) valor umbral, usado para seleccionar como endmembers finales solo aquellos píxeles que han sido seleccionados como extremos al menos proceso PPI. La salida del algoritmo es el conjunto de E tv veces durante el endmembers finales E {e e }e=1 . El algoritmo se puede resumir en los siguientes pasos: 1. Generación de skewers. Produce un conjunto de K vectores generados aleatoriamente {skewer j }Kj=1 . 2. Proyecciones de extremos. Para cada fi píxeles skewer j , toda la muestra de vectores de del conjunto de datos original F se proyecta sobre el skewer j mediante el producto escalar para encontrar una muestra de vectores en sus extremos (máximo y mínimo), de esta manera se forma un conjunto de extremos para el skewer j que se denota por S extrema skewer j . El producto escalar es el siguiente: ∣ f i⋅skewer j∣ A pesar del hecho de que un S extrema skewer j skewer j diferente puede generar un diferente, es muy probable que la misma muestra de vectores aparezca en más de un conjunto de extremos. Para hacer frente a esta situación, definiremos una función indicadora de un conjunto S , a la que llamaremos l s x , para denotar la pertenencia la pertenencia de un elemento x a un conjunto en particular de la siguiente manera: l s f i ={1 si x ∈S } 0 si x ∉S 3. Calculo de las puntuaciones PPI. Utilizando la función indicadora anterior, calculamos la puntuación PPI asociada a un píxel vector muestra fi (por ejemplo el número de veces que un píxel dado ha sido seleccionado como extremo en el paso 2) usando la siguiente ecuación: Trabajo Fin de Máster 45 Sergio Sánchez Martínez Implementación de algoritmos de análisis hiperespectral en tarjetas gráficas programables (GPUs) N PPI f i =∑ K I S j=1 extrema skewer j f i 4. Selección de endmember. Encontrar los píxeles cuyo valor de N PPI f i esté por encima de t v . El método PPI contiene etapas totalmente automatizadas, como la fase de generación de la imagen de pureza, pero es necesaria una etapa final, altamente interactiva, en la que el usuario selecciona manualmente los píxeles que quiere utilizar como endmembers. El usuario no conoce a priori cuál es el número apropiado de endmembers a seleccionar, por lo que debe escoger el número de endmembers en base a su intuición. Este hecho pone de manifiesto la conveniencia de cierto conocimiento a priori sobre la imagen. Esta característica, unida a otras como la aleatoriedad en el proceso de generación de vectores unitarios (ver Figura 4.1), representan los principales inconvenientes de esta metodología [ 18 ]. Por otra parte, es importante destacar que el método PPI puede generar endmembers artificiales en caso de que el usuario del método seleccione conjuntos de puntos en el proceso interactivo de identificación de firmas espectrales extremas. Figura 4.1:Funcionamiento del algoritmo PPI Trabajo Fin de Máster 46 Sergio Sánchez Martínez Implementación de algoritmos de análisis hiperespectral en tarjetas gráficas programables (GPUs) 5 Procesamiento paralelo en GPU Una vez introducidas las ventajas que aportan las GPUs para el procesamiento paralelo pasemos a exponer como se lleva a cabo este procesamiento en el caso que nos ocupa, es decir en el algoritmo PPI que ya ha sido descrito anteriormente. Como ya sabemos contamos con una GPU NVidia Tesla C1060 la cual cuenta con 30 multiprocesadores cada uno de los cuales dispone a su vez de 8 procesadores. Pongamos como caso de uso una imagen hipotética de dimensiones 350 x 350 x 188, esto es, 350 líneas, 350 columnas y 188 bandas. Nuestra implementación del algoritmo PPI se puede dividir en 3 fases bien diferenciadas. Por un lado tenemos la generación de los skewers, por otro lado la proyección de todos los puntos de la imagen sobre cada skewer y la detección de los píxels mas extremos. Y por otro lado la agrupación de los resultados para formar la imagen de pureza. 5.1 Generación de los skewers Como ya sabemos un skewer es un vector unitario cuyas componentes se generan de forma aleatoria. El número de componentes de cada skewer debe ser igual al número de bandas de la imagen hiperespectral, así tenemos que en nuestro caso de uso el número de componentes de cada skewer es 188. En una iteración el algoritmo proyecta todos los puntos de la imagen sobre un skewer, por tato necesitamos tantos skewers como iteraciones. Supongamos por ejemplo que el número de iteraciones que queremos realizar es de 15360, necesitaremos pues general un total de 15360 x 188 números aleatorios. A la hora de generar tal cantidad de números hemos utilizado una función que realiza dicha acción haciendo uso de la GPU. Esta función se encuentra dentro de los ejemplos que proporciona CUDA, concretamente se trata de una función llamada RandomGPU que aparece dentro del ejemplo MersenneTwister. Esta función permite generar y almacenar en la memoria de la GPU 24002560 elementos en tan solo 10 milisegundos, muchos más de los que necesitamos para este ejemplo. De esta manera con una sola llamada al kernel ya tenemos solucionado el problema de generación de números en un tiempo ínfimo comparado con el tiempo total. Cada grupo de 188 elementos formará un skewer. Trabajo Fin de Máster 47 Sergio Sánchez Martínez Implementación de algoritmos de análisis hiperespectral en tarjetas gráficas programables (GPUs) La configuración de la llamada al kernel se ha dejado tal y como viene en el ejemplo, por lo que se van a usar 32 bloques de 128 hilos cada bloque. Los parámetros necesarios para la ejecución de la función son 3, a continuación se detalla cada uno de ellos. 1. Dirección a la estructura de datos donde se van a almacenar los números generados. Éste parámetro debe de ser un puntero a un float y previamente se deberá reservar espacio en la GPU para albergar esta estructura. A esta estructura la llamaremos d_Random y es de tipo vector. 2. Número de elementos que generará cada hilo, se trata de un entero cuyo valor indica el número de elementos que se va a generar durante la ejecución de un hilo. De esta forma si este parámetro tiene el valor N, tras la ejecución del kernel tendremos almacenados en la estructura num_bloques x num_hilos x N elementos. 3. Semilla. Se trata de un valor que sirve de semilla para la función de generación, su valor está en función de la hora y fecha de la máquina mediante la llamada a srand(time(NULL)); La utilización de un valor semilla se debe a que internamente la computadora no dispone de un “dado” con el que poder generar valores sino que utiliza funciones matemáticas y estadísticas complejas que necesitan de un valor inicial. La asignación de un valor en función de la hora y fecha a la semilla nos permite obtener valores distintos en cada ejecución. Si el valor de la semilla fuese el mismo siempre obtendríamos los mismos resultados. De hecho como los parámetros son comunes a todos los hilos del kernel todos los hilos generarían el mismo conjunto de elementos aleatorios de no ser porque internamente este valor de semilla se modifica multiplicándose por el identificador interno del hilo. De esta forma cada hilo cuenta con una semilla distinta y por lo tanto genera un conjunto de elementos distinto del resto de hilos. Una vez finalizada la fase de generación de skewers el siguiente paso es la proyección de todos los puntos de la imagen sobre cada skewer y la detección de los píxels extremos. La forma en la que los skewers quedarán almacenados en la estructura d_Random se muestra en la Figura 5.1, siendo N el número de iteraciones: Trabajo Fin de Máster 48 Sergio Sánchez Martínez Implementación de algoritmos de análisis hiperespectral en tarjetas gráficas programables (GPUs) Figura 5.1:Almacenamiento de los Skewers 5.2 Proyección Esta fase se realiza mediante una única llamada al kernel PPI. En ella cada hilo realizará la proyección de todos los píxels de la imagen sobre un único skewer y detectará los dos píxels más extremos para ese skewer. La configuración para la ejecución del kernel es la siguiente: tendremos tantos hilos de ejecución como iteraciones queramos realizar en el algoritmo, estos hilos se agruparán en bloques de 512, por tanto el número de bloques será el número de iteraciones / 512. El hecho de que se escoja un tamaño de bloque de 512 hilos no es algo fortuito sino que se obtiene como resultado de una configuración que garantiza una ejecución óptima del kernel y que al final se traducirá en una mejora importante de rendimiento. A continuación detallaremos cada uno de los parámetros de este kernel y concluiremos esta fase con la explicación del funcionamiento del mismo. 1. El primer parámetro es la dirección de la imagen hiperespectral en memoria global. Este parámetro se llama d_imagen y se trata de un puntero a un vector de floats. La imagen esta almacenada en formato BSQ (secuencial por bandas) esto quiere decir que primero se almacenan los píxels de la primera banda, después de la segunda y así sucesivamente. Las primeras posiciones del vector se corresponden con los primeros píxels de la primera banda de la imagen. Este parámetro es un parámetro de entrada ya que la imagen no se va a modificar. 2. El segundo parámetro nos es conocido, se trata de la dirección de la estructura donde se encuentran almacenados los skewers, su nombre es d_random y como ya sabemos es un puntero a un vector de floats. También se trata de un parámetro de entrada. 3. El tercer parámetro es la dirección en memoria global de la estructura donde se Trabajo Fin de Máster 49 Sergio Sánchez Martínez Implementación de algoritmos de análisis hiperespectral en tarjetas gráficas programables (GPUs) almacenarán los resultados parciales del algoritmo. Su nombre es d_res_parcial y se trata de un vector de floats. El tamaño de este vector es el número de iteraciones x 2. Esto es así porque en cada iteración se almacenan las dos proyecciones extremas sobre un skewer, esto es los píxels que obtienen el valor mas alto y mas bajo en el producto escalar con el skewer. La forma en la que se almacenan estos valores en la estructura está representada en la Figura 5.2: Figura 5.2:Almacenamiento de los resultados parciales 4. Los tres parámetros restantes son tres enteros que representan las dimensiones de la imagen hiperespectral: num_lines (número de lineas), num_samples (número de columnas) y num_bands (número de bandas). Pasamos ahora a explicar el funcionamiento del kernel. Como ya hemos comentado lo que se pretende es que cada hilo realice una iteración del algoritmo, esto es que calcule la proyección de todos los puntos de la imagen sobre un único skewer y obtenga los dos píxels extremos. Para ello adames de las estructuras que tenemos como parámetros necesitamos un par de estructuras más. La primera de ellas es local a cada hilo y se almacena en memoria local, es decir en los registros, se trata de una estructura de tipo vector de floats con tantos elementos como bandas tenga la imagen, en nuestro caso de uso 188, el nombre de esta estructura es l_rand y en ella el hilo i cargará desde memoria global el skewer que ocupe la posición i dentro de la estructura d_random descrita anteriormente. Almacenamos esta estructura en memoria local por dos razones: la primera es que los valores almacenados en esta estructura por cada hilo no van a ser necesitados por el resto de hilos, por tanto no ubicamos esta estructura en la memoria compartida. La segunda es que a lo largo de la ejecución del algoritmo l_rand va a ser accedida continuamente, concretamente se accede a cada Trabajo Fin de Máster 50 Sergio Sánchez Martínez Implementación de algoritmos de análisis hiperespectral en tarjetas gráficas programables (GPUs) elemento una vez por cada píxel de la imagen. Por esto hacemos uso de la memoria local que es cientos de veces mas rápida que la memoria global de la GPU. La segunda estructura que necesitamos se almacena en memoria compartida, se trata también de una estructura de tipo vector que tendrá capacidad para 10 píxels en nuestro caso de uso. El nombre de la estructura es s_píxels Como cada hilo necesita realizar el producto escalar por los mismos píxels es lógico hacer uso de una estructura compartida a la que puedan acceder todos los hilos de un bloque sin necesidad de acceder cada uno a memoria global por separado. Además esta memoria es también cientos de veces mas rápida que la memoria global con lo que notamos una fuerte mejora en el rendimiento al hacer uso de ella. Por esta estructura irán pasando todos los píxels de la imagen en grupos de 10. Ahora si veamos el funcionamiento del kernel paso a paso: lo primero que se hace es cargar de memoria global un skewer de d_random y almacenarlo en l_rand, también cargamos de d_imagen un grupo de 10 píxels y los almacenamos en s_píxels alojado en memoria compartida, hagamos aquí un pequeño paréntesis para explicar como se hace esta lectura de píxels que utiliza dos mecanismos que ofrece CUDA y que mejoran el rendimiento en gran medida. A la hora de leer los píxels de memoria global estamos lo hacemos mediante una lectura coalescente, la coalescencia permite realizar lecturas o escrituras con un único acceso siempre que los datos se encuentren en un segmento de 32, 64 o 128 bytes, en nuestro caso cargamos de memoria píxels cuyos valores en la misma banda se encuentran en posiciones consecutivas, cada valor en una banda es un float (4 bytes) como tenemos 10 píxels leemos 40 bytes que supondrían un único acceso a un segmento de 64 bytes. Al tratarse la memoria global de una memoria lenta en comparación con la compartida o la local, el hecho de reducir el número de accesos supone una mejora en el rendimiento. El otro mecanismo lo usamos al escribir estos píxels en memoria compartida. La memoria compartida tiene un tamaño de 16 KB organizados en 16 bancos de 1KB. La escritura en memoria se puede paralelizar siempre que estemos accediendo a bancos distintos sino los accesos a memoria se serializan y resultan mas lentos. En nuestro caso aunque todos los hilos de un bloque necesiten todos los píxels, la lectura de memoria Trabajo Fin de Máster 51 Sergio Sánchez Martínez Implementación de algoritmos de análisis hiperespectral en tarjetas gráficas programables (GPUs) globlal y escritura en memoria compartida la realizan exclusivamente los 10 primeros hilos de cada bloque, cada uno lee de memoria el valor de un píxel en una banda y lo escribe en memoria compartida en un banco distinto del resto por lo que la escritura se realiza en paralelo. Una vez que tenemos almacenados el skewer y un grupo de 10 píxels el siguiente paso es calcular el producto escalar de cada píxel con el skewer. Cada hilo utiliza dos variables pemin y pemax que almacenan los valores máximo y mínimo obtenidos en el producto escalar, y otras dos variables imax e imin que almacenan el índice de los píxels que han obtenido esos valores máximo y mínimo. Cuando finaliza la proyección de un grupo de 10 píxels se carga otro grupo y se repite el proceso hasta que hayamos cargado todos los píxels de la imagen. Finalmente cada hilo en la posición 2 x identificador_hilo y 2 x identificador_hilo + 1 de la estructura d_res_parcial almacenará los valores imin e imax como vimos anteriormente en la Figura 5.2. 5.3 Agrupación de resultados Esta fase se realiza en la CPU y como su nombre indica se trata de aunar los resultados en una imagen de pureza, en esta imagen el valor de cada píxel representará el número de veces que ese píxel se ha seleccionado como extremo. El proceso a seguir es recorrer la estructura h_res_parcial (que es la copia de d_res_parcial pero en memoria de CPU) e incrementar en uno la posición de h_res_total (imagen de pureza) que indique cada elemento de h_res_parcial. Este proceso se puede apreciar en la Figura 5.3: Trabajo Fin de Máster 52 Sergio Sánchez Martínez Implementación de algoritmos de análisis hiperespectral en tarjetas gráficas programables (GPUs) Figura 5.3:Agrupación de resultados Como podemos ver el píxel 1 aparece 2 veces en h_res_parcial y se incrementa 2 veces en h_res_total, el píxel 3 aparece 3 veces en h_res_parcial y se incrementa 3 veces en h_res_total. De la misma forma hay píxels que ni siquiera se incrementa como es el caso del píxel 0 que no aparece en h_res_parcial. A continuación presentaremos dos herramientas de CUDA que son muy necesarias a la hora de llevar a cabo cualquier mejora de optimización en nuestro código. Éstas son Cuda Occupancy Calculator y CUDA Visual Profiler. 5.4 CUDA Occupancy Calculator Es una herramienta que nos asesora en la elección de los parámetros de configuración de un kernel. Se trata de una hoja de cálculo que nos presenta diversas gráficas de la ocupación de un multiprocesador en función de la capacidad de cómputo, del número de hilos que utilicemos en cada bloque, del número de registros que utilicemos por hilo y de la cantidad de memoria compartida por bloque. El número de hilos por bloque es fácil de conocer ya que es un parámetro fijado por nosotros. Para conocer tanto el número de registros usados como la cantidad de memoria compartida necesitamos incluir como modificador en el compilador el flag –ptxas-options="-v". Con ésto a la hora de compilar se nos mostrarán esos valores. Con estos cuatro parámetros CUDA Occupancy Calculator nos muestra entre otras cosas el número de hilos activos por multiprocesador, el número de warps activos por multiprocesador, el número de bloques de hilos activos por multiprocesador y la ocupación Trabajo Fin de Máster 53 Sergio Sánchez Martínez Implementación de algoritmos de análisis hiperespectral en tarjetas gráficas programables (GPUs) de cada multiprocesador. El principal objetivo de usar esta herramienta es conseguir un 100% de ocupación lo que significa que no hay ningún procesador ocioso en ningún multiprocesador. A continuación la Figura muestra el aspecto de esta herramienta: Figura 5.4:CUDA Occupancy Calculator Seguidamente mostraremos los resultados de CUDA Occupancy Calculator para una de nuestras ejecuciones, concretamente para una de nuestras imágenes sintéticas descritas en el siguiente capítulo. La Tabla 5.1 muestra los parámetros introducidos en la herramienta: 1.3 1.) Select Compute Capability: 2.) Enter your resource usage: 512 Threads Per Block 14 Registers Per Thread 8104 Shared Memory Per Block (bytes) Tabla 5.1:CUDA Occupancy Calculator:Parámetros introducidos Una vez que hemos introducido los parámetros se calcula la ocupación de cada Trabajo Fin de Máster 54 Sergio Sánchez Martínez Implementación de algoritmos de análisis hiperespectral en tarjetas gráficas programables (GPUs) multiprocesador y se muestra el estado de estos en las gráficas. La Tabla 5.2 muestra los resultados calculados en base a estos parámetros: 3.) GPU Occupancy Data is displayed here and in the graphs: 1024 Active Threads per Multiprocessor 32 Active Warps per Multiprocessor 2 Active Thread Blocks per Multiprocessor 100% Occupancy of each Multiprocessor Tabla 5.2:CUDA Occupancy Calculator: Resultados calculados en función de los parámetros La herramienta además proporciona tres gráficas, cada una de ellas asociada a un parámetro (nº de hilos por bloque, nº de registros por hilo y memoria compartida usada por cada bloque), en éstas se muestra la ocupación conseguida con la configuración actual y los posibles cambios en la ocupación si variamos el valor del parámetro asociado. La ocupación se calcula como: Ocupación= Nº de warps ejecutandose concurrentemente en un multiprocesador Nº máximo de warps Máximo = 24 warps (1.0 y 1.1), 32 warps (1.3) La Figura 5.5 muestra la gráfica asociada al número de hilos por bloque. Figura 5.5:CUDA Occupancy Calculator:Gráfica asociada al número de hilos por bloque La ocupación calculada con la configuración actual viene representada por la Trabajo Fin de Máster 55 Sergio Sánchez Martínez Implementación de algoritmos de análisis hiperespectral en tarjetas gráficas programables (GPUs) posición del triángulo rojo, éste está a la misma altura en las tres gráficas. De esta gráfica podemos extraer que con 512 hilos por bloque conseguimos la máxima ocupación. Además si no variamos ninguno de los otros dos parámetros, cualquier otra configuración en cuanto al número de hilos por bloque perjudicaría a la ocupación y por tanto al rendimiento conseguido. Por otra parte 512 hilos es el límite permitido para un tamaño de bloque de una dimensión. La Figura 5.6 Muestra la gráfica asociada al número de registros utilizado por cada hilo. Figura 5.6:CUDA Occupancy Calculator:Gráfica asociada al número de registros por hilo En esta gráfica podemos observar que se ha alcanzado la ocupación máxima usando cada hilo un total de 14 registros. Como sabemos por los resultados de la Tabla 5.2 en cada multiprocesador tenemos 2 bloques activos por tanto los recursos hardware de ese multiprocesador se comparten entre los dos bloques, estos bloques podrán ejecutarse en paralelo siempre y cuando ninguno de los dos consuma mas de la mitad de los recursos disponibles. Cada multiprocesador tiene un total de 16K registros de 32 bits, es decir 16384 registros. La mitad de estos es 8192, tenemos en un bloque 512 hilos que consumen 14 registros cada uno lo que hace un total de 7168 registros, cantidad que no supera la mitad pero que tampoco permite que alojar un tercer bloque. Como puede verse en la Trabajo Fin de Máster 56 Sergio Sánchez Martínez Implementación de algoritmos de análisis hiperespectral en tarjetas gráficas programables (GPUs) gráfica, en el supuesto de que cada hilo utilizase mas de 16 registros, la ocupación se reduciría a la mitad, permitiendo solamente la ejecución de un bloque de hilos por multiprocesador. Finalmente la Figura 5.7 Muestra la gráfica asociada a la cantidad de memoria compartida usada por cada bloque. Figura 5.7: CUDA Occupancy Calculator:Gráfica asociada a la cantidad de memoria compartida usada por cada bloque En esta gráfica también podemos ver que la ocupación de los multiprocesador es máxima, como sabemos el número de bloques activos en un multiprocesador es 2 por tanto cada bloque no debe consumir más de la mitad de los recursos disponibles. Un total de 9 píxels en memoria compartida almacenados por cada bloque hacen que se usen 8104 bytes que no alcanza por poco la mitad de la memoria compartida disponible que tiene un total de 16KB. Si mantenemos el numero de hilos y el numero de registros por hilo y aumentamos el uso de la memoria compartida a mas de la mitad del total, la ocupación se reduciría a la mitad y afectaría al rendimiento gravemente. 5.5 CUDA Visual Profiler CUDA Visual Profiler es otra herramienta mucho más completa y compleja que nos proporciona ciertas mediciones estratégicas para la localización de problemas de rendimiento como pueden ser la temporización entre GPU y CPU para las invocaciones al Trabajo Fin de Máster 57 Sergio Sánchez Martínez Implementación de algoritmos de análisis hiperespectral en tarjetas gráficas programables (GPUs) los kernels y las llamadas a memcpy o la evoluciona determinados pasos de tiempo. Sigue la pista de ciertos eventos definidos por el programa a través de contadores hardware que registran determinadas señales emitidas por el chip. Estos contadores representan eventos dentro de un warp de hilos e involucran a un solo multiprocesador, esto es que los valores no se corresponden con el número total de warps lanzados por un kernel dado. Los valores se utilizan para cuantificar la mejora de rendimiento producida por una versión optimizada del código. Los contadores que proporciona el profiler entre muchos otros son: • Cargas y almacenamientos en memoria global que pueden ser coherentes (“coalesced”) o incoherentes (“non-coalesced”) ◦ gld_incoherent ◦ gld_coherent ◦ gst_incoherent ◦ gst_coherent • Cargas y almacenamientos locales ◦ local_load ◦ local_store • Número total de bifurcaciones(“branches”) y bifurcaciones divergentes tomadas por los hilos ◦ branch ◦ divergent_branch • Número de instrucciones ejecutadas ◦ instructions • Warps de hilos que han sido secuencializadios por los conflictos de direccionamiento a la memoria compartida o a la memoria de constantes. ◦ warp_serialize • Bloques de hilos que han sido ejecutados ◦ cta_launched Trabajo Fin de Máster 58 Sergio Sánchez Martínez Implementación de algoritmos de análisis hiperespectral en tarjetas gráficas programables (GPUs) Para conseguir un código optimizado debemos reducir gld/gst_incoherent, divergent_branch y warp_serialize. La Figura 5.8 muestra el aspecto de esta herramienta. Figura 5.8:CUDA Visual Profiler Usar esta herramienta es bastante fácil. Simplemente creamos una sesión y editamos las propiedades en el siguiente cuadro de diálogo mostrado en la Figura 5.9. Trabajo Fin de Máster 59 Sergio Sánchez Martínez Implementación de algoritmos de análisis hiperespectral en tarjetas gráficas programables (GPUs) Figura 5.9:CUDA Visual Profiler: Propiedades de la sesión Como puede verse en la figura anterior debemos rellenar cinco campos: El nombre de la sesión, la ruta del ejecutable, la ruta del directorio de trabajo, los parámetros del ejecutable y el tiempo máximo de ejecución. La información que nos muestra tras la ejecución de nuestro algoritmo para una de las imágenes sintéticas es la siguiente: Figura 5.10:CUDA Visual Profiler: resultados parte 1 La Figura 5.10 nos muestra de izquierda a derecha el instante en que se ha lanzado cada kernel y las transferencias entre CPU y GPU (GPU Timestamp) y el tiempo que ha durado cada una de las operaciones. Además para los kernels nos muestra el número de bloques (grid size X), el número de hilos por bloque (block size X), la cantidad de memoria compartida usada por cada bloque (static shared memory per block), el número Trabajo Fin de Máster 60 Sergio Sánchez Martínez Implementación de algoritmos de análisis hiperespectral en tarjetas gráficas programables (GPUs) de registros que usa cada hilo (registers per threads), la ocupación (Occupancy), el número de saltos (branch) y el número de saltos divergentes (divergent branch). Figura 5.11:CUDA Visual Profiler: resultados parte 1 La Figura 5.11 nos muestra para las transferencias CPU GPU la cantidad de datos transferidos en bytes (mem transfer size), y para cada kernel el número de instrucciones ejecutadas, el total de cargas y almacenamientos locales (local load y local store) y número de cargas y almacenamientos de segmentos de 32, 64 y 128 bytes con la memoria global (gld/gst 32b/64b/128b). CUDA Visual Profiler además nos permite mostrar una serie de gráficas temporales relacionadas con la evolución temporal de la ejecución, vamos a mostrar dos de ellas. La primera gráfica mostrada en la Figura 5.12 muestra el porcentaje del tiempo total de ejecución que se ha invertido en cada parte, este tipo de gráficas resultan muy utiles para detectar cuellos de botella en las transferencias entre CPU y GPU. Figura 5.12:CUDA Visual Profiler: Gráfica resumen Podemos ver en nuestro caso que el 99% del tiempo total del algoritmo se emplea en el kernel PPI y el resto en generar los skewers y en las transferencias. La siguiente gráfica muestra el tiempo empleado por cada transferencia y por cada kernel, se muestra en la Figura 5.13. Trabajo Fin de Máster 61 Sergio Sánchez Martínez Implementación de algoritmos de análisis hiperespectral en tarjetas gráficas programables (GPUs) Figura 5.13:CUDA Visual Profiler: Gráfica de tiempos Como se puede ver prácticamente todo el tiempo se emplea en el kernel PPI que tarda 1.7 segundos en ejecutarse. El resto del tiempo se emplea en las transferencias y en la generación de skewers. Trabajo Fin de Máster 62 Sergio Sánchez Martínez Implementación de algoritmos de análisis hiperespectral en tarjetas gráficas programables (GPUs) 6 Resultados En este apartado vamos a mostrar y analizar los resultados obtenidos. Utilizando para realizar comparaciones las versiones en C+ y CUDA para las imágenes sintéticas y además la versión de ENVI para la imagen real. Antes de comenzar con el análisis de los resultados mostraremos y explicaremos las imágenes que se han utilizado en el estudio. Las imágenes sintéticas permiten adoptar una forma objetiva a la hora de evaluar el rendimiento de un algoritmo de análisis hiperespectral, ya que dichas imágenes se generan de forma controlada y la información relativa al grado de pureza de cada píxel puede controlarse a priori. Describimos a continuación el proceso de generación de una imagen sintética mediante un sencillo ejemplo, detallando el proceso de selección de firmas espectrales, la forma en que se establece la asignación de abundancias y el esquema seguido para la generación de ruido. En el ejemplo que nos ocupa, las firmas empleadas en la construcción de la imagen simulada corresponden a una imagen obtenida por el sensor AVIRIS en 1997 sobre la región Jasper Ridge en California. La disponibilidad pública de esta imagen en unidades de reflectancia resulta muy atractiva con objeto de crear imágenes simuladas a partir de firmas espectrales reales. A continuación, se establece la abundancia de cada una de las referencias elegidas en cada píxel de la imagen, de forma que la contribución individual de cada firma se expresa mediante un conjunto de coeficientes de abundancia que cumplen las restricciones de no-negatividad y suma unitaria, propias del modelo lineal de mezcla. La última etapa en la simulación consiste en añadir ruido a la imagen simulada. Nuestro proceso de simulación de ruido en imágenes sintéticas se ha llevado a cabo utilizando un generador aleatorio de números entre 1 y –1. Estos valores siguen un modelo de distribución de probabilidad normal, con lo que su valor medio es 0 y su desviación estándar es 1. El ruido se añade a cada píxel de la imagen en una determinada proporción que determina la relación señal-ruido presente en la escena. Una vez descrito el proceso de generación de imágenes sintéticas, pasamos a describir las imágenes concretas que vamos a emplear en nuestro estudio. La primera imagen simulada tiene dimensiones 100x100 píxeles, y viene caracterizada por una situación de mezcla entre componentes progresiva, a intervalos de abundancia muy cortos. Trabajo Fin de Máster 63 Sergio Sánchez Martínez Implementación de algoritmos de análisis hiperespectral en tarjetas gráficas programables (GPUs) Como resultado, los cambios en la abundancia en las regiones que componen esta imagen son muy sutiles. En total, hay 100 regiones de 1 píxel de ancho y 100 píxeles de alto. La abundancia del componente suelo decrece de forma lineal de izquierda a derecha, mientras que la abundancia del componente vegetación crece de izquierda a derecha. La Figura 6.1 ilustra el proceso de construcción de esta imagen sintética, que proporciona un conjunto de datos muy apropiado para analizar el proceso de estimación de abundancias realizado por el algoritmo PPI. Este hecho se debe a la situación de mezcla particular que se representa en la imagen. Figura 6.1:Regiones y asignación de abundancias para la primera imagen sintética Por otra parte, la Figura 6.2 muestra los mapas de abundancia empleados para la generación de la primera imagen sintética utilizada en los experimentos, que comprende mapas para dos endmembers simulados (vegetación y suelo). Conviene destacar que la relación SNR en la imagen simulada (tras el proceso de simulación de ruido) es de 110:1 (proporción de señal de 110 veces a 1 sobre el ruido) y el número de bandas es de 224 (correspondiente a un espectro real de AVIRIS) comprendidas entre 0.4 y 2.5 micrómetros, con una nueva banda aproximadamente cada 10 nanómetros. El tamaño total de la imagen es de 4.27 MB. Trabajo Fin de Máster 64 Sergio Sánchez Martínez Implementación de algoritmos de análisis hiperespectral en tarjetas gráficas programables (GPUs) Figura 6.2:Mapas de abundancia empleados en la generación de la primera imagen sintética. a) Mapa de abundancia para el endmember suelo. b) Mapa de abundancia para el endmember vegetación. Finalmente, la Figura 6.3 muestra los mapas de abundancia empleados para la generación de la segunda imagen sintética utilizada en los experimentos, que comprende mapas para cinco endmembers simulados (cuatro tipos de vegetación y suelo). De nuevo, la relación SNR en la imagen simulada (tras el proceso de simulación de ruido) es de 110:1 y el número de bandas es de 224, comprendidas entre 0.4 y 2.5 micrómetros. El tamaño total de la imagen es, de nuevo, de 4.27 MB. Trabajo Fin de Máster 65 Sergio Sánchez Martínez Implementación de algoritmos de análisis hiperespectral en tarjetas gráficas programables (GPUs) Figura 6.3: Segunda imagen sintética. a) Distribución en la imagen de los cinco píxeles puros (endmembers), denotados como r1, r2, r3, r4 y r5. b-f) Mapas de abundancia empleados en la generación de la segunda imagen sintética. La Figura 6.4 muestra la imagen real utilizada en los experimentos. Esta imagen fue adquirida en 1995 por el sensor AVIRIS sobre la región minera denominada Cuprite, en el estado de Nevada, Estados Unidos. En concreto, la Figura 6.4 muestra la ubicación de la imagen sobre una fotografía aérea de la zona. Visualmente, puede apreciarse la existencia de zonas compuestas por minerales, así como abundantes suelos desnudos y una carretera interestatal que cruza la zona en dirección norte-sur. La imagen consta de 350x350 píxeles, cada uno de los cuales contiene 188 valores de reflectancia en el rango espectral 0.4 a 2.5 µm. Este rango, situado en la región SWIR-II del espectro, se caracteriza por que en él se manifiestan singularidades que permiten discriminar entre una amplia gama de minerales de tipo calizo. Cada valor espectral en el rango anteriormente mencionado equivale a 10 veces el tanto por ciento de la reflectancia en una determinada longitud de onda. Estos valores han sido obtenidos como resultado de la aplicación del método de corrección atmosférica ATREM, seguido de un post-procesado mediante el método EFFORT sobre la imagen en unidades de radiancia, originalmente capturada por el sensor. Trabajo Fin de Máster 66 Sergio Sánchez Martínez Implementación de algoritmos de análisis hiperespectral en tarjetas gráficas programables (GPUs) Figura 6.4:Ubicación de la imagen real AVIRIS Cuprite sobre una fotografía aérea de la región minera Cuprite, Nevada, Estados Unidos. Una vez mostradas las imágenes sobre las que se han realizado las pruebas pasamos a describir las medidas que se han tomado en las distintas ejecuciones: Nº iteraciones: es el número de iteraciones que ha realizado el algoritmo. Tiempo (ms): el tiempo de ejecución del algoritmo medido en milisegundos. Precisión E: porcentaje de precisión estricta (se explica a continuación). Precisión P: porcentaje de precisión ponderada (se explica a continuación). Seguidamente se explica cómo se ha llevado a cabo el análisis de precisión en cada imagen. El análisis de precisión se hace en base a la imagen de pureza final del algoritmo, recordemos que esta es una matriz con tantas filas como líneas tiene la imagen y tantas columnas como samples, en la que la posición (i,j) se almacena el número de veces que se ha seleccionado al píxel (i,j) como extremo. Partiendo de esta base y de que sabemos cuál es la organización exacta de cada una de las imágenes podemos hacer dos tipos de análisis de precisión. El primero y más inmediato es el análisis de precisión estricto que considera que se ha producido un acierto en la elección de un píxel como endmember si este es realmente un endmember. El análisis de precisión estricto se lleva a cabo en las 2 imágenes sintéticas. Trabajo Fin de Máster 67 Sergio Sánchez Martínez Implementación de algoritmos de análisis hiperespectral en tarjetas gráficas programables (GPUs) Para la imagen sintética 1 el procedimiento es el siguiente: por un lado sumamos el total de incrementos que ha realizado el algoritmo, es decir el valor de cada celda de la matriz de resultados, a esta suma la llamaremos incrementos, por otro lado sumamos los valores de las celdas que estén situadas en la primera y en la última columna (que es donde sabemos que se encuentran los endmembers), a esta suma la llamaremos aciertos. La Figura 6.5 muestra este proceso: Figura 6.5:Precisión estricta PPI en imagen sintética 1. Para la imagen sintética 2 el proceso es parecido, por un lado debemos sumar la cantidad de incrementos que se han realizado en total, que llamaremos incrementos. Por otro lado sumamos los valores de las celdas cuya posición coincida con la de un píxel que sabemos que es puro. Para ver donde se sitúan estos endmembers hacemos uso de los mapas de esta imagen. Estos están en escala de grises y asignan el valor 255 a la posición de un endmember y a medida que nos alejamos de esa posición asignan valores más pequeños hasta llegar a 0 donde la presencia de ese endmember es nula. De esta manera el proceso para saber si un píxel de la matriz está en la misma posición que un endmember es el siguiente. Se leen por separado cada uno de los mapas y se almacenan en matrices diferentes. Se recorre celda a celda nuestra matriz de resultado y si en alguno de los mapas el valor de la celda cuya posición coincide con la que se está recorriendo es de 255 entonces nos encontramos en una posición donde hay un endmember y por tanto sumamos Trabajo Fin de Máster 68 Sergio Sánchez Martínez Implementación de algoritmos de análisis hiperespectral en tarjetas gráficas programables (GPUs) el valor de nuestra celda a un contador que llamaremos aciertos. Finalmente: Precisión_estricta = aciertos / incrementos * 100 El proceso se muestra en la Figura 6.6: Figura 6.6:Precisión estricta PPI en imagen sintética 2. El otro tipo de análisis de precisión es el ponderado. En el caso de la imagen sintética 1 consiste en lo siguiente: si en el análisis de precisión estricto solo sumábamos los incrementos de los píxeles que se habían considerado como endmembers (es decir los de la primera y la última columna) en éste haremos una escala de aciertos multiplicando por 100 en caso de que el acierto se haya producido en las columnas extremas y multiplicando por cero en caso de que el acierto se haya producido en las columnas centrales, de la misma forma multiplicaremos por 50 los aciertos que se hayan producido en las columnas 25 y 75. Con este sistema es lógico que se obtenga un porcentaje de acierto mayor ya que prácticamente suman todos los incrementos que se hayan producido aunque en menor medida los más alejados de los endmembers. En este método hay que diferenciar si el píxel se encuentra en la primera mitad de la imagen (entre las columnas 0 y 49) o en la segunda (entre las columnas 50 y 99). Trabajo Fin de Máster 69 Sergio Sánchez Martínez Implementación de algoritmos de análisis hiperespectral en tarjetas gráficas programables (GPUs) De esta manera para un píxel que se encuentre en la primera mitad, su ratio de acierto por el que hay que multiplicar el los incrementos que ha recibido sería: ratio_acierto=100-(columna*2) De la misma manera para un píxel que se encuentre en la segunda mitad, su ratio de acierto por el que hay que multiplicar el los incrementos que ha recibido sería: ratio_acierto=((columna+1)*2)-100 Así la variable aciertos será la suma de los incrementos de cada píxel por su ratio de acierto. Finalmente aciertos=aciertos*ratio_aciertos Precisión_ponderada = aciertos / incrementos Este proceso se muestra en la Figura 6.7: Figura 6.7:Precisión ponderada PPI en imagen sintética 1 Finalmente debemos calcular la precisión ponderada para el caso de la imagen sintética 2, para ello podemos hacer uso de los mapas de abundancias de la imagen. Mediante el uso de estos mapas podemos conocer las proporciones exactas en las que cada endmember está presente en un píxel dado. En cada píxel los valores de estos mapas de abundancias son positivos y suman 1. Para calcular la precisión en el píxel i nos situaremos en el píxel i de los mapas de abundancias y nos quedaremos con la mayor abundancia, esta pertenece al endmember que nuestro algoritmo pretendía encontrar. El valor de esta Trabajo Fin de Máster 70 Sergio Sánchez Martínez Implementación de algoritmos de análisis hiperespectral en tarjetas gráficas programables (GPUs) abundancia nos dará la precisión con la que se ha elegido el píxel i, de manera que si este valor es 1(el resto de abundancias valdrían 0) el acierto habrá sido del 100% puesto que la posición i se corresponde con la posición real de un endmember en la imagen. Si el valor es 0 (el resto de abundancias valdrían también 0, sino se habría escogido otra) el acierto sera de 0%. Si el valor es de 0.5 el acierto será del 50%. En definitiva se escala la precisión entre 0 y 100 en función del valor de la abundancia máxima en un píxel dado. aciertos=aciertos*ratio_aciertos(máxima abundancia) Precisión_ponderada = aciertos / incrementos La Figura 6.8 muestra como se establece el ratio de acierto para la precisión ponderada en la imagen sintética 2 teniendo en cuenta la abundancia máxima en un píxel Figura 6.8:Precisión ponderada PPI en imagen sintética 2 Una vez que hemos explicado como se ha llevado a cabo el análisis de resultados en cuanto a precisión, a continuación expondremos los resultados de los experimentos con las tres imágenes y compararemos las versiones CPU y GPU tanto en tiempo como en precisión. Además mostraremos los resultados obtenidos en la imagen real con ENVI que es una herramienta comercial para el estudio de imágenes y que permite ejecutar nuestro Trabajo Fin de Máster 71 Sergio Sánchez Martínez Implementación de algoritmos de análisis hiperespectral en tarjetas gráficas programables (GPUs) algoritmo sobre una imagen hiperespectral. Comencemos pues con la imagen sintética 1, la Tabla 6.1 muestra el número de iteraciones, el tiempo de ejecución, la precisión estricta, la precisión ponderada y el speedup conseguido. PPI versión C (CPU) Imagen Sintética 1 Nº Iteraciones Tiempo (ms) Precisión E Precisión P 15360 244300 45.18% 94.73% PPI versión CUDA (GPU) Imagen Sintética 1 Nº Iteraciones Tiempo (ms) Precisión E Precisión P 15360 1834 45.11% 94.78% Speedup = 133.21x Tabla 6.1: Comparación de resultados CPU y GPU para PPI en imagen sintética 1 Para comenzar hay que decir que el número de iteraciones seleccionado no es aleatorio sino que encaja perfectamente con la configuración de ejecución del kernel lanzado a la GPU, éste tiene precisamente 30 bloques de 512 hilos cada bloque, como ya sabemos cada hilo realiza una iteración, por tanto tenemos 15360 iteraciones. Siguiendo con el análisis de precisión vemos que en ambas versiones los resultados son idénticos lo que quiere decir que ambas versiones funcionan igual y son perfectamente comparables, las pequeñas variaciones en los resultados se deben básicamente a la naturaleza aleatoria del algoritmo. Recordemos que PPI utiliza un gran número de vectores construidos de forma aleatoria por así que estos resultados de precisión pueden variar de una ejecución a otra y por tanto de una versión a otra. Fijándonos en la precisión estricta podemos ver que casi uno de cada dos píxels seleccionados como extremos en una iteración se corresponde con un endmember en la imagen, es decir que se sitúa en la primera o en la última columna. Por otra parte tenemos la precisión ponderada que presenta un porcentaje muy alto (94%) lo que nos permite afirmar que en ambas versiones la mayoría de los píxels que no se han seleccionado en la primera columna o en la última se han seleccionado en columnas muy cercanas a estas, esto lo podremos corroborar viendo los resultados gráficos. En cuanto al tiempo de ejecución de ambas versiones vemos una gran diferencia, Trabajo Fin de Máster 72 Sergio Sánchez Martínez Implementación de algoritmos de análisis hiperespectral en tarjetas gráficas programables (GPUs) concretamente la versión GPU tarda 133 veces menos en ejecutarse que la versión CPU demostrando la gran potencia de cálculo que puede aprovecharse en una GPU. A continuación la Figura 6.9 muestra los resultados gráficos para esta ejecución: Figura 6.9:Resultados gráficos de PPI para la imagen sintética 1 Como puede verse en la figura, los resultados son prácticamente los mismos en ambas versiones, situándose en torno a las columnas 1 y 100 los píxels que han sido más veces seleccionados como extremos por nuestro algoritmo. Sigamos ahora con el análisis de los resultados de nuestro algoritmo para la imagen sintética 2. De la misma forma que len la imagen anterior la Tabla 6.2 muestra el número de iteraciones, el tiempo de ejecución, la precisión estricta, la precisión ponderada y el speedup conseguido. Trabajo Fin de Máster 73 Sergio Sánchez Martínez Implementación de algoritmos de análisis hiperespectral en tarjetas gráficas programables (GPUs) PPI versión C (CPU) Imagen Sintética 2 Nº Iteraciones Tiempo (ms) Precisión E Precisión P 15360 244550 42.95% 94.85% PPI versión CUDA (GPU) Imagen Sintética 2 Nº Iteraciones Tiempo (ms) Precisión E Precisión P 15360 1827 43.44% 94.98% Speedup = 133.85x Tabla 6.2: Comparación de resultados CPU y GPU para PPI en imagen sintética 2 El numero de iteraciones ha sido seleccionado con el mismo criterio que con la imagen sintética 1, encaja perfectamente con la configuración de ejecución del kernel lanzado a la GPU, éste tiene precisamente 30 bloques de 512 hilos cada bloque, como ya sabemos cada hilo realiza una iteración, por tanto tenemos 15360 iteraciones. Siguiendo con el análisis de precisión vemos que en ambas versiones los resultados son idénticos lo que quiere decir que ambas versiones funcionan igual y son perfectamente comparables, las pequeñas variaciones en los resultados se deben básicamente a la naturaleza aleatoria del algoritmo. Fijándonos en la precisión estricta podemos ver que casi uno de cada dos píxels seleccionados como extremos en una iteración se corresponde con un endmember en la imagen, es decir que se sitúa en una de las esquinas o en el centro de la imagen. Por otra parte tenemos la precisión ponderada que presenta un porcentaje muy alto (94%) lo que nos permite afirmar que en ambas versiones la mayoría de los píxels que no se han seleccionado en una de las esquinas o en el centro se han seleccionado en zonas muy cercanas a estas, esto lo podremos corroborar viendo los resultados gráficos. En cuanto al tiempo de ejecución de ambas versiones vemos una gran diferencia, concretamente la versión GPU tarda 133 veces menos en ejecutarse que la versión CPU demostrando la gran potencia de cálculo que puede aprovecharse en una GPU. A continuación la Figura 6.10 muestra los resultados gráficos para esta ejecución: Trabajo Fin de Máster 74 Sergio Sánchez Martínez Implementación de algoritmos de análisis hiperespectral en tarjetas gráficas programables (GPUs) Figura 6.10:Resultados gráficos de PPI para la imagen sintética 2 Como puede verse en la figura, los resultados son prácticamente los mismos en ambas versiones, situándose en torno a las esquinas y el centro los píxels que han sido más veces seleccionados como extremos por nuestro algoritmo. Seguidamente vamos a comparar los resultados obtenidos con la imagen real para las versiones C (CPU), CUDA (GPU) y ENVI (CPU). La Tabla Muestra los resultados de tiempo obtenidos por las tres ejecuciones con 15360 iteraciones: Imagen real C (CPU) ENVI (CPU) CUDA (GPU) Tiempo (ms) 3454534 1078000 17590 Speedup GPU 196.3x 61.28 - Tabla 6.3:Comparación de resultados para las versiones C (CPU), CUDA (GPU) y ENVI (CPU) para 15360 iteraciones. Los resultados en cuanto a velocidad son bastante buenos llegando a conseguir un speedup de 196 con respecto a la versión C. La ejecución pasa de tardar algo mas de 57 minutos a tardar 17 segundos lo que supone una gran mejora. Con respecto a la versión de ENVI el speedup no es tan alto y esto tiene su explicación: ENVI aplica un proceso (denominado Fast PPI [ 25 ]) para incrementar el número de píxeles que se seleccionan en cada proyección como puros. En la descripción original del algoritmo [ 9 ], los autores indican que para cada proyección el máximo de Trabajo Fin de Máster 75 Sergio Sánchez Martínez Implementación de algoritmos de análisis hiperespectral en tarjetas gráficas programables (GPUs) píxeles etiquetados es de 2 (los píxeles que resultan en los valores de proyección máxima y mínima). No obstante, en la versión Fast PPI de ENVI el número de píxeles seleccionados depende de un valor umbral, lo cual hace que para un mismo número de proyecciones dicha versión seleccione muchos más candidatos finalizando antes su ejecución. Una buena forma de ver el acierto que se produce con nuestro algoritmo es comparar visualmente los resultados obtenidos con los resultados que ofrece ENVI para la imagen real. A continuación mostraremos figuras con imágenes de las tres versiones para comparar los resultados con los de ENVI. La Figura 6.11 muestra los resultados visuales para las versiones C y ENVI. Figura 6.11:Resultados visuales para las versiones C y ENVI en imagen real. Como puede apreciarse en la figura ambas versiones presentan una gran similitud seleccionando píxels como extremos en las mismas regiones de la imagen. Esto nos permite afirmar que ambas versiones son perfectamente comparables y que podemos encontrar en ambas el mismo conjunto de endmembers. La Figura 6.12 muestra los resultados visuales para las versiones CUDA y ENVI. Trabajo Fin de Máster 76 Sergio Sánchez Martínez Implementación de algoritmos de análisis hiperespectral en tarjetas gráficas programables (GPUs) Figura 6.12:Resultados visuales para las versiones CUDA y ENVI en imagen real. Los resultados mostrados en la figura anterior llaman especialmente la atención porque ambas versiones detectan sus píxels extremos no sólo en las mismas regiones sino casi exactamente en las mismas coordenadas. O lo que es lo mismo los resultados son prácticamente los mismos, lo que nos permite decir que nuestra versión es igual de buena que la de ENVI e incluso la supera ya que obtiene los mismos resultados 60 veces más rápido. Para finalizar y ver un poco la precisión alcanzada con la imagen real, en la Figura 6.13 mostraremos algunos de los materiales que se han detectado tras la ejecución, comparando los resultados obtenidos en la versión CUDA con los de la versión comercial de ENVI. Trabajo Fin de Máster 77 Sergio Sánchez Martínez Implementación de algoritmos de análisis hiperespectral en tarjetas gráficas programables (GPUs) Figura 6.13:Ejemplo de materiales detectados La imagen anterior muestra un ejemplo de tres materiales que han sido detectados en las dos versiones (CUDA y ENVI) en las mismas posiciones de la imagen lo que nos permite hacernos una idea del grado de precisión alcanzado por nuestra versión del algoritmo PPI en una imagen real. Trabajo Fin de Máster 78 Sergio Sánchez Martínez Implementación de algoritmos de análisis hiperespectral en tarjetas gráficas programables (GPUs) 7 Conclusiones y líneas futuras En la presente memoria se ha realizado un estudio sobre la implementación de un algoritmo de extracción de endmembers en imágenes hiperespectrales sobre GPUs programables. Concretamente se ha implementado el algoritmo PPI en sus versiones serie (C++) y paralela (CUDA) obteniendo una serie de resultados en base al tiempo y la precisión que han sido comparados. Dichos resultados han sido obtenidos utilizando una base de datos de imágenes simuladas (construidas mediante un proceso basado en simulación de mezclas y generación de ruido) y reales, evaluando el algoritmo en términos de precisión y rendimiento computacional en arquitecturas GPU de última generación. Como resultado, se ha obtenido un detallado estudio de técnicas paralelas para tratamiento de datos hiperespectrales en GPUs, realizando contribuciones sustanciales al estado del arte en la materia dada la falta de implementaciones paralelas (en particular, en GPUs) de algoritmos de análisis de imágenes hiperespectrales en la literatura actual. Según los objetivos conseguidos a lo largo del trabajo, las principales aportaciones del presente estudio pueden resumirse un conjunto de contribuciones que se enumeran a continuación: • Después de analizar los resultados de las ejecuciones podemos decir que se ha conseguido obtener la misma precisión con la ejecución del algoritmo en serie y en paralelo, además se ha conseguido obtener un speedup de en torno a 60 con respecto a la versión de ENVI, de 133 en las imágenes sintéticas con respecto a la versión C y de 196 en la imagen real con respecto a la versión C, por lo que podemos decir que las GPUs son una buena alternativa a la hora de dar soporte de una forma barata a algoritmos paralelos. • Se ha conseguido utilizar la GPU para realizar la mayor parte de las fases del algoritmo dejando a la CPU la tarea de cargar la imagen y agrupar los resultados. • En cuanto a las precisiones obtenidas podemos decir que se ha logrado una gran efectividad aun más si comparamos los resultados aportados por ENVI en la imagen real. • Conviene destacar, llegados a este punto, las ventajas económicas que la Trabajo Fin de Máster 79 Sergio Sánchez Martínez Implementación de algoritmos de análisis hiperespectral en tarjetas gráficas programables (GPUs) implementación GPU ofrece con respecto a otras soluciones paralelas como las basadas en clusters de computadores. En concreto, mientras que el precio de una GPU de última generación se sitúa en torno a los 400 euros, el precio de un cluster puede ser mucho mayor, además de resultar en una serie de condiciones desfavorables desde el punto de vista de su implantación como módulo de procesamiento a bordo del sensor, con diferentes aspectos que pueden afectar de forma negativa al payload de la misión (peso, consumo energético, calentamiento, mantenimiento, etc.) En este sentido, las GPUs ofrecen una solución mucho más compacta, si bien es cierto que es preciso realizar un estudio detallado de las condiciones de tolerancia de las GPUs a requerimientos extremos en cuanto a consumo y sensibilidad a radiación, necesario a la hora de calibrar la adaptabilidad de esta plataforma hardware especializada a misiones reales de observación remota de la tierra. Para concluir este capítulo, planteamos algunas líneas futuras de trabajo adicionales que serían interesante perseguir en futuras ampliaciones de este trabajo: • Se ha hablado de que la principal ventaja de la implementación GPU con respecto al uso de cluster es el aspecto económico; sin embargo no se ha podido realizar ningún tipo de comparación real entre ambas soluciones en cuanto resultados y tiempos de ejecución, luego sería interesante realizar dicho estudio en el futuro. • Una alternativa interesante a la metodología propuesta viene dada por la posibilidad de implementar algoritmos paralelos en clusters de GPUs, aprovechando las ventajas de ambos paradigmas de computación paralela. • Por otra parte, y con vistas a extrapolar los resultados obtenidos en el estudio a otras aplicaciones, otra línea futura de trabajo deberá consistir en probar la implementación GPU con distintos tipos de imágenes (uno de los problemas de CUDA, es que hoy por hoy no dispone de funciones para realizar la carga de ficheros de imágenes directamente en espacios de memoria, excepto para las imágenes de tipo PPM y PGM, con lo cual se produce una carga de imagen quizá poco optimizada). • Finalmente, se plantea como futura extensión del trabajo desarrollado la Trabajo Fin de Máster 80 Sergio Sánchez Martínez Implementación de algoritmos de análisis hiperespectral en tarjetas gráficas programables (GPUs) implementación del algoritmo en otras arquitecturas paralelas como FPGAs, clusters de computación paralela o sistemas Grid, de cara a evaluar las prestaciones de dicho algoritmo en comparación otros algoritmos de extracción de endmembers también disponibles en forma de implementación paralela. Trabajo Fin de Máster 81 Sergio Sánchez Martínez Implementación de algoritmos de análisis hiperespectral en tarjetas gráficas programables (GPUs) 8 Apéndice 8.1 Programación con CUDA A continuación vamos a incluir una pequeña guía de programación con CUDA, nos centraremos en las nociones básicas necesarias para cualquier proyecto y que nos han permitido realizar este. A parte de las funciones que se describirán en este capítulo existen muchísimas otras así como un gran número de técnicas para mejorar los resultados de cualquier proyecto, pero este apartado no pretende ser una guía completa de programación. Para más información visitar: http://www.nvidia.com/object/cuda_develop.html Compilación CUDA permite utilizar las GPUs de Nvidia como coprocesadores para la ejecución de trabajos paralelos de propósito general. El código fuente de las aplicaciones CUDA consiste en una mezcla de C/C++ convencional para el código del host, a ejecutar en la(s) CPU(s), y de una extensión del lenguaje C para implementar el código del device, kernel que se ejecuta en la(s) GPU(s). El proceso de compilación CUDA se encarga de separar los tipos de código, device y host, de forma que las funciones del device son compiladas con los compiladores/ensambladores propietarios de Nvidia, mientras que el resto del código (host) se compila con cualquier compilador C/C++ presente en el host, como por ejemplo los compiladores gcc/g++ de GNU. Tras la compilación, el código objeto generado para el device se incrusta en el ejecutable creado para el host, junto con las bibliotecas del runtime de CUDA que permiten la llamada remota, ejecución y gestión de esas funciones en la GPU. Para facilitar todo el proceso de compilación, el toolkit de CUDA proporciona el frontal nvcc, que se encarga de llevar a cabo todas las tareas necesarias para generar los ejecutables. Supongamos que tenemos un código fuente que suma dos vectores en la GPU (sumavectores.cu), para compilar con nvcc sin ningún parámetro de optimización adicional: Trabajo Fin de Máster 82 Sergio Sánchez Martínez Implementación de algoritmos de análisis hiperespectral en tarjetas gráficas programables (GPUs) nvcc -o sumavectores sumavectores.cu Para compilar kernels que utilicen doble precisión hay que utilizar la opción que le indica a nvcc que genere código para GPUs con capacidad de cómputo igual o superior a 1.3. nvcc -o sumavectores sumavectores.cu -arch=sm_13 Reserva y liberación de memoria de la GPU La CPU y la GPU tienen espacios de memoria separados. El código del host (CPU) mantiene la memoria del device (GPU), permitiendo realizar funciones como reserva y liberación de memoria, copiar datos al device y desde el device o acceder a la DRAM global del device. cudaMalloc () cudaError_t cudaMalloc(void** devPtr, size_t count); Reserva count bytes de memoria lineal en el device y retorna en *devPtr un puntero a la memoria reservada. La memoria reservada se amolda a cualquier tipo de variable. La memoria no se libera. cudaMalloc() devuelve cudaErrorMemoryAllocation en caso de fallo. cudaMemset() cudaError_t cudaMemset(void* devPtr, int value, size_t count); Rellena el primer count byte de la zona de memoria apuntada por devPtr con el valor constante value. cudaMemset() devuelve cudaErrorMemoryAllocation en caso de fallo. cudaFree() cudaError_t cudaFree(void* devPtr); Libera el espacio de memoria apuntado por devPtr, el cual debe de haber sido retornado por una llamada cudaMalloc(). En caso contrario se devolverá un error. Si devPtr es 0, no se ejecuta ninguna operación. Se devuelve cudaErrorInvalidDevicePointer en caso de fallo. Ejemplo de código: int n = 1024; Trabajo Fin de Máster 83 Sergio Sánchez Martínez Implementación de algoritmos de análisis hiperespectral en tarjetas gráficas programables (GPUs) int nbytes = 1024*sizeof(int); int *d_a = 0; cudaMalloc( (void**)&d_a, nbytes ); cudaMemset( d_a, 0, nbytes); cudaFree(d_a); cudaMemcpy() cudaError_t cudaMemcpy(void* dst, const void* src, size_t count, enum cudaMemcpyKind kind); Copia count bytes desde la zona de memoria apuntada por src a la zona de memoria apuntada por dst, donde kind es uno de los siguientes modificadores: cudaMemcpyHostToHost, cudaMemcpyHostToDevice, cudaMemcpyDeviceToHost, o cudaMemcpyDeviceToDevice, y especifican la dirección de la copia. La zona de memoria no debe sobrepasarse. Ejecutando código en la GPU Los kernels son funciones C con algunas restricciones: • Solo pueden acceder a la memoria de la GPU • Deben devolver un tipo void • No pueden tener un número variable de parámetros • No son recursivos • No pueden usar variables estáticas. Los parámetros de las funciones se copian automáticamente desde la CPU a la GPU. Modificadores de funciones • __global__: se invoca desde el código del host (CPU), no se puede llamar desde el código del device y debe retornar void. • __device__: se invoca desde otras funciones GPU, no puede llamarse desde el código del host. • __host__: solo se puede ejecutar en la CPU y llamarse desde el host. • __host__ y __device__ se pueden combinar. Trabajo Fin de Máster 84 Sergio Sánchez Martínez Implementación de algoritmos de análisis hiperespectral en tarjetas gráficas programables (GPUs) Ejecución del kernel Existe un modificador para funciones C que sirve para configurar la ejecución de un kernel, esta configuración se refiere a la cantidad de hilos y bloques que se van a utilizar durante su ejecución (“<<< >>>”). kernel<<<dim3 grid, dim3 block>>>(…) La dimensión del grid viene indicada por x, y, y la dimensión del bloque de hilos por x,y,z. dim3 grid(16, 16); dim3 block(16,16); kernel<<<grid, block>>>(...); kernel<<<32, 512>>>(...); Variables CUDA integradas en el device: Todas las funciones __global__ y __device__ tienen acceso instantáneo a unas variables definidas automáticamente, estas son las siguientes: • dim3 gridDim: Dimensiones del grid en bloques (como mucho 2D). • dim3 blockDim: Dimensiones del bloque en hilos. • dim3 blockIdx: Índice del bloque dentro del grid. • dim3 threadIdx: Índice del hilo dentro del bloque. Ejemplo de kernel A continuación mostraremos como ejemplo cómo se haría la suma de dos vectores en CUDA. La Figura 8.1 muestra los 3 vectores: los vectores operandos v_A y v_B y el vector resultado v_C. Trabajo Fin de Máster 85 Sergio Sánchez Martínez Implementación de algoritmos de análisis hiperespectral en tarjetas gráficas programables (GPUs) Figura 8.1:Vectores operandos (v_A, v_B) y resultado (v_C) para suma de vectores Como puede verse el tamaño del problema es N=16. Este número, aunque pequeño viene perfecto para explicar como se lleva a cabo la partición del problema en el kernel pudiendo dividirse de forma exacta en 4 bloques de 4 hilos cada uno. La siguiente figura muestra este proceso de partición lógica del problema mostrando para cada bloque el valor de las variables de entorno: Figura 8.2:Partición lógica del problema en bloques Como puede verse en la Figura 8.2 el valor de las variables de entorno para cada bloque de ejecución es distinto. Expliquemos el valor de éstas una a una. blockIdx.x representa el identificador del bloque en ejecución, es por esto que cada bloque tiene un valor distinto para esta variable, el primer bloque tendrá el valor 0 en esta variable. En nuestro caso el valor de esta variable para el último bloque será 3 ya que tenemos 4 bloques. blockDim.x especifica la dimensión del bloque, es decir el número de hilos que forman cada bloque. Durante la ejecución de un kernel todos los bloques tienen la misma dimensión por tanto esta variable presenta el mismo valor en todos los bloques. En nuestro caso este valor es 4. Trabajo Fin de Máster 86 Sergio Sánchez Martínez Implementación de algoritmos de análisis hiperespectral en tarjetas gráficas programables (GPUs) threadIdx.x es un índice para referenciar a los hilos de un bloque, además es un índice relativo a cada bloque. Esto quiere decir que el primer hilo de todos los bloques tendrá el valor 0 en esta variable. En nuestro caso el último hilo de cada bloque tendrá el valor 3 en esta variable. Idx no es una variable de entorno sino que es una variable definida por nosotros. Resulta muy útil ya que nos permite pasar de un índice local a cada bloque para referenciar un hilo a un índice global al kernel donde cada hilo tiene un identificador único. De esta forma sólo el primer hilo del primer bloque tendrá 0 en esta variable y en nuestro caso el último hilo del último bloque tendrá el valor 15 en esta variable. Se calcula en base a las otras 3 variables de la siguiente forma: Idx=blockDim.x∗blockIdx.xthreadIdx.x Para el primer hilo del primer bloque: Idx=4∗00=0 Para el último hilo del último bloque: Idx=4∗33=15 Para el segundo hilo del tercer bloque: Idx=4∗21=9 La terminación “.x” en las variables de entorno hacen referencia a la dimensión x de la estructura grid y de los bloques, esto es así porque estas estructuras pueden tener más de una dimensión. Seguidamente la Figura 8.3 muestra el código de la función C que realiza la suma de vectores y lo compara con el kernel CUDA: Trabajo Fin de Máster 87 Sergio Sánchez Martínez Implementación de algoritmos de análisis hiperespectral en tarjetas gráficas programables (GPUs) Figura 8.3:Comparación de código de función C y kernel CUDA Herramientas de sincronización Todas las ejecuciones de los kernels son asíncronas. El control es devuelto a la CPU inmediatamente. Los kernels que se ejecuten después de llamadas CUDA previas tienen que completarse. De la misma manera la llamada cudaMemcpy síncrona por lo que el control se devuelve a la CPU después de que las copias se han completado. Las copias que comienzan después de llamadas CUDA previas tienen que completarse. Para sincronizar contamos con la siguiente llamada: cudaThreadSynchronize() cudaError_t cudaThreadSynchronize(void); La CPU espera hasta que el device ha completado todas las áreas pendientes anteriores. cudaThreadSynchronize() retorna error si una de las tareas pendientes falla. Existe otra herramienta de sincronización interna a un kernel que se utiliza para sincronizar hilos dentro de un bloque, esto resulta útil por ejemplo cuando todos los hilos de un bloque necesitan leer un dato común que debe ser calculado previamente, suele utilizarse también tras finalizar una escritura en la memoria compartida de manera que ningún hilo lea de ésta hasta que no se haya finalizado la escritura. Utilizamos la siguiente llamada: synctrheads() cudaError_t syncthreads(void); Trabajo Fin de Máster 88 Sergio Sánchez Martínez Implementación de algoritmos de análisis hiperespectral en tarjetas gráficas programables (GPUs) 9 Bibliografía 1 Chang, C.-I, Hyperspectral Imaging: Techniques for Spectral Detection and Classification, Kluwer Academic/Plenum Publishers, 2003. 2 Plaza, A., Chang, C.-I, “Impact of Initialization on Design of Endmember Extraction Algorithms,” IEEE Transactions on Geoscience and Remote Sensing, vol. 44, no. 11, pp. 3397-3407, 2006. 3 Plaza, A., Martínez, P., Pérez, R.M., Plaza, J.. “Spatial/Spectral Endmember Extraction by Multidimensional Morphological Operations”. IEEE Transactions on Geoscience and Remote Sensing, vol. 40, no. 9, pp. 2025-2041, 2002. 4 Green, R.O. et al., “Imaging spectroscopy and the airborne visible/infrared imaging spectrometer (AVIRIS),” Remote Sens. Environ., vol. 65, pp. 227–248, 1998. 5 Landgrebe, D., “Hyperspectral Image Data Analysis”, IEEE Signal Processing Magazine, vol. 19, no. 1, pp. 17-28, 2002. 6 Landgrebe, D., “Multispectral Data Analysis, A Signal Theory Perspective” http://dynamo.ecn.purdue.edu/~biehl/MultiSpec/documentation.html, 1998. 7 Antonio Plaza, Pablo Martínez, Rosa Pérez, and Javier Plaza,”Spatial/Spectral Endmember extraction by Multidimensional Morphological Operations” IEEE Transactions on Geoscience and Remote Sensing, vol. 40, no. 9, SEPTEMBER 2002. 8 Goetz, A. F., & Kindel, B. (1999). "Comparison of Unmixing Result Derived from AVIRIS, High and Low Resolution, and HYDICE images at Cuprite, NV". Proc. IX NASA/JPL Airborne Earth Science Workshop. 9 Boardman, J., Kruse, F., & Green, R. (1995). "Mapping target signatures via partial unmixing of AVIRIS data". Proc. Summaries JPL Airborne Earth Sci. Workshop, 23-26. 10 Green, R.O. y Pavri, B., “AVIRIS In-Flight Calibration Experiment, Sensitivity Analysis, and Intraflight Stability”, en Proc. IX NASA/JPL Airborne Earth Science Workshop, Pasadena, CA, 2000. Trabajo Fin de Máster 89 Sergio Sánchez Martínez Implementación de algoritmos de análisis hiperespectral en tarjetas gráficas programables (GPUs) 11 Heinz, D. y Chang, C.-I, "Fully constrained least squares linear mixture analysis for material quantification in hyperspectral imagery," IEEE Trans. on Geoscience and Remote Sensing, vol. 39, no. 3, pp. 529-545, March 2001. 12 Plaza, A., Martínez, P., Pérez, R.M., Plaza, J., “A quantitative and comparative analysis of endmember extraction algorithms from hyperspectral data,” IEEE Trans. On Geoscience and Remote Sensing, vol. 42, no. 3, pp. 650-663, March 2004. 13 Bateson, C.A., Curtiss, B., “A method for manual endmember selection and spectral unmixing,” Remote Sensing of Environment, vol. 55, pp.229–243, 1996. 14 Plaza, A., Valencia, D., Plaza, J. y Chang, C.-I, “Parallel Implementation of Endmember Extraction Algorithms from Hyperspectral Data”. IEEE Geoscience and Remote Sensing Letters, vol. 3, no. 3, pp. 334-338, July 2006. 15 Plaza, A., Plaza, J., Valencia, D., “AMEEPAR: Parallel Morphological Algorithm for Hyperspectral Image Classification in Heterogeneous Networks of Workstations.” Lecture Notes in Computer Science, vol. 3391, pp. 888-891, 2006. 16 Setoain, J., Prieto, M., Tenllado, C., Plaza, A., Tirado, F., “Parallel Morphological Endmember Extraction Using Commodity Graphics Hardware,” IEEE Geoscience and Remote Sensing Letters, vol. 43, no. 3, pp. 441-445, 2007. 17 Pérez, R.M., Martinez, P., Plaza, A., Aguilar, P.L. “Systolic Array Methodology for a Neural Model to Solve the Mixture Problem”, in: Neural Networks and Systolic Array Design. Edited by D. Zhang and S.K. Pal. World Scientific, 2002. 18 Lavenier, D., Fabiani, E., Derrien, S., Wagner, C., “Systolic array for computing the píxel purity index (PPI) algorithm on hyper spectral images”. 19 NVIDIA GeForce 8800 GPU Architecture Overview (November 2006 TB-02787001_v0.9) 20 NVIDIA CUDA Compute Unified Device Architecture – Programming Guide, Versión 1.1 (29/11/07) 21 NVIDIA CUDA. Installation and Verification on Microsoft Windows XP and Windows Vista (C Edition). (August 2008 | DU-04165-001_v01) Trabajo Fin de Máster 90 Sergio Sánchez Martínez Implementación de algoritmos de análisis hiperespectral en tarjetas gráficas programables (GPUs) 22 CUDA Technical Training. Volumen 1: Introduction to CUDA Programming (Prepared and Provided by NVIDIA. Q2 2008) 23 CUDA Technical Training. Volumen 2: CUDA Case Studies (Prepared and Provided by NVIDIA. Q2 2008) 24 Antonio Plaza, Chein-I Chang, “Clusters Versus FPGA for Parallel Processing of Hyperspactral Imagery” The International Journal of High Performance Computing Applications, vol. 22 – no. 1 – pp.1-7, 2008. 25 C.-I Chang and A. Plaza. “A Fast Iterative Algorithm for Implementation of Pixel Purity Index”, IEEE Geoscience and Remote Sensing Letters, vol. 3, no. 1, pp. 6367, January 2006. Trabajo Fin de Máster 91 Sergio Sánchez Martínez Implementación de algoritmos de análisis hiperespectral en tarjetas gráficas programables (GPUs) 10 Publicaciones del Candidato Revistas.• S. Sanchez, A. Paz, G. Martin and A. Plaza. Parallel Unmixing of Remotely Sensed Hyperspectral Images on Commodity Graphics Processing Units. Concurrency and Computation: Practice and Experience. Enviado, 2010. Capítulos de libro.• A. Plaza, G. Martin, J. Plaza, M. Zortea and S. Sanchez. Recent Developments in Spectral Unmixing and Endmember Extraction, in: Optical Remote Sensing Advances in Signal Processing and Exploitation Techniques. Edited by S. Prasad, L. Bruce and J. Chanussot, Springer, 2010. • S. Sanchez, A. Paz, G. Martin and A. Plaza. Parallel Processing of Remotely Sensed Hyperspectral Images Using Nvidia GPUs. NVidia GPU Gems, Wiley, enviado, 2010. Publicaciones online.• A. Plaza, J. Plaza and S. Sanchez. Hyperspectral Image Compression on NVidia GPUs. NVidia CUDA Zone, 2009. • A. Plaza, S. Sanchez and J. Plaza. Hyperspectral Unmixing on NVidia GPUs. NVidia CUDA Zone, 2009. Congresos.• S. Sanchez and A. Plaza. Implementación Paralela del Algoritmo Pixel Purity Index para Análisis Hiperespectral en GPUs. Jornadas de Paralelismo CEDI 2010. • S. Sanchez, G. Martin, K. Fisher, A. Plaza and C.-I Chang. GPU Implementation of Fully Constrained Linear Spectral Unmixing for Remotely Sensed Hyperspectral Data Exploitation. SPIE Optics and Photonics, Satellite Data Compression, Communication, and Processing Conference, San Diego, CA, 2010. • S. Sanchez, G. Martin and A. Plaza. Parallel Implementation of the N-FINDR Endmember Extraction Algorithm on Commodity Graphics Processing Units. IEEE International Geoscience and Remote Sensing Symposium (IGARSS'10), Hawaii, USA, 2010. • S. Sanchez, G. Martin, A. Plaza and J. Plaza. Near Real-Time Endmember Extraction from Remotely Sensed Hyperspectral Data Using NVidia GPUs. SPIE Photonics Europe, Real-Time Image and Video Processing Conference, Brussels, Belgium, 2010. • A. Plaza, J. Plaza, H. Vegas and S. Sanchez. Parallel Implementation of Trabajo Fin de Máster 92 Sergio Sánchez Martínez Implementación de algoritmos de análisis hiperespectral en tarjetas gráficas programables (GPUs) Endmember Extraction Algorithms Using NVidia Graphical Processing Units. IEEE International Geoscience and Remote Sensing Symposium (IGARSS'09), Cape Town, South Africa, 2009. • A. Plaza, J. Plaza, G. Martin and S. Sanchez. New Hyperspectral Unmixing Techniques in the Framework of the Earth Observation Optical Data Calibration and Information Extraction. (EODIX) Project. 3rd International Symposium on Recent Advances in Quantitative Remote Sensing (RAQRS'10), Valencia, Spain, 2010. • A. Plaza, J. Plaza, S. Sanchez and A. Paz. Lossy Hyperspectral Image Compression Tuned for Spectral Mixture Analysis Applications on NVidia Graphics Processing Units. SPIE Optics and Photonics, Satellite Data Compression, Communication, and Processing Conference, San Diego, 2009. Trabajo Fin de Máster 93 Sergio Sánchez Martínez