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.

TornadoVM’s Hybrid API lets a single TaskGraph mix JIT-compiled Java kernels with calls into vendor-optimized native GPU libraries—cuBLAS, cuBLASLt, cuFFT, cuDNN, cuSPARSE, and CUTLASS—without any manual memory management or host synchronization between them. A library task is a SchedulableTask node that never passes through the JIT compiler. Instead, at execution time the TornadoVM interpreter resolves every reference argument to the raw device pointer of the associated TornadoVM buffer (past the array header), then dispatches the call through a registered TornadoLibraryProvider. Scalars are passed as boxed Java objects. Because library tasks share the same TornadoVM-managed buffers as surrounding JIT tasks, data produced on the GPU by a Java kernel is consumed by cuBLAS with no copies, and vice versa.
Library tasks require the CUDA backend (make BACKEND=cuda) and an NVIDIA GPU. On OpenCL, SPIR-V, and Metal backends the library task is silently reported as UNSUPPORTED.

TaskGraph.libraryTask

The entry point for library tasks is the libraryTask method on TaskGraph, a sibling of the standard .task(...) method. It takes a string task identifier, a factory method reference that returns a LibraryTaskDescriptor, and the arguments to forward to that factory.
TaskGraph libraryTask(String id, LibraryTask factory, A1 arg1, ... AN argN)
There are 20 typed overloads covering 1 through 20 arguments. All are otherwise identical—pick the overload that matches your argument count.
import uk.ac.manchester.tornado.api.TaskGraph;
import uk.ac.manchester.tornado.api.enums.DataTransferMode;
import uk.ac.manchester.tornado.api.types.arrays.FloatArray;
import uk.ac.manchester.tornado.cublas.CuBlas;
import uk.ac.manchester.tornado.cublas.enums.CuBlasOperation;

TaskGraph graph = new TaskGraph("gemm")
    .transferToDevice(DataTransferMode.EVERY_EXECUTION, matA, matB)
    .libraryTask("sgemm", CuBlas::cublasSgemm,
            CuBlasOperation.CUBLAS_OP_N.operation(),
            CuBlasOperation.CUBLAS_OP_N.operation(),
            size, size, size,
            1.0f, matB, size, matA, size,
            0.0f, output, size)
    .transferToHost(DataTransferMode.EVERY_EXECUTION, output);
cuBLAS is column-major. For row-major FloatArray inputs, either pass the transpose operation (CUBLAS_OP_T) or swap the A/B operands as shown above so that C_cm = B_cm · A_cm computes the row-major C = A · B.

LibraryTaskDescriptor

LibraryTaskDescriptor is a simple builder that factory methods (e.g., CuBlas::cublasSgemm) construct and return to the TornadoVM runtime. You interact with it directly only when writing a custom provider. Package: uk.ac.manchester.tornado.api.common
withLibrary(String libraryName)
LibraryTaskDescriptor
Sets the provider ID string (e.g. "nvidia/cublas"). The runtime matches this string against the registered TornadoLibraryProvider.libraryName() values.
withFunction(String functionName)
LibraryTaskDescriptor
Names the specific entry point within the library (e.g. "cublasSgemm"). Passed verbatim to TornadoLibraryProvider.dispatch(String, LibraryInvocation).
withParameters(Object[] parameters)
LibraryTaskDescriptor
Sets the argument array. Reference arguments (off-heap arrays) are resolved to device pointers by the interpreter; scalars are passed as boxed values.
withAccess(Access[] access)
LibraryTaskDescriptor
Declares the data-flow access mode for each argument using Access.READ_ONLY, Access.WRITE_ONLY, or Access.READ_WRITE. The runtime uses these to schedule transfers and determine dependencies.
withTuning(Object tuning)
LibraryTaskDescriptor
Attaches library-specific tuning options (algorithm selection, workspace size, math mode, etc.) as an opaque object. Ignored by the runtime; interpreted by the matching provider at dispatch time.

Getters

