“We installed the OpenCL package, there were no errors, and the code runs — so it’s on the GPU, right?” That sentence is where most OpenCL setup problems begin, and the answer is usually no. A clean install with no error messages is not evidence that your kernels touch the GPU at all. It is entirely possible to have a working OpenCL program that quietly executes on a CPU device — slower by a wide margin, and invisible until someone profiles it. The reason is structural, not accidental. OpenCL on Ubuntu is not a single package. It is a small stack of independent pieces — an installable client driver (ICD) loader, one or more vendor runtimes, and the underlying kernel driver — and apt install on the wrong package satisfies the linker without ever exposing your GPU. The workload runs because the loader found a device. It just was not the device you wanted. Why a successful OpenCL install can still miss the GPU The failure class here is a silent device-selection error. Your code compiles, links against libOpenCL.so, enumerates at least one platform, picks a device, and runs. Nothing throws. But if the only runtime the loader can see is a CPU implementation — POCL, or an Intel CPU runtime — then clGetDeviceIDs returns a CPU device and your parallel kernel executes on host threads. For a genuinely parallel workload, that gap is not marginal. Moving a data-parallel kernel from a CPU fallback to the intended GPU commonly changes throughput by roughly one to two orders of magnitude — a 10-100x range for well-suited workloads (observed across GPU-porting engagements; the exact multiple depends entirely on the kernel’s arithmetic intensity and memory pattern, so treat it as a planning range, not a benchmark). The point is not the number. The point is that a misconfigured environment produces working-but-slow code, and working-but-slow is the hardest failure to catch because everything looks fine. The teams that get burned by this tend to treat the environment as done the moment the build succeeds. Then they spend two days profiling kernels, tuning work-group sizes, and staring at occupancy — chasing a performance problem that is actually an environment problem. Getting the stack right first is cheap. Debugging it later, disguised as a kernel-optimisation problem, is expensive. What actually gets installed: loader, runtime, driver The single most useful thing you can internalise about OpenCL on Linux is that three distinct things carry the name “OpenCL,” and you need all three, from potentially different sources. The ICD loader (ocl-icd-libopencl1 on Ubuntu, providing libOpenCL.so). This is the thin dispatch library your application actually links against. It does not compute anything. Its entire job is to read the ICD registry in /etc/OpenCL/vendors/ and route API calls to whatever vendor runtimes are registered there. One loader serves every vendor on the machine. The vendor runtime (the ICD). This is the real implementation for a specific device family — NVIDIA’s OpenCL runtime shipped inside the CUDA/driver package, AMD’s ROCm OpenCL runtime, Intel’s Compute Runtime (NEO) for its GPUs, or POCL as a portable CPU/LLVM-based fallback. Each installs a .icd file into /etc/OpenCL/vendors/ pointing at its shared library. This is the piece that determines whether your GPU appears at all. The kernel driver. The vendor runtime talks to hardware through the kernel driver — the proprietary nvidia module, or amdgpu, or i915/xe. If the driver is missing or mismatched with the runtime, the runtime loads but enumerates no usable device. The mental model worth holding: the loader is a switchboard, the runtime is the operator who knows a specific hardware line, and the driver is the physical wire. apt install ocl-icd-opencl-dev gives you the switchboard and headers. It does not give you an operator for your GPU. That is the exact trap. If you want the full landscape of loaders, headers, and SDK components before you touch a machine, our walkthrough of getting the OpenCL runtime and SDK in place for a portable compute path covers the pieces in more depth. Installing OpenCL for NVIDIA, AMD, and Intel GPUs on Ubuntu The loader and headers are vendor-neutral and identical across setups. Install them once: sudo apt update sudo apt install ocl-icd-libopencl1 ocl-icd-opencl-dev clinfo clinfo is not optional tooling here — it is your verification instrument, and it should go on before anything else. Everything after this depends on which GPU you have. Decision table: which runtime for which GPU Your GPU Vendor runtime to install Registers ICD via Kernel driver Common trap NVIDIA Bundled with the NVIDIA driver / CUDA toolkit (nvidia-driver-<ver>) nvidia.icd proprietary nvidia module Installing nvidia-opencl-icd alone without the matching driver → runtime present, no device AMD (recent) ROCm OpenCL runtime (rocm-opencl-runtime) amdocl64.icd amdgpu (kernel) + ROCm stack Consumer cards often unsupported by ROCm; amdgpu-pro is a separate path Intel Arc / recent iGPU Intel Compute Runtime — intel-opencl-icd intel.icd i915 or xe Older beignet/intel-opencl names are obsolete; use the NEO package No GPU / CPU fallback POCL (pocl-opencl-icd) pocl.icd none Installing POCL alongside a GPU runtime silently adds a CPU device that code may pick first Two things about that last row deserve emphasis. First, POCL is genuinely useful — it gives you a portable CPU device for development on machines with no GPU. Second, it is the single most common cause of the silent-fallback failure: with both a GPU runtime and POCL installed, clGetDeviceIDs(CL_DEVICE_TYPE_DEFAULT, …) may hand back the CPU device depending on enumeration order, and your “GPU” workload never leaves the host. If you install POCL, your code must select CL_DEVICE_TYPE_GPU explicitly rather than trusting the default. The vendor-specific mechanics — driver stack quirks, oneAPI/SYCL interplay on Intel, ROCm support matrices — are their own topic. For the Intel side specifically, what the Intel Arc Linux driver stack actually supports today is worth reading before you buy hardware, because “supported by the runtime” and “supported by the kernel driver” are not the same date. How do I verify with clinfo that OpenCL sees my GPU? This is the step that separates a real install from a hopeful one. Run: clinfo You are not skimming for “no errors.” You are reading for three specific facts: Number of platforms is at least 1, and the platform name matches your vendor (NVIDIA CUDA, AMD Accelerated Parallel Processing, Intel(R) OpenCL Graphics). If you installed POCL too, you will see it as an additional platform — expected, but now you know it is there. Under your GPU platform, Device Type reads GPU — not CPU. This is the assertion that fails silently in code and that clinfo makes visible. Device Name is your actual card, and figures like Max compute units, Global memory size, and Max clock frequency line up with the hardware. A device reporting your CPU’s core count and system RAM is a CPU pretending to be your compute target. A quick self-check rubric before you write a line of kernel code: clinfo lists a platform whose name matches my GPU vendor That platform exposes a device with Device Type: GPU Device Name is my physical card, and memory/compute-unit counts match its spec If POCL is installed, my application explicitly requests CL_DEVICE_TYPE_GPU Number of platforms and the ICD files in /etc/OpenCL/vendors/ agree with each other If clinfo reports zero platforms, the loader found no .icd files — the vendor runtime is missing or unregistered. If it reports a platform but no device, the runtime loaded but the kernel driver is missing or version-mismatched. Those are two different fixes, and clinfo’s output tells you which one you are looking at. That diagnostic separation is why we treat clinfo as the first-class verification step, not a formality. Why does my OpenCL code run but stay slow? Assume clinfo looks correct and the program still crawls. Now the question shifts from “is the GPU visible” to “is my code actually using it,” and there are three usual suspects. The first is default-device selection, already covered: if the code calls for the default device and a CPU runtime is present, it may bind to the CPU. Fix it by enumerating platforms, filtering for CL_DEVICE_TYPE_GPU, and asserting on the result rather than falling through. The second is that the workload genuinely is not GPU-bound. If the kernel spends its time waiting on host-to-device transfers over PCIe, or the arithmetic intensity is too low to amortise launch overhead, the GPU is engaged but starved. That is a real optimisation problem, not an environment one — and it is exactly the kind of thing you cannot diagnose until the environment is confirmed clean. The broader question of where the cost actually lives in an inference path is the right lens once you have ruled out device misbinding. The third is silent context creation on the wrong device inside a framework or library that “helpfully” picks a device for you. When this issue arises in practice, the fastest confirmation is to log the device name your context was built on at runtime and compare it against what clinfo reports. If they disagree, you have found it. A minimal device-selection guard The cheapest insurance against silent fallback is to make your code refuse to run on the wrong device. In practice that means querying CL_DEVICE_TYPE_GPU specifically, reading back CL_DEVICE_NAME, and failing loudly if no GPU device is found rather than degrading to whatever the loader offers. A workload that aborts with “no GPU device available” on a misconfigured node is infinitely easier to debug than one that runs at CPU speed and reports success. We build this guard into every OpenCL host program as a matter of habit — it converts an invisible performance regression into a visible startup error. How the environment connects to profiling the real bottleneck Getting the OpenCL stack right is not the optimisation work. It is the precondition for it. A GPU performance audit — profiling kernels, measuring occupancy, reasoning about memory bandwidth — is only valid if the workload is actually running where you think it is. Profile a CPU-fallback workload and every conclusion you draw is about the wrong device. Every recommendation is noise. That is why the sequence matters: confirm the correct platform and device are enumerated, assert device visibility in code, then profile. Skipping the first two steps does not save time; it moves the debugging cost downstream and disguises it as a harder problem than it is. If you are provisioning several nodes rather than one workstation, our guide to installing OpenCL on Ubuntu for GPU compute nodes covers the repeatable-provisioning angle, and the broader GPU engineering practice page frames where environment verification sits in a performance engagement. FAQ What does working with installing OpenCL on Ubuntu involve in practice? Installing OpenCL on Ubuntu means assembling a small stack, not running one command: the ICD loader (ocl-icd-libopencl1) that your app links against, a vendor runtime that implements OpenCL for your specific GPU, and the matching kernel driver. In practice, apt install succeeds and your code links even when no GPU runtime is present — so a clean install is not proof the GPU is engaged until you verify device enumeration. What is the difference between the OpenCL ICD loader, the vendor runtime, and the driver — and which do I need? The ICD loader is a thin dispatch library that routes API calls to whatever runtimes are registered in /etc/OpenCL/vendors/; it computes nothing. The vendor runtime (NVIDIA, ROCm, Intel NEO, or POCL) is the actual implementation for a device family and determines whether your GPU appears. The kernel driver (nvidia, amdgpu, i915/xe) is what the runtime uses to reach hardware. You need all three, and they can come from different packages. How do I install OpenCL for NVIDIA, AMD, or Intel GPUs on Ubuntu without conflicts? Install the vendor-neutral loader and headers once (ocl-icd-opencl-dev, clinfo), then add the runtime for your GPU: NVIDIA’s runtime ships with its driver/CUDA package, AMD uses rocm-opencl-runtime, and recent Intel GPUs use intel-opencl-icd (the NEO Compute Runtime). Conflicts usually come from installing POCL alongside a GPU runtime, which adds a CPU device that default selection may pick first. How do I verify with clinfo that OpenCL sees my GPU rather than falling back to the CPU? Run clinfo and read three things: that a platform matching your GPU vendor exists, that it exposes a device whose Device Type is GPU (not CPU), and that the Device Name plus memory and compute-unit figures match your physical card. Zero platforms means the runtime is missing or unregistered; a platform with no device means the kernel driver is missing or mismatched. Why does my OpenCL code run but stay slow — and how do I confirm it’s actually using the GPU? The usual causes are default-device selection binding to a CPU runtime, a workload that is transfer- or launch-overhead bound rather than compute-bound, or a framework silently creating its context on the wrong device. Confirm by logging the device name your context was built on at runtime and comparing it to clinfo; if they disagree, you have found the misbinding. Requesting CL_DEVICE_TYPE_GPU explicitly and failing loudly is the reliable fix. How does getting the OpenCL environment right connect to profiling the real bottleneck later? A performance audit is only valid if the workload runs on the device you think it does. Profiling a CPU-fallback workload produces conclusions about the wrong hardware, so verifying platform and device enumeration and asserting device visibility in code are the preconditions for any meaningful kernel-level profiling. Do them first; skipping them moves the debugging cost downstream and disguises an environment problem as an optimisation one. The honest close here is a question, not a checklist: before your next profiling session, can you point to the line of output — from clinfo or from your own runtime logging — that proves the kernel ran on the GPU and not the host? If you cannot, that is the first thing to fix, and it is almost always the cheapest.