Heterogeneous Scheduling
Heterogeneous scheduling assigns work across CPUs, GPUs, and other accelerators to keep every resource productive rather than idle.
Mixed hardware
A modern node combines general-purpose CPU cores with one or more GPUs, and sometimes further accelerators. These devices differ in throughput, latency, and the kinds of work they do well. Heterogeneous scheduling is the problem of dividing an application's tasks among them so that no capable resource sits idle while another is overloaded. Done well, the CPU and GPU work in parallel; done poorly, one waits on the other.
Static versus dynamic
Static assignment fixes which device runs which work ahead of time, based on known characteristics: dense regular kernels to the GPU, irregular latency-bound logic and orchestration to the CPU. It is simple and predictable but can leave a device idle if the workload shifts. Dynamic scheduling maintains a pool of ready tasks and hands each to whichever device is free, adapting to runtime variation at the cost of scheduling overhead and less predictable data placement.
- Match task character to device: throughput work to GPU, latency work to CPU.
- Static maps are predictable; dynamic maps adapt to load.
- Data-movement cost must be weighed against compute speedup.
- Overlap CPU and GPU work rather than serializing the handoff.
The data-movement tax
The central complication is that moving data between CPU and GPU memory costs time. A task that runs faster on the GPU can still be a net loss if the transfer dominates. Good schedulers account for locality, preferring to run a task where its data already lives and batching transfers so they overlap with computation via streams. This makes heterogeneous scheduling as much a data-placement problem as a compute-assignment one.
In practice
In a Hyperion run, the GPU carries the dense field-update and particle kernels while the CPU handles I/O, mesh bookkeeping, and MPI coordination, with the two overlapped so neither stalls the other. Deciding the split, and keeping data resident where it is used, is what separates a code that uses the accelerator from one that merely owns it.