Analysis of TornadoVM for GPU acceleration from Java, comparing with other GPU programming approaches.
Java Code (@Parallel)
↓
Graal JIT Compiler
↓
TornadoVM Runtime
↓
Backend (OpenCL/PTX/SPIR-V)
↓
GPU Execution
Key components:
- TaskGraph - Explicit data/task management
- @Parallel - Marks parallelizable loops
- Graal JIT - Compiles Java to GPU kernels
- Multi-backend - OpenCL, PTX, SPIR-V support
Apple M1 Pro:
- CPU Baseline: 15.2ms, 7.5 GB/s
- TornadoVM OpenCL: 1.3ms, 89 GB/s
- Speedup: 12x
NVIDIA Tesla T4:
- CPU Baseline: 25ms, 4.5 GB/s
- TornadoVM OpenCL: 0.8ms, 140 GB/s
- Speedup: 30x
Apple M1 Pro:
- java-llama.cpp (Metal): 50 tok/s
- Cyfra (Vulkan): 33 tok/s
- TornadoVM (OpenCL): 6 tok/s
Why TornadoVM is slower for LLM:
- Not optimized for transformer workloads
- Frequent CPU ↔ GPU transfers (autoregressive)
- OpenCL overhead vs Metal/CUDA
- JIT compilation time
TornadoVM excels at: Simple, large array operations TornadoVM struggles with: Complex algorithms, frequent transfers
| Feature | TornadoVM | JCuda | Babylon/HAT | Native (Metal/CUDA) |
|---|---|---|---|---|
| Platforms | Multi (OpenCL) | NVIDIA only | Future | Platform-specific |
| API Level | High (TaskGraph) | Low (CUDA API) | High (@CodeReflection) | Lowest (C/C++) |
| Performance | Good (12-30x) | Excellent | Unknown | Best |
| Ease of Use | Medium | Hard | Easy (planned) | Very Hard |
| Maturity | Beta | Stable | Experimental | Production |
| JDK Required | JDK 21 + JVMCI | Any JDK | Custom build | N/A |
✅ Good for:
- Cross-platform GPU support needed
- Array/matrix operations
- Research and experimentation
- Don't want platform-specific code
❌ Not ideal for:
- LLM inference (use java-llama.cpp)
- NVIDIA-only (use JCuda for better performance)
- Production critical path (still beta)
- Complex control flow
TaskGraph taskGraph = new TaskGraph("id")
.transferToDevice(FIRST_EXECUTION, a, b) // Upload once
.task("add", Class::method, a, b, c) // GPU kernel
.transferToHost(EVERY_EXECUTION, c); // Download each timeBenefits:
- Control when data moves
- Minimize transfers (expensive)
- Can chain multiple kernels
- Clear performance model
Comparison with CUDA:
- CUDA: Manual cudaMemcpy calls
- TornadoVM: Declarative transfers
- Both give full control
public static void add(IntArray a, IntArray b, IntArray c) {
for (@Parallel int i = 0; i < c.getSize(); i++) {
c.set(i, a.get(i) + b.get(i));
}
}How it works:
- Graal detects @Parallel loop
- Generates GPU kernel
- Each iteration → one GPU thread
- Automatically handles indexing
Limitations:
- Loop must be parallelizable
- No dependencies between iterations
- Limited to supported operations
Pros:
- Cross-platform (NVIDIA, AMD, Intel, Apple)
- Good compatibility
- Fallback to CPU possible
Cons:
- Slower than platform-specific solutions
- Driver quality varies
- Apple deprecated it (still works)
Pros:
- Better performance on NVIDIA
- Direct CUDA path
- Full GPU utilization
Cons:
- NVIDIA-only
- Experimental in TornadoVM
- Requires CUDA drivers
Pros:
- Modern, Vulkan-based
- Future direction
- Good performance potential
Cons:
- Experimental
- Limited device support
- Not production-ready
./demos/tornadovm/scripts/run-tornado.sh
# Downloads TornadoVM 2.2.0 (~100 MB)
# Extracts to build/tornadovm-sdk/Benefits:
- Zero manual setup
- Works in CI/CD
- Version locked
Challenges:
- Must use JDK 21 with JVMCI
- GraalVM CE recommended (other JDKs need JVMCI_CONFIG_CHECK=ignore)
- OpenCL/CUDA drivers required
- Backend selection (opencl vs ptx)
Unlike other demos, TornadoVM cannot be fully integrated into Gradle because:
- Complex runtime: TornadoVM SDK needed at runtime
- Special JDK: JVMCI required
- Device selection: Environment variables needed
- Multiple backends: OpenCL vs PTX vs SPIR-V
- Used by infrastructure: Benchmarks, Docker, GCP scripts
Solution:
- Gradle: Baseline demo (CPU, educational)
- Scripts: Full functionality (GPU, production)
- No single solution fits all
- OpenCL is widest but not fastest
- Platform-specific (Metal/CUDA) wins for performance
- TaskGraph is convenient
- But: Limited compared to raw CUDA
- Trade-off: Ease of use vs control
- Simple arrays: TornadoVM excels (12-30x)
- LLM inference: TornadoVM struggles (6 tok/s)
- Choose tool based on workload
- Multiple approaches (TornadoVM, JCuda, Babylon)
- No clear winner yet
- Exciting future ahead
scripts/run-tornado.sh- Full GPU demoscripts/run-gpullama3.sh- GPU LLM inferencedemos/jcuda/- CUDA-specific approachdemos/babylon/- Code Reflection approachdemos/java-llama-cpp/- Best LLM performance