getLibraryName()
String
Returns the provider ID.
getFunctionName()
String
Returns the function entry-point name.
getParameters()
Object[]
Returns the argument array.
getAccess()
Access[]
Returns the per-argument access declarations.
getTuning()
Object
Returns the tuning object, or null.

Access enum

uk.ac.manchester.tornado.api.common.Access controls how the runtime transfers data for each buffer argument.
ValueBit maskMeaning
NONE0b00No data movement (internal use).
READ_ONLY0b01Buffer is only read by the task; transferred to device but never back.
WRITE_ONLY0b10Buffer is only written; allocated on device, transferred to host after the task.
READ_WRITE0b11Buffer is both read and written; transferred both directions.
When beta != 0 in cuBLAS/CUTLASS GEMM calls, the output matrix is read before being updated. In those cases the runtime automatically marks the output as READ_WRITE and requires it to be included in transferToDevice.

Provider catalog

TornadoVM ships six built-in library providers for NVIDIA GPUs.
Provider ID: nvidia/cublas · Module: tornado-cublasDense linear algebra in FP32 and FP16, using NVIDIA BLAS. All factories are in uk.ac.manchester.tornado.cublas.CuBlas.
FactoryOperation
cublasSgemv(op, m, n, α, A, lda, x, incx, β, y, incy)y = α·op(A)·x + β·y
cublasSgemm(opA, opB, m, n, k, α, A, lda, B, ldb, β, C, ldc)C = α·op(A)·op(B) + β·C
cublasSgemmTF32(...)SGEMM using TF32 tensor cores
cublasGemmExFP16(...)FP16 inputs, tensor-core GEMM
cublasSgemmStridedBatched(...)Batched SGEMM
// SGEMM: row-major C = A·B by swapping operands for column-major cuBLAS
taskGraph.libraryTask("sgemm", CuBlas::cublasSgemm,
    CuBlasOperation.CUBLAS_OP_N.operation(),
    CuBlasOperation.CUBLAS_OP_N.operation(),
    size, size, size,
    1.0f, matrixB, size, matrixA, size,
    0.0f, output, size);

TornadoLibraryProvider SPI

The TornadoLibraryProvider interface in uk.ac.manchester.tornado.runtime.library.spi is the extension point for adding new native library bindings. Providers are discovered via java.util.ServiceLoader.
libraryName()
String
Returns the unique provider identifier (e.g. "nvidia/cublas"). Matched against LibraryTaskDescriptor.getLibraryName().
canHandle(TornadoXPUDevice device)
boolean
Returns true when the provider can execute on the given device. CUDA-only providers test instanceof TornadoNativeStreamSupport.
createContext(TornadoXPUDevice device, long executionPlanId)
LibraryContext
Creates a native execution context for the given device and execution plan (e.g. creates a cuBLAS handle and binds it to the device’s CUDA stream). Called once per (library, device, plan) tuple.
prepare(LibraryTaskDescriptor descriptor, LibraryContext context)
default void
Optional hook invoked before CUDA graph capture starts. Providers that allocate per-shape plans or workspaces (cuFFT, cuDNN, CUTLASS, cuSPARSE) create them here so dispatch is capture-safe. Must be idempotent—typically a plan-cache lookup.
dispatch(String functionName, LibraryInvocation invocation)
void
Executes the named function. Arguments are accessed via invocation.getArg(i) (scalars) and invocation.getDevicePointer(i) (device pointer for reference args).
destroyContext(LibraryContext context)
void
Releases all native resources (plans, workspace, library handle) held by the context.

LibraryInvocation

A single dispatch call, with all arguments already resolved by the TornadoVM interpreter.
getNumArgs()
int
Total number of arguments.
getArg(int index)
Object
The Java argument at index—a boxed scalar or the host-side array object for reference arguments.
getDevicePointer(int index)
long
Raw device pointer for a reference argument at index, pointing past the TornadoVM array header to the first data element.
isReference(int index)
boolean
Returns true if argument index is a buffer reference (has a device pointer) rather than a scalar.
getContext()
LibraryContext
The per-(device, plan) context created by createContext.
getTuning()
Object
Library-specific tuning object from LibraryTaskDescriptor.withTuning(...), or null.
isCapturing()
boolean
Returns true when the call is being recorded into a CUDA graph. Device allocations and host synchronization are not capture-safe; reject them here and do the sizing work in prepare() instead.

