Por qué una GPU potente no acelera
Un cuello de botella es la parte de un sistema que limita la velocidad de todo lo demás. En una GPU suele ser la memoria: mover los datos cuesta más que sumarlos.
Saber si un trabajo está limitado por el cálculo o por la memoria te dice si comprar más potencia servirá de algo. En este laboratorio no hay GPU: todo son cuentas sobre un dispositivo inventado, sin medir tiempos.
Al final de esta lección
Nota
Puedo decidir si un trabajo está limitado por el cálculo o por la memoria.
Palabras clave
| Palabra | Qué significa |
|---|---|
| Ancho de banda | los bytes que la memoria mueve por segundo |
| Ritmo de cálculo | las operaciones que el dispositivo hace por segundo |
| Cuello de botella | el límite que decide cuánto tarda todo |
| Transferencia | mover datos entre dos memorias, como la del ordenador y la de la GPU |
| Acceso agrupado | hilos vecinos que leen direcciones vecinas |
Las celdas que rellenan los estudiantes quedan en blanco en esta vista previa.
Un dispositivo tiene dos límites
El primero es cuántos bytes mueve la memoria por segundo: el ancho de banda. El segundo es cuántas operaciones hace por segundo: el ritmo de cálculo.
Un trabajo necesita mover cierta cantidad de bytes y hacer cierto número de sumas. Tarda lo que pida cada límite, y manda el mayor de los dos.
Dos divisiones dicen qué límite manda
El tiempo de memoria es los bytes movidos entre el ancho de banda. El tiempo de cálculo es las sumas entre el ritmo.
Si el de memoria es mayor, el trabajo está limitado por la memoria. Si no, por el cálculo. Son cuentas con números que se dan, no tiempos medidos.
Ejemplo: 200 GB/s y 4 teraflops
| Trabajo | Bytes movidos | Sumas | Memoria | Cálculo | Limita |
|---|---|---|---|---|---|
| Suma de vectores de 1 millón | 12.000.000 | 1.000.000 | 60 µs | 0,25 µs | La memoria |
| Producto de matrices de 500 | 3.000.000 | 250.000.000 | 15 µs | 62,5 µs | El cálculo |
Las celdas que rellenan los estudiantes quedan en blanco en esta vista previa.
Calcular qué límite manda
# Un dispositivo inventado: mueve 200 GB por segundo y hace 4 billones de sumas por segundo.
ancho_banda = 200 * 10**9
ritmo = 4 * 10**12
bytes_por_numero = 4
def informe(nombre, bytes_movidos, sumas):
memoria = bytes_movidos / ancho_banda * 10**6
calculo = sumas / ritmo * 10**6
print(nombre)
print(" microsegundos de memoria:", round(memoria, 2))
print(" microsegundos de cálculo:", round(calculo, 2))
if memoria > calculo:
print(" limita: la memoria")
else:
print(" limita: el cálculo")
n = 1000000
informe("Suma de vectores de 1 millón", 3 * n * bytes_por_numero, n)
m = 500
informe("Producto de matrices de 500", 3 * m * m * bytes_por_numero, 2 * m * m * m)
# Cambia 200 por 800 en ancho_banda y vuelve a ejecutar: ¿qué trabajo cambia?
Mover los datos a la GPU también cuesta
Los datos viven primero en la memoria del ordenador y hay que copiarlos a la de la GPU, y el resultado vuelve. Esa transferencia pasa por un enlace más lento que la memoria de la GPU.
Con un enlace de 25 GB/s, copiar 12 millones de bytes cuesta 480 µs, mucho más que las sumas de la tabla. Para un trabajo pequeño, copiar puede no compensar.
Hilos vecinos deben leer direcciones vecinas
La memoria entrega los datos en líneas, por ejemplo de 16 bytes. Si cuatro hilos vecinos leen números seguidos, una sola línea les sirve a todos. Eso es un acceso agrupado.
Si cada hilo lee un número lejano, hay que traer cuatro líneas para usar cuatro números. Sobra tráfico y el trabajo va más lento.
Contar las líneas que se traen
# Cuatro hilos leen un número de 4 bytes cada uno. La memoria se trae en líneas de 16 bytes.
hilos = 4
tamano = 4
linea = 16
for paso in [1, 4]:
direcciones = []
for hilo in range(hilos):
direcciones.append(hilo * paso * tamano)
lineas = []
for direccion in direcciones:
numero = direccion // linea
if numero not in lineas:
lineas.append(numero)
print("paso", paso, "direcciones", direcciones, "líneas traídas", len(lineas))
# Con paso 1 los cuatro leen una sola línea. Con paso 4, cuatro.
Qué imprimen los dos scripts del laboratorio
El laboratorio trae steps/transfer.py y steps/coalesce.py. El primero hace estas mismas cuentas con otro dispositivo, de 500 GB/s y 10 teraflops. El segundo cuenta líneas de memoria para ocho hilos.
Las palabras que imprimen están en inglés, como limited by y lines fetched. No miden tiempos y siempre imprimen lo mismo.
Ver qué trabajo limita a qué
docker exec jupyter python3 steps/transfer.pySe muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.
Ver cuántas líneas toca cada reparto
docker exec jupyter python3 steps/coalesce.pySe muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.
Un error muy común
Advertencia
Mucha gente cree que una GPU más rápida acelera cualquier trabajo. Solo acelera los limitados por el cálculo. Si la memoria manda, hace falta mover menos bytes, agrupar los accesos o hacer más cuentas con cada byte que se trae.