If all of the devices that you are targetting come from the same platform then @Lee's response is fine (e.g. AMD GPUs + CPU, or Intel GPUs + CPU). If you expect to have to target a mix of platforms (e.g. combining Nvidia GPUs with AMD GPUs and a CPU) then your contexts cannot cross from one platform to another - at the very least, you will need one context per platform.
The options as I see it are:
- One device per context. Synchronization between devices requires copying to host memory.
- Multiple devices in one context, only using one platform. This can make it easier to share data between devices in the same context.
- Multiple devices from the same platform in one context, one context per platform. Allows you to concurrently utilise multiple platforms while giving you the benefits of having multiple devices in one context.
Option 3 gets a bit tricky in the work distribution because you have two levels at which work gets divided - between contexts/platforms and between devices. Option 1 is, IMHO, the easiest way to get access to every OpenCL device in a computer, irrespective of their platform. Option 2 is only really worthwhile if you are guaranteed to always be working on devices from one vendor (i.e. all devices in one platform). That assumption breaks pretty quickly if targeting GPU+CPU simultaneously.
Once you have worked through the above three options, you will need at least one command queue per device. You will need to compile your OpenCL kernels for every group of identical devices. Every generation of GPUs from every vendor is different. At the very least, you could end up with macros that have different deffinitions from one device to another. At worst, you could have different algorithms from one device to another (easier to handle if using Option 1 above).