Complete cuBLAS SGEMM example

The following self-contained example adds a cuBLAS SGEMM library task to a TaskGraph between two JIT preprocessing/postprocessing steps, then executes it with a TornadoExecutionPlan.
import uk.ac.manchester.tornado.api.*;
import uk.ac.manchester.tornado.api.enums.DataTransferMode;
import uk.ac.manchester.tornado.api.annotations.Parallel;
import uk.ac.manchester.tornado.api.types.arrays.FloatArray;
import uk.ac.manchester.tornado.cublas.CuBlas;
import uk.ac.manchester.tornado.cublas.enums.CuBlasOperation;

public class HybridGemmExample {

    // JIT kernel: scale matrix elements before GEMM
    public static void scaleMatrix(FloatArray m, float factor) {
        for (@Parallel int i = 0; i < m.getSize(); i++) {
            m.set(i, m.get(i) * factor);
        }
    }

    // JIT kernel: apply ReLU after GEMM
    public static void relu(FloatArray out) {
        for (@Parallel int i = 0; i < out.getSize(); i++) {
            float v = out.get(i);
            out.set(i, v < 0 ? 0 : v);
        }
    }

    public static void main(String[] args) throws Exception {
        final int M = 512, N = 512, K = 512;

        FloatArray matA   = new FloatArray(M * K);
        FloatArray matB   = new FloatArray(K * N);
        FloatArray output = new FloatArray(M * N);

        // Host initialization
        for (int i = 0; i < M * K; i++) matA.set(i, (float) Math.random());
        for (int i = 0; i < K * N; i++) matB.set(i, (float) Math.random());

        TaskGraph graph = new TaskGraph("hybrid_gemm")
            // Transfer inputs to device once
            .transferToDevice(DataTransferMode.FIRST_EXECUTION, matA, matB)
            // JIT: scale A by 0.5 on the GPU
            .task("preprocess", HybridGemmExample::scaleMatrix, matA, 0.5f)
            // Native cuBLAS SGEMM: output = 1.0 * matB * matA + 0.0 * output
            // (operands swapped: row-major A·B == column-major B·A)
            .libraryTask("sgemm", CuBlas::cublasSgemm,
                    CuBlasOperation.CUBLAS_OP_N.operation(),
                    CuBlasOperation.CUBLAS_OP_N.operation(),
                    N, M, K,
                    1.0f, matB, N, matA, K,
                    0.0f, output, N)
            // JIT: apply ReLU on the GPU
            .task("relu", HybridGemmExample::relu, output)
            // Transfer result back
            .transferToHost(DataTransferMode.EVERY_EXECUTION, output);

        try (TornadoExecutionPlan plan = new TornadoExecutionPlan(graph.snapshot())) {
            plan.execute();
        }

        System.out.printf("output[0] = %.6f%n", output.get(0));
    }
}

Error handling

When a library task runs on a backend that has no matching provider (OpenCL, SPIR-V, Metal), the TornadoVM runtime reports the task as UNSUPPORTED in profiling output and silently skips the dispatch. Production code should either:
  • Confirm the CUDA backend is active via TornadoRuntimeProvider.getTornadoRuntime().getBackendType(0), or
  • Guard the libraryTask call path with TornadoVMBackendType.CUDA checks and provide a CPU fallback using the standard .task(...) API.
All six providers are captured into CUDA Graphs automatically on the second execution of a plan. CUDA Graphs eliminate per-launch kernel submission overhead and are particularly beneficial for chains of small library tasks (e.g. a multi-layer transformer block).

Build docs developers (and LLMs) love