Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/Deepak-Sangle/TornadoVM/llms.txt

Use this file to discover all available pages before exploring further.

WorkerGrid objects define the thread launch configuration for tasks that use KernelContext. They specify how many global threads to launch in each dimension (equivalent to the total number of GPU threads) and how those threads are grouped into workgroups — called thread blocks in CUDA or work-groups in OpenCL. A GridScheduler then maps task names to their respective WorkerGrid instances and is attached to a TornadoExecutionPlan via withGridScheduler(). If no GridScheduler is provided, TornadoVM falls back to an automatic thread-count heuristic and the local work size is chosen by the driver.
WorkerGrid objects are required when a task uses KernelContext to read thread indices. Without a GridScheduler, TornadoVM cannot determine the iteration space at compile time, which prevents correct code generation for explicit thread-ID kernels.

WorkerGrid1D

WorkerGrid1D describes a one-dimensional thread grid. It is the correct choice for element-wise array kernels where each thread handles a single scalar element along a single axis.
WorkerGrid1D(int x)
constructor
Creates a 1D grid launching x global threads in the X dimension. The Y and Z global work sizes are implicitly set to 1.
ParameterTypeDescription
xintTotal number of threads in the X dimension (global work size).
WorkerGrid1D grid = new WorkerGrid1D(1024);
setLocalWork(long x, long y, long z)
void
Sets the workgroup (thread block) size. For a 1D grid, set y = 1 and z = 1. The number of workgroups launched is globalWork[i] / localWork[i] for each dimension.
ParameterTypeDescription
xlongThreads per workgroup in the X dimension.
ylongThreads per workgroup in the Y dimension. Set to 1 for 1D grids.
zlongThreads per workgroup in the Z dimension. Set to 1 for 1D grids.
grid.setLocalWork(256, 1, 1); // 256 threads per block, 4 blocks total
setGlobalWork(long x, long y, long z)
void
Updates the global work size after construction. Useful when the problem size changes between executions without rebuilding the scheduler.
grid.setGlobalWork(2048, 1, 1); // resize to 2048 threads
The global work size must be evenly divisible by the local work size. If your problem size is not a power of two, pad it to the next multiple of your chosen workgroup size and add a bounds check inside the kernel (if (idx >= N) return;).

WorkerGrid2D

WorkerGrid2D describes a two-dimensional thread grid. Use it for matrix, image, or any 2D domain decomposition where each thread handles one (row, col) element.
WorkerGrid2D(int x, int y)
constructor
Creates a 2D grid with x threads in the X dimension and y threads in the Y dimension. The Z global work size is implicitly 1.
ParameterTypeDescription
xintTotal threads in the X dimension (e.g., number of columns).
yintTotal threads in the Y dimension (e.g., number of rows).
WorkerGrid2D grid = new WorkerGrid2D(1024, 1024); // 1M threads for a 1024×1024 matrix
setLocalWork(long x, long y, long z)
void
Sets the 2D workgroup dimensions. Typical values are 16×16 or 32×8 depending on the access pattern and the cache structure of the target GPU.
grid.setLocalWork(16, 16, 1); // 256 threads per block arranged as a 16×16 tile
setGlobalWork(long x, long y, long z)
void
Overrides the global work size for both dimensions.

WorkerGrid3D

WorkerGrid3D describes a three-dimensional thread grid. Use it for volumetric computations, 3D stencils, or any problem that maps naturally to a three-axis decomposition.
WorkerGrid3D(int x, int y, int z)
constructor
Creates a 3D grid with independent work sizes along all three axes.
ParameterTypeDescription
xintTotal threads in the X dimension.
yintTotal threads in the Y dimension.
zintTotal threads in the Z dimension.
WorkerGrid3D grid = new WorkerGrid3D(64, 64, 64); // 3D volume, 262144 threads
setLocalWork(long x, long y, long z)
void
Sets the 3D workgroup dimensions. Ensure that x * y * z does not exceed the device’s maximum threads-per-block limit (typically 1024).
grid.setLocalWork(8, 8, 4); // 256 threads per block in a 3D arrangement
setGlobalWork(long x, long y, long z)
void
Overrides all three global work sizes simultaneously.

Common WorkerGrid Methods (all variants)

All three WorkerGrid classes inherit from AbstractWorkerGrid, which provides the following shared accessors:
getGlobalWork()
long[]
Returns a 3-element array [x, y, z] of the current global work sizes.
getLocalWork()
long[]
Returns a 3-element array [x, y, z] of the current local workgroup sizes, or null if setLocalWork() has not been called.
getNumberOfWorkgroups()
long[]
Returns a 3-element array of the computed workgroup counts: globalWork[i] / localWork[i] for each dimension.
getGlobalOffset()
long[]
Returns the 3-element global offset array [x, y, z]. The offset shifts the starting global ID in each dimension. Defaults to [0, 0, 0].
setGlobalOffset(long x, long y, long z)
void
Sets the global offset for each dimension. The offset is added to the base global thread ID computed from the workgroup index, enabling sub-range dispatch within a larger iteration space.
setLocalWorkToNull()
void
Clears the local work size, reverting to driver-chosen workgroup dimensions.
setNumberOfWorkgroupsToNull()
void
Clears the precomputed workgroup-count array. The count is automatically recomputed the next time setLocalWork() is called.

GridScheduler

GridScheduler maintains a map from fully-qualified task names ("graphName.taskId") to WorkerGrid instances. It is passed to TornadoExecutionPlan.withGridScheduler() to activate explicit thread scheduling.
GridScheduler()
constructor
Creates an empty grid scheduler. Use addWorkerGrid() to populate it.
GridScheduler sched = new GridScheduler();
sched.addWorkerGrid("myGraph.matmul", grid2D);
sched.addWorkerGrid("myGraph.bias",   grid1D);
GridScheduler(String taskName, WorkerGrid workerGrid)
constructor
Creates a grid scheduler pre-populated with a single task-to-grid mapping. This is the most common constructor for single-task workloads.
ParameterTypeDescription
taskNameStringFully qualified name: "graphName.taskId".
workerGridWorkerGridThe WorkerGrid1D, WorkerGrid2D, or WorkerGrid3D to assign.
GridScheduler sched = new GridScheduler("computeGraph.t0", new WorkerGrid1D(4096));
addWorkerGrid(String taskName, WorkerGrid workerGrid)
void
Registers an additional task-to-grid mapping after construction.
ParameterTypeDescription
taskNameStringFully qualified task name.
workerGridWorkerGridWorker grid to assign to this task.
get(String taskName)
WorkerGrid
Retrieves the WorkerGrid registered for a given task name, or null if none is registered.
contains(String taskScheduleName, String taskName)
boolean
Returns true if a grid is registered for the composite key taskScheduleName + "." + taskName.
keySet()
Set<String>
Returns the set of all registered task names.

WorkerGrid Dimension Summary


Global vs. Local Work Size Relationship

The number of workgroups launched in each dimension is derived automatically:
numWorkgroups[i] = globalWork[i] / localWork[i]
This means globalWork[i] must be a multiple of localWork[i] in every dimension. If you do not call setLocalWork(), the GPU driver selects a default workgroup size (often 64 or 128 for 1D, 16×16 for 2D).
ScenarioglobalWorklocalWorkworkgroups
1D, 4096 threads, 256 per block[4096, 1, 1][256, 1, 1]16
2D matrix 512×512, 16×16 tiles[512, 512, 1][16, 16, 1]32×32 = 1024
3D volume 64×64×64, 8×8×4[64, 64, 64][8, 8, 4]8×8×16 = 1024

Complete 2D Matrix Multiply Example

The global work sizes in each dimension must be exactly divisible by the corresponding local work sizes. A mismatch (e.g., globalWork=1000, localWork=256) results in a runtime exception on most backends. Always round up your problem size and add a bounds-check in the kernel.
import uk.ac.manchester.tornado.api.*;
import uk.ac.manchester.tornado.api.enums.DataTransferMode;

public class MatMul2D {

    // Tiled matrix multiplication using KernelContext and local memory
    public static void matmul(KernelContext ctx,
                               float[] a, float[] b, float[] c, int N) {
        int row = ctx.globalIdy;
        int col = ctx.globalIdx;
        int localRow = ctx.localIdy;
        int localCol = ctx.localIdx;
        int tileSize = ctx.localGroupSizeX;

        float[] tileA = ctx.allocateFloatLocalArray(16 * 16);
        float[] tileB = ctx.allocateFloatLocalArray(16 * 16);

        float sum = 0.0f;
        int numTiles = N / tileSize;

        for (int t = 0; t < numTiles; t++) {
            // Load tile of A and B into shared memory
            tileA[localRow * tileSize + localCol] = a[row * N + (t * tileSize + localCol)];
            tileB[localRow * tileSize + localCol] = b[(t * tileSize + localRow) * N + col];
            ctx.localBarrier();

            // Compute partial dot product for this tile
            for (int k = 0; k < tileSize; k++) {
                sum += tileA[localRow * tileSize + k] * tileB[k * tileSize + localCol];
            }
            ctx.localBarrier();
        }

        c[row * N + col] = sum;
    }

    public static void main(String[] args) throws Exception {
        int N = 512;
        float[] a = new float[N * N];
        float[] b = new float[N * N];
        float[] c = new float[N * N];
        // ... initialise a and b ...

        KernelContext ctx = new KernelContext();

        // 2D grid: N×N global threads, 16×16 workgroup tiles
        WorkerGrid2D grid = new WorkerGrid2D(N, N);
        grid.setLocalWork(16, 16, 1);
        GridScheduler sched = new GridScheduler("mm.matmul", grid);

        TaskGraph tg = new TaskGraph("mm")
            .transferToDevice(DataTransferMode.FIRST_EXECUTION, a, b)
            .task("matmul", MatMul2D::matmul, ctx, a, b, c, N)
            .transferToHost(DataTransferMode.EVERY_EXECUTION, c);

        try (TornadoExecutionPlan plan = new TornadoExecutionPlan(tg.snapshot())) {
            plan.withGridScheduler(sched).execute();
        }
    }
}

Kernel Context

Read thread IDs, allocate local memory, and issue barriers inside your kernel.

Execution Plan

Attach the GridScheduler to the execution plan with withGridScheduler().

Build docs developers (and LLMs) love