Use this file to discover all available pages before exploring further.
TornadoVM is designed to target any heterogeneous hardware that a JVM process can see — discrete GPUs, integrated GPUs, multi-core CPUs exposed through OpenCL, and anything else a backend driver enumerates. When a TaskGraph contains independent tasks (no shared data dependencies between them), TornadoVM can run each task on a different device, either sequentially or truly concurrently. This makes it possible to saturate all available accelerators simultaneously: one task processes a red colour channel on an NVIDIA GPU while another processes a green channel on an Intel CPU, for example. Understanding how to list, select, and assign devices is fundamental to extracting peak hardware utilisation from TornadoVM.
The quickest way to see what TornadoVM can access is the --devices flag:
tornado --devices
Example output on a system with one GPU and two CPU OpenCL devices:
Number of Tornado drivers: 1Driver: OpenCL Total number of OpenCL devices : 3 Tornado device=0:0 (DEFAULT) OPENCL -- [NVIDIA CUDA] -- NVIDIA GeForce RTX 3070 Global Memory Size: 7.8 GB Local Memory Size: 48.0 KB Workgroup Dimensions: 3 Total Number of Block Threads: [1024] Max WorkGroup Configuration: [1024, 1024, 64] Device OpenCL C version: OpenCL C 1.2 Tornado device=0:1 OPENCL -- [Intel(R) OpenCL] -- 13th Gen Intel(R) Core(TM) i7-13700 Global Memory Size: 62.5 GB Local Memory Size: 32.0 KB Workgroup Dimensions: 3 Total Number of Block Threads: [8192] Max WorkGroup Configuration: [8192, 8192, 8192] Device OpenCL C version: OpenCL C 3.0 Tornado device=0:2 OPENCL -- [Intel(R) OpenCL] -- Intel(R) Core(TM) CPU Global Memory Size: 62.5 GB Local Memory Size: 256.0 KB Device OpenCL C version: OpenCL C 1.2
The notation driverIndex:deviceIndex (e.g. 0:0, 0:1) is used throughout TornadoVM to identify a device uniquely.
When a TaskGraph contains multiple tasks, you can route each individual task to a different device using the overloaded .withDevice(String taskName, TornadoDevice device) form. The task name uses the format "graphName.taskName".
TornadoDevice gpu = TornadoExecutionPlan.getDevice(0, 0);TornadoDevice cpu = TornadoExecutionPlan.getDevice(0, 1);try (TornadoExecutionPlan plan = new TornadoExecutionPlan(itg)) { plan.withDevice("blur.red", gpu) // red channel → GPU .withDevice("blur.green", cpu) // green channel → CPU .withDevice("blur.blue", gpu) // blue channel → GPU .execute();}
By default, TornadoVM runs each independent task sequentially — one device finishes before the next starts. This mode is useful for debugging and profiling individual tasks.
Sequential (default)
Concurrent (recommended for throughput)
Tasks assigned to different devices still run one after the other in the order they appear in the TaskGraph.
All bytecodes execute from the main Java thread. One device is always idle while the other is working.
Adding --enableConcurrentDevices (CLI) or calling .withConcurrentDevices() (API) spawns a separate Java thread per device. Tasks on different devices truly overlap.
// Programmatic concurrent executiontry (TornadoExecutionPlan plan = new TornadoExecutionPlan(itg)) { plan.withDevice("blur.red", gpu) .withDevice("blur.green", cpu) .withConcurrentDevices() // spawn one thread per device .execute();}
In concurrent mode, TornadoVM spawns one interpreter instance per device, each running inside a thread-pool thread. The debug flag --printBytecodes will print the bytecode stream for each interpreter instance labelled by the device and thread name (e.g. pool-1-thread-1).
TornadoVM includes a dynamic reconfiguration mode (DRMode) that can evaluate execution across all available devices. This feature is not enabled automatically and is maintained as a research capability rather than a core runtime feature.DRMode is an enum in uk.ac.manchester.tornado.api with two values:
DRMode.SERIAL — The runtime evaluates all devices sequentially (compiles and runs each ImmutableTaskGraph one after another) before making a device-switching decision.
DRMode.PARALLEL — The runtime evaluates all devices in parallel, mapping each physical accelerator to a separate Java thread.
Dynamic reconfiguration only explores single-device execution paths. It does not distribute tasks across multiple devices simultaneously.
Independent tasks (no shared data) routed to different devices
Sequential and concurrent multi-device execution modes
Per-task device selection via API or JVM system properties
Mix of GPU and CPU tasks in the same execution plan
Not Supported
Tasks with shared data dependencies across devices (must run on a single device)
Batch processing across multiple devices (single device only)
Dynamic reconfiguration with multi-device task distribution
Automatic dependency analysis between tasks on different devices
When designing for multi-device execution, structure your TaskGraph so that independent work items are expressed as separate tasks. The natural decomposition for image processing (one task per colour channel, one task per image tile) or physics simulation (one task per particle subsystem) maps cleanly onto independent multi-device scheduling.