// línea open-source
← productos

gpu4j

Álgebra matricial y precisión media, en Java puro

Versión 0.4.0 · Java 17+ · Apache-2.0 · io.github.craton-co:gpu4j-core

6,3× menor
Sobrecarga de lanzamiento
fp32 y fp16
Tipos numéricos
Java 17+
Base de Java
Apache-2.0
Licencia
✦ El problema que esto elimina

Tu plataforma es Java. La etapa que domina tu tiempo de reloj no lo es.

Escribir CUDA

Ahora tenés un archivo .cu, un build que necesita nvcc, una capa JNI para marshalling, un artefacto nativo por plataforma en tu pipeline de release y un segundo lenguaje en code review. El trabajo de GPU son unos cientos de líneas. La frontera que lo rodea es el resto del año.

Levantar un servicio Python

Ahora tus datos salen de la JVM en cada llamada, y tenés un segundo despliegue, una segunda guardia de on-call, una segunda superficie de autenticación y un salto de red en medio de un bucle caliente.

Llamar a un método

gpu4j es la tercera opción. La GPU es una llamada a método tipada desde el código que ya es dueño de tus datos, y el resultado se verifica bit a bit contra lo que habría producido la JVM estándar.

✦ Multiplicar dos matrices

La integración completa.

try (GpuExecutor gpu     = GpuExecutor.open();
     GpuArray<float[]> weights = GpuArray.wrap(w);
     GpuArray<float[]> input   = GpuArray.wrap(x);
     GpuArray<float[]> output  = GpuArray.allocateFloat(m * n)) {

    GpuBlas.gemm(weights, input, output, m, n, k).get();
    float[] result = output.toHost().get();
}

Ningún kernel que escribir, ningún archivo .cu en tu build, ninguna biblioteca nativa que distribuir por plataforma, ninguna frontera de serialización entre tus datos y tu cómputo.

✦ Por qué es rápido

Dos kernels, elegidos por la forma de tu salida.

Una GPU tiene un número fijo de multiprocesadores, y una multiplicación de matrices teselada cubre la salida en bloques. Elegí una tesela demasiado grande para el problema y generás muy pocos bloques para llenar la máquina: la mayor parte del dispositivo queda ociosa mientras un puñado de bloques hace todo el trabajo.

Tesela de 128×128 · salida de 256×256

4 blocks / 30 SMs

Mejor intensidad aritmética por bloque —64 FMA por cada 16 cargas compartidas— y ni de cerca los bloques suficientes para usar la máquina.

Tesela de 64×64 · la misma salida

16 blocks / 30 SMs

Menor intensidad por bloque, cuatro veces más bloques, y a este tamaño ese es el intercambio que gana.

Tamaño de salidaTesela ganadoraMás rápida en
64×6464×6418%
128×12864×6435%
256×25664×6467%
512×51264×6413%
1024×1024128×12858%

GEMM cuadrado, sólo tiempo de kernel, una sola placa. La cifra es cuánto más rápido es el kernel ganador que el perdedor en cada tamaño: una diferencia de software, con el hardware constante.

Ninguna tesela gana en todos lados, y por eso GpuBlas incluye ambas y elige según el tamaño de la salida. Las cabezas de atención pequeñas y las capas de proyección grandes quieren respuestas opuestas. Obtenés la correcta sin pedirla.

✦ Cómo se mueven los datos

Una subida. Muchos kernels. Una lectura de vuelta.

El costo que decide si el trabajo en GPU se paga solo rara vez es la aritmética: es mover datos por el bus PCIe. gpu4j está construido para que los pesos crucen una vez y se queden, y para que sólo viajen los valores que de verdad cambian.

HOST — HEAP DE LA JVMDISPOSITIVO — VRAMfloat[] a, bordinary Java arraysGpuArray.wraphandle + residencyDevice bufferuploaded onceGpuBlas.gemmtile chosen by shapeouttoHostsubidalectura de vueltalos pesos quedan residentes — sin segunda subida
Cada paso posterior mueve sólo las activaciones.

Un paso de decodificación multiplica por los mismos pesos en cada token. Volver a subirlos costaría más que la propia aritmética, y por eso existe GpuArray.allocateFloat para los intermedios que el host nunca lee, y por eso copyFromHost escribe a través de un puntero que el dispositivo ya tiene en lugar de reservar un búfer nuevo.

✦ Lo que te da la biblioteca

Las piezas que una carga numérica realmente necesita.

Precisión media, de primera clase

Los pesos dominan la memoria de la GPU y binary16 los reduce a la mitad. Half convierte en Java puro, verificado contra la propia implementación del JDK sobre los 2³² valores float posibles, una comprobación que corre en CI. gemmHalf mantiene un acumulador en precisión completa, porque sumar K términos en precisión media pierde exactitud a escala de porcentaje con el K que usa un transformer.

Datos que se quedan en el dispositivo

Los pesos subidos una vez quedan residentes entre llamadas, así que un paso de decodificación que multiplica por los mismos pesos en cada token paga la transferencia una vez y no por token. Los intermedios no necesitan copia en el host en absoluto.

Sobrecarga de lanzamiento, eliminada

Un lanzamiento cuesta tiempo de host esté o no ocupado el dispositivo, y un solo paso de decodificación de un transformer de 1B de parámetros emite 453. Grabá la secuencia una vez y reproducila: 3,483 ms de tiempo de host para emitirla, 0,550 ms para reproducirla. El trabajo en el dispositivo es idéntico en ambos casos.

Un orden que ya entendés

GpuStream y GpuFuture son el modelo de concurrencia que ya conocés. Un stream corre en orden; los streams independientes se solapan. El stream es dueño de sus handles de envío y los libera, así que la fuga más común en esta forma de API no es expresable.

Tus propios kernels, cuando los quieras

Anotá un método estático con @GpuKernel y el runtime lo compila para el dispositivo: sin API de grafo de tareas, sin marcar @Parallel en cada bucle. Donde no puede garantizar un resultado idéntico ejecuta tu método en la CPU en lugar de adivinar, y te dice por qué.

Testeable sin GPU presente

Una biblioteca de GPU que tu CI no puede ejecutar es una biblioteca que tu CI no cubre. Un backend sustituto se instala en una línea, así que el código escrito contra esta API se puede testear en cualquier máquina.

✦ Cómo se compara

Dónde gana realmente cada opción.

EnfoqueSegunda base de códigoSegundo servicioForma de la APIBackends
gpu4jNoNoLlamadas Java tipadasNVIDIA
CUDA + JNI / JCudaSí — .cu, nvcc, artefactos nativosNoMarshalling manualNVIDIA
TornadoVMNoNo@Parallel + TaskGraphOpenCL, PTX, SPIR-V
Servicio de inferencia en PythonSíSíLlamada de redCualquiera

TornadoVM

El titular maduro, y cubre más hardware que nosotros. Si necesitás OpenCL o dispositivos que no sean NVIDIA, usalo. El intercambio es el modelo de programación: te pide expresar el trabajo como un grafo de tareas con el paralelismo anotado. gpu4j pide una llamada a método.

JCuda y afines

Control completo, y el control completo es el costo. Estás escribiendo, compilando y distribuyendo CUDA C junto a tu Java.

Un servicio Python

La respuesta correcta cuando el modelo es el producto. La respuesta equivocada cuando la parte numérica es una etapa de un pipeline JVM que es dueño de los datos a ambos lados.

✦ El motor por debajo

gpu4j es la API. CratonVM es el runtime.

Una JVM que trata a la GPU como destino de primera clase. Medido el 2026-09-07 en un host verificado como inactivo contra HotSpot C2 —la JVM de producción estándar— y TornadoVM 4.0.1, misma RTX 2060, N = 2²⁴, mejor de 5, en caliente, viaje completo host→dispositivo→host. Cada columna de una fila se ejecutó en la misma sesión, líneas base de CPU incluidas. El mismo hardware en todo momento; sólo varía el software.

Kernel de cómputo (16,7M+ elementos)CPU (HotSpot C2)TornadoVM GPUCratonVM GPUvs HotSpotvs TornadoVM
Cadena de divisiones enteras2.179 ms27 ms7 ms311x más rápido3,9x más rápido
Cadena de divisiones en doble precisión1.784 ms135 ms82 ms21,8x más rápido1,6x más rápido
128 multiplicaciones-sumas por elemento1.298 ms26 ms7 ms185x más rápido3,7x más rápido
Reducción de producto punto1.168 msno implementado2 ms584x más rápidon/d
Kernel de ray tracing, 7680x4320837,1 ms24,29 ms12,29 ms68x más rápido2,0x más rápido

Cada salida de kernel es idéntica bit a bit a la de HotSpot. La velocidad que cambia tus respuestas no es velocidad.

Un matiz que conviene decir en lugar de disimular: en la cadena de divisiones en doble precisión, las salidas de kernel de ambos runtimes coinciden exactamente (OUT0=45.40459327514974 en los dos), mientras que el epílogo del propio benchmark —una suma serial de 2²⁴ doubles— difiere en la 11ª cifra significativa, porque la suma en punto flotante no es asociativa y se reasocia. La divergencia de TornadoVM en esa fila es de 5,1×10⁻³, ocho órdenes de magnitud mayor, y esa sí es una diferencia real de división. No son la misma clase de cosa.

✦ Honestos sobre el alcance

Mejor enterarse de esto acá que en la tercera semana.

La superficie soportada

GpuBlas, GpuArray, Half y las APIs de streams y grafos están ajustadas a mano y soportadas. Usalas y obtenés el comportamiento descrito arriba.

@GpuKernel es angosto

El camino automático cubre un subconjunto de elegibilidad de Java deliberadamente angosto. No es una promesa de que código arbitrario corra en el acelerador. Donde el runtime no puede garantizar un resultado idéntico, vuelve a la CPU y dice por qué. Más lento y correcto le gana a más rápido y equivocado.

Sólo NVIDIA

El lado del dispositivo necesita CratonVM compilado con soporte de GPU y una GPU NVIDIA. En esta biblioteca no hay driver CUDA: es la superficie Java, y el trabajo sobre el hardware ocurre por debajo.

Pre-1.0, y todavía no en Central

La API puede romperse entre versiones menores hasta la 1.0; fijá tu versión. La verificación del namespace sigue pendiente, así que hoy compilás desde el código fuente o hacés mvn install localmente; las coordenadas publicadas son aquellas bajo las que se lanzará.

✦ Empezá

Traenos una carga de trabajo.

Un batch que tarda demasiado, un modelo que no podés servir de forma económica, una etapa de pipeline que domina la factura de tu clúster: lo medimos honestamente contra lo que usás hoy y te decimos si la respuesta es no.

¿Listo para asegurar
el futuro?

Solicitar Briefing