You apt-installed an OpenCL package on the new target, the build compiled, the workload ran, and nobody threw an error. That is exactly the situation to be suspicious of. A ported OpenCL workload that runs without complaint may not be running on the accelerator at all — it may be quietly executing on a CPU fallback path, and the only symptom is throughput that is an order of magnitude lower than you expected. This is the failure mode teams walk into when they treat “opencl install” as a single package to install and forget. It is the same mental shortcut that produces illegal-instruction crashes when someone assumes compiler flags are portable configuration: skip the per-target re-validation step, and the environment lies to you. With compiler flags the lie is loud — a crash. With OpenCL the lie is silent, which is worse. What does “opencl install” actually put on the machine? The core misconception is that installing OpenCL means installing “OpenCL.” In practice you are installing (at least) two distinct pieces that do different jobs, and confusing them is where most porting mistakes begin. The first piece is the ICD loader — the Installable Client Driver loader, typically shipped as ocl-icd-libopencl1 or an equivalent libOpenCL.so. The loader is vendor-neutral. It does not execute a single kernel on any device. Its entire job is to look at the ICD registry (on Linux, the .icd files under /etc/OpenCL/vendors/), find the platforms that are registered there, and dispatch your API calls to whichever vendor runtime claims the device. When your program calls clGetPlatformIDs, it is talking to the loader. The second piece is the vendor-specific runtime — the actual driver that compiles and executes kernels on the target device. This is NVIDIA’s OpenCL runtime bundled with the CUDA driver, AMD’s ROCm-based OpenCL stack, Intel’s Compute Runtime (NEO), or a CPU runtime like PoCL or Intel’s CPU OpenCL. The vendor runtime is the thing that turns your OpenCL C into instructions the silicon runs. The loader finds devices; the vendor runtime runs kernels — and installing the loader alone gives you a working OpenCL API that discovers nothing. A machine can carry a perfectly healthy ICD loader and zero usable accelerators, because no vendor runtime registered a GPU platform. Your code initializes cleanly, enumerates whatever is registered — often a CPU runtime that came along for the ride — and runs there. No error. This is the crux of the whole problem and the reason the naive install-and-move-on approach is dangerous rather than merely incomplete. If you want the underlying architecture spelled out before the porting angle, our explainer on what OpenCL is and how cross-vendor parallel compute works covers the platform/device model in more depth. How do I verify with clinfo that the target device is discoverable? clinfo is the single most useful diagnostic here, and it should run before any benchmark, not after a slow run has already confused everyone. It walks the same ICD registry the loader walks and prints every platform and device it can enumerate. The question you are answering is narrow and mechanical: does the intended accelerator show up as a device, or does the platform list contain only a CPU runtime? Here is what “correct” looks like versus the two common failure shapes: Observation in clinfo Interpretation What it means for a ported workload Number of platforms = 0 No vendor runtime registered any .icd Every OpenCL call will fail to find a device; the workload errors out (this is the good failure — it is loud) Platforms > 0 but only a CPU device listed Loader is healthy; GPU vendor runtime absent or unregistered Workload runs on CPU fallback — silent, slow, no error surfaced Target GPU listed under Device Type: GPU with the expected name and driver version ICD loader resolved the vendor GPU runtime Safe to trust the device path; proceed to compiler-flag and throughput validation The middle row is the one that costs engineering days. A common pattern we see is a team that installed the loader and a CPU runtime (both pulled in as dependencies), never installed the GPU vendor runtime, and then spent a week profiling a “slow GPU workload” that was never on the GPU. The fix was a one-line install of the vendor runtime and a .icd file the loader could find. The lost week was entirely preventable with a thirty-second clinfo check. Confirm three things in the clinfo output, not one: that the platform you intend to use is present, that a device of Device Type: GPU (or the accelerator class you targeted) is enumerated under it, and that the driver/runtime version is the one you think you installed. A stale driver that reports an old version can execute kernels but produce different numerics or miss features your kernels assume. Why does a ported OpenCL workload fall back to CPU silently? OpenCL was designed to be portable, and portability here cuts both ways. Your host code typically calls clGetPlatformIDs, picks a platform, calls clGetDeviceIDs, and picks a device. If the code was written to grab “the first device” or “any device,” and the only device the loader can resolve is a CPU runtime, the program does exactly what it was told — it runs on the CPU. Nothing in the specification obligates the runtime to prefer a GPU or to warn you that no GPU was found. The abstraction that makes OpenCL portable across NVIDIA, AMD, Intel, and FPGA targets is the same abstraction that lets it quietly land on the wrong one. This is why device selection in host code and device availability in the environment have to be verified together. A workload that hard-codes CL_DEVICE_TYPE_GPU will at least fail loudly on a machine with no GPU runtime — which, again, is the outcome you want. A workload that requests CL_DEVICE_TYPE_ALL or CL_DEVICE_TYPE_DEFAULT will happily bind to whatever is there. When you move that binary to a new target, the behavior changes based entirely on what the ICD registry resolves, and that is environment state you did not carry over with the code. The parallel to CPU SIMD and compiler flags is exact. When you build with -march=native on one machine and run the binary on another, you can get an illegal-instruction crash because the target lacks the instructions the compiler emitted — a lesson worth internalizing from what SSE-versus-AVX portability teaches about performance-portable code. The OpenCL install problem is the runtime-environment twin of that build-time problem: assume the target matches the source, skip re-validation, and you get a workload doing the wrong thing on the wrong hardware. The difference is only that one screams and the other whispers. What should a per-target OpenCL environment check cover? Before any performance number is worth writing down, run the same short sequence on every new target. This is deliberately mechanical — the point is that it is the step teams skip, not that it is hard. Confirm the loader is present — ldconfig -p | grep -i opencl should show libOpenCL.so. This is the vendor-neutral loader, not the runtime. Confirm at least one vendor runtime registered — inspect /etc/OpenCL/vendors/ for .icd files. An empty directory means the loader has nothing to dispatch to. Run clinfo and read it, not just its exit code — verify platform count, that your target device is enumerated as the correct device type, and that the driver version matches what you installed. Assert the device in code, not by hope — the workload should query the device it bound to and refuse to proceed if it is not the intended accelerator. Log the device name and type at startup. Measure throughput on the confirmed device before optimizing — establish that you are on the accelerator first, then let throughput numbers drive tuning decisions. That last point is where this connects to real cost. The ROI of getting the install right is not abstract: it is the difference between an accelerator running your workload and a CPU pretending to. In configurations we have worked through during porting assessments, the throughput delta between correct GPU execution and silent CPU fallback is routinely an order of magnitude, and none of it shows up as an error (observed across TechnoLynx porting engagements; not a published benchmark). The measurable outcomes are concrete — device enumeration confirmed before benchmarking, the throughput gap between correct execution and fallback quantified, and the engineering hours you did not spend chasing a phantom-slow GPU. How does a wrong install undermine downstream compiler-flag profiling? Runtime verification is the step that has to come before compiler-flag tuning, and getting the order wrong invalidates everything downstream. If you begin profiling -O3, vectorization, or fast-math trade-offs — the kind of work covered in what compiler flags actually change for a ported inference path — while the workload is silently on CPU fallback, every measurement you take is measuring the wrong execution path. You will “optimize” host-side CPU code, watch numbers move, and attribute the movement to GPU tuning that never happened. This is why we treat runtime and environment verification as a discrete gate inside a porting and performance assessment, ahead of the compiler-flag validation step, not folded into it. The two failures rhyme — both come from assuming a build or environment is portable and skipping per-target re-validation — but they live at different layers, and profiling the second before confirming the first is a category error. The GPU services work we do treats device confirmation as a precondition, not a step you circle back to after the numbers look strange. If you are choosing between OpenCL and a vendor-locked path in the first place, the install-verification discipline is one input among several; our comparison of what CUDA and OpenCL each mean when porting AI workloads sets that decision in context. But whichever runtime you land on, the environment check does not go away — it just changes vendor. FAQ How does installing OpenCL work in practice? An OpenCL install is not one package — it is a vendor-neutral ICD loader plus one or more vendor-specific runtimes. The loader provides the API and discovers devices by reading the ICD registry; the vendor runtime is the actual driver that compiles and executes kernels on the target. Installing only the loader gives you a working API that finds no accelerator, so in practice “install OpenCL” means install the loader and the correct vendor runtime for your device. What is the difference between the OpenCL ICD loader and the vendor runtime, and which one actually executes kernels? The ICD loader (libOpenCL.so) is vendor-neutral and only dispatches API calls to whatever runtime registered a platform — it executes nothing itself. The vendor runtime (NVIDIA’s, AMD’s ROCm stack, Intel’s NEO, or a CPU runtime like PoCL) is what compiles and runs kernels on the silicon. The vendor runtime executes kernels; the loader merely finds and routes to it. How do I verify with clinfo that the target device is discoverable before benchmarking? Run clinfo and read its output rather than just its exit code. Confirm three things: the platform you intend to use is present, your target device is enumerated under it as the correct device type (e.g. Device Type: GPU), and the driver version matches what you installed. If the platform list contains only a CPU device, the GPU vendor runtime is missing or unregistered. Why does a ported OpenCL workload sometimes fall back to CPU silently instead of failing? Nothing in OpenCL obligates the runtime to prefer a GPU or warn when none is found. Host code that requests CL_DEVICE_TYPE_ALL or the default device binds to whatever the loader can resolve — often a CPU runtime pulled in as a dependency. The workload then runs correctly but on the wrong hardware, producing no error and roughly an order-of-magnitude lower throughput. What should a per-target OpenCL environment check cover before running any performance validation? Confirm the loader is present (ldconfig -p | grep opencl), confirm at least one vendor runtime registered a .icd file under /etc/OpenCL/vendors/, run and actually read clinfo, assert the bound device in code and refuse to proceed if it is wrong, then measure throughput on the confirmed device. The sequence is mechanical — the risk is skipping it, not performing it. How does getting the OpenCL install wrong on a new target undermine downstream compiler-flag profiling? If the workload is silently on CPU fallback, every compiler-flag measurement profiles the wrong execution path — you tune host-side CPU code and misattribute the movement to GPU optimization that never happened. Runtime and device verification must be a discrete gate ahead of compiler-flag validation. Profiling flags before confirming the device is a category error. Runtime verification is the unglamorous first move of any credible port: confirm the device before you trust a single number. It is the same discipline our [inference cost assessment work](Inference Cost-Cut Pack) applies before touching a compiler flag — silent CPU fallback is a named failure class, and the cheapest place to catch it is a thirty-second clinfo check, not a week of profiling the wrong hardware.