Estoy trabajando en el algoritmo de GPU que se supone que debe hacer muchos cálculos modulares. Particularmente, varias operaciones en matrices en un campo finito que a la larga se reducen a operaciones primitivas como: (a * b - c * d) mod m o (a * b + c) mod m donde a, b, c y d son residuos módulo m y m es un primo de 32 bits.aritmética modular en la gpu
A través de la experimentación, aprendí que el rendimiento del algoritmo está limitado en gran medida por una aritmética modular lenta porque las operaciones de módulo entero (%) y división no son compatibles con la GPU en el hardware.
Agradezco si alguien me puede dar una idea de cómo realizar cálculos modulares eficientes con CUDA?
Para ver cómo se implementa en CUDA, yo uso el siguiente fragmento de código:
__global__ void mod_kernel(unsigned *gout, const unsigned *gin) {
unsigned tid = threadIdx.x;
unsigned a = gin[tid], b = gin[tid * 2], m = gin[tid * 3];
typedef unsigned long long u64;
__syncthreads();
unsigned r = (unsigned)(((u64)a * (u64)b) % m);
__syncthreads();
gout[tid] = r;
}
no se supone que este código funcione, sólo quería ver cómo la reducción modular está implementado en CUDA.
Cuando desmonte este con cuobjdump --dump-Sass (! Njuffa gracias por el consejo), veo lo siguiente:
/*0098*/ /*0xffffdc0450ee0000*/ BAR.RED.POPC RZ, RZ;
/*00a0*/ /*0x1c315c4350000000*/ IMUL.U32.U32.HI R5, R3, R7;
/*00a8*/ /*0x1c311c0350000000*/ IMUL.U32.U32 R4, R3, R7;
/*00b0*/ /*0xfc01dde428000000*/ MOV R7, RZ;
/*00b8*/ /*0xe001000750000000*/ CAL 0xf8;
/*00c0*/ /*0x00000007d0000000*/ BPT.DRAIN 0x0;
/*00c8*/ /*0xffffdc0450ee0000*/ BAR.RED.POPC RZ, RZ;
Tenga en cuenta que entre las dos llamadas a bar.red.popc hay una llame al procedimiento 0xf8 que implementa algún algoritmo sofisticado (alrededor de 50 instrucciones o incluso más). No sorprende que la operación de mod (%) sea lenta
¿En qué plataforma estás? Una GPU Fermi de alta gama probablemente le proporcione el mejor rendimiento entre las tarjetas de envío. ¿Todos los operandos son enteros de 32 bits? Desearía que los operandos fueran del tipo "unsigned int" para obtener el mejor rendimiento. El compilador genera un código muy eficiente para división y módulo con divisor fijo, puede verificar el SASS (código de máquina) con cuobjdump --dump-sass. Puede usar funciones con plantillas con divisores constantes si solo usa algunos modulos. Para las GPU basadas en Fermi, la división y el módulo con divisores variables son competitivos en el desempeño con soluciones basadas en hardware. – njuffa
Hola Norbert, copie y pegue esto en la sección de respuestas donde pertenece :-) – ArchaeaSoftware
@njuffa sí Tengo una tarjeta gráfica basada en Fermi y todos los números son de 32 bits. El punto es que los módulos no son fijos: cada bloque resuelve el mismo problema para un módulo diferente (o a veces yo uso 2da dimensión para procesar 2-3 módulos por bloque si el tamaño del problema es demasiado pequeño).En realidad, la idea es usar la GPU para resolver un problema para diferentes primos y luego reconstruir el resultado en el lado del host. –