第31章 Multiple_device_context - 多设备上下文测试

31.1 概述

多设备上下文(Multiple Device Context)是OpenCL的核心特性之一,允许在单个上下文中管理多个设备,实现跨设备的内存共享和任务协调。本章基于OpenCL-CTS test_conformance/multiple_device_context/ 测试源码,介绍多设备上下文的创建、使用和测试方法。


31.2 多设备上下文基础

31.2.1 创建多设备上下文

基本用法:

c 复制代码
cl_int err;
cl_platform_id platform;
cl_device_id devices[MAX_DEVICES];
cl_uint num_devices;

// 获取所有设备
clGetPlatformIDs(1, &platform, NULL);
clGetDeviceIDs(platform, CL_DEVICE_TYPE_ALL, MAX_DEVICES, 
               devices, &num_devices);

// 创建包含多个设备的上下文
cl_context context = clCreateContext(NULL, num_devices, devices,
                                     NULL, NULL, &err);

指定特定设备类型:

c 复制代码
cl_device_id gpu_devices[10];
cl_device_id cpu_devices[10];
cl_uint num_gpus, num_cpus;

// 获取GPU设备
clGetDeviceIDs(platform, CL_DEVICE_TYPE_GPU, 10, 
               gpu_devices, &num_gpus);

// 获取CPU设备
clGetDeviceIDs(platform, CL_DEVICE_TYPE_CPU, 10,
               cpu_devices, &num_cpus);

// 创建混合设备上下文
cl_device_id all_devices[20];
memcpy(all_devices, gpu_devices, num_gpus * sizeof(cl_device_id));
memcpy(all_devices + num_gpus, cpu_devices, num_cpus * sizeof(cl_device_id));

cl_context context = clCreateContext(NULL, num_gpus + num_cpus,
                                     all_devices, NULL, NULL, &err);

31.2.2 查询上下文设备

c 复制代码
// 查询上下文包含的设备数量
cl_uint num_devices;
clGetContextInfo(context, CL_CONTEXT_NUM_DEVICES,
                 sizeof(num_devices), &num_devices, NULL);

// 查询设备列表
cl_device_id* devices = (cl_device_id*)malloc(
    num_devices * sizeof(cl_device_id));
clGetContextInfo(context, CL_CONTEXT_DEVICES,
                 num_devices * sizeof(cl_device_id), devices, NULL);

// 打印设备信息
for (cl_uint i = 0; i < num_devices; i++) {
    char device_name[128];
    clGetDeviceInfo(devices[i], CL_DEVICE_NAME,
                    sizeof(device_name), device_name, NULL);
    printf("Device %d: %s\n", i, device_name);
}

31.3 多设备内存对象

31.3.1 跨设备缓冲区共享

创建在所有设备上可访问的缓冲区:

c 复制代码
size_t buffer_size = 1024 * 1024;

// 缓冲区在上下文创建时就关联了所有设备
cl_mem buffer = clCreateBuffer(context, CL_MEM_READ_WRITE,
                               buffer_size, NULL, &err);

// 可以在任何设备上访问
cl_command_queue queue1 = clCreateCommandQueue(context, devices[0], 0, &err);
cl_command_queue queue2 = clCreateCommandQueue(context, devices[1], 0, &err);

// 设备0写入数据
clEnqueueWriteBuffer(queue1, buffer, CL_TRUE, 0, buffer_size, 
                     host_data, 0, NULL, NULL);

// 设备1读取数据
clEnqueueReadBuffer(queue2, buffer, CL_TRUE, 0, buffer_size,
                    host_result, 0, NULL, NULL);

31.3.2 设备间数据传输

直接在设备间复制:

c 复制代码
cl_mem buffer1 = clCreateBuffer(context, CL_MEM_READ_WRITE, size, NULL, &err);
cl_mem buffer2 = clCreateBuffer(context, CL_MEM_READ_WRITE, size, NULL, &err);

// 设备0上的队列写入buffer1
clEnqueueWriteBuffer(queue_dev0, buffer1, CL_TRUE, 0, size, 
                     data, 0, NULL, NULL);

// 使用设备1的队列从buffer1复制到buffer2
clEnqueueCopyBuffer(queue_dev1, buffer1, buffer2, 0, 0, size,
                    0, NULL, NULL);

// 验证数据
clEnqueueReadBuffer(queue_dev1, buffer2, CL_TRUE, 0, size,
                    result, 0, NULL, NULL);

31.3.3 图像对象的多设备访问

c 复制代码
cl_image_format format = { CL_RGBA, CL_UNSIGNED_INT8 };
cl_image_desc desc = {
    .image_type = CL_MEM_OBJECT_IMAGE2D,
    .image_width = 1024,
    .image_height = 1024,
    .image_depth = 0,
    .image_array_size = 0,
    .image_row_pitch = 0,
    .image_slice_pitch = 0,
    .num_mip_levels = 0,
    .num_samples = 0,
    .buffer = NULL
};

cl_mem image = clCreateImage(context, CL_MEM_READ_WRITE,
                              &format, &desc, NULL, &err);

// 多个设备可以访问同一图像
clEnqueueWriteImage(queue_dev0, image, CL_TRUE, origin, region,
                    0, 0, image_data, 0, NULL, NULL);

clEnqueueReadImage(queue_dev1, image, CL_TRUE, origin, region,
                   0, 0, output_data, 0, NULL, NULL);

31.4 CTS测试用例

31.4.1 test_multiple_devices

测试内容: 验证在包含多个设备的上下文中创建和使用内存对象。

测试步骤:

c 复制代码
int test_multiple_devices(cl_device_id deviceID, cl_context context,
                         cl_command_queue queue, int num_elements)
{
    // 1. 查询上下文中的设备数量
    cl_uint num_devices;
    clGetContextInfo(context, CL_CONTEXT_NUM_DEVICES,
                     sizeof(num_devices), &num_devices, NULL);
    
    if (num_devices < 2) {
        log_info("Test requires at least 2 devices\n");
        return 0; // Skip test
    }
    
    // 2. 获取所有设备
    cl_device_id* devices = malloc(num_devices * sizeof(cl_device_id));
    clGetContextInfo(context, CL_CONTEXT_DEVICES,
                     num_devices * sizeof(cl_device_id), devices, NULL);
    
    // 3. 为每个设备创建命令队列
    cl_command_queue* queues = malloc(num_devices * sizeof(cl_command_queue));
    for (cl_uint i = 0; i < num_devices; i++) {
        queues[i] = clCreateCommandQueue(context, devices[i], 0, &err);
    }
    
    // 4. 创建共享缓冲区
    size_t buffer_size = num_elements * sizeof(cl_int);
    cl_mem buffer = clCreateBuffer(context, CL_MEM_READ_WRITE,
                                   buffer_size, NULL, &err);
    
    // 5. 在第一个设备上写入数据
    cl_int* input_data = malloc(buffer_size);
    for (int i = 0; i < num_elements; i++) {
        input_data[i] = i;
    }
    clEnqueueWriteBuffer(queues[0], buffer, CL_TRUE, 0, buffer_size,
                        input_data, 0, NULL, NULL);
    
    // 6. 在其他设备上读取并验证
    for (cl_uint i = 1; i < num_devices; i++) {
        cl_int* output_data = malloc(buffer_size);
        clEnqueueReadBuffer(queues[i], buffer, CL_TRUE, 0, buffer_size,
                           output_data, 0, NULL, NULL);
        
        // 验证数据
        for (int j = 0; j < num_elements; j++) {
            if (output_data[j] != input_data[j]) {
                log_error("Data mismatch on device %d at index %d\n", i, j);
                return -1;
            }
        }
        free(output_data);
    }
    
    return 0;
}

31.4.2 test_multiple_contexts

测试内容: 验证多个独立上下文的正确性。

测试场景:

c 复制代码
int test_multiple_contexts(cl_device_id deviceID, cl_context context,
                          cl_command_queue queue, int num_elements)
{
    cl_int err;
    
    // 创建多个独立上下文,每个包含一个设备
    cl_context context1 = clCreateContext(NULL, 1, &devices[0],
                                         NULL, NULL, &err);
    cl_context context2 = clCreateContext(NULL, 1, &devices[1],
                                         NULL, NULL, &err);
    
    // 在各自上下文中创建缓冲区
    cl_mem buffer1 = clCreateBuffer(context1, CL_MEM_READ_WRITE,
                                   buffer_size, NULL, &err);
    cl_mem buffer2 = clCreateBuffer(context2, CL_MEM_READ_WRITE,
                                   buffer_size, NULL, &err);
    
    // 缓冲区之间不能直接传输(不同上下文)
    // 需要通过主机内存中转
    
    cl_command_queue queue1 = clCreateCommandQueue(context1, devices[0], 0, &err);
    cl_command_queue queue2 = clCreateCommandQueue(context2, devices[1], 0, &err);
    
    // 设备1写入
    clEnqueueWriteBuffer(queue1, buffer1, CL_TRUE, 0, buffer_size,
                        input_data, 0, NULL, NULL);
    
    // 读到主机
    void* host_buffer = malloc(buffer_size);
    clEnqueueReadBuffer(queue1, buffer1, CL_TRUE, 0, buffer_size,
                       host_buffer, 0, NULL, NULL);
    
    // 从主机写到设备2
    clEnqueueWriteBuffer(queue2, buffer2, CL_TRUE, 0, buffer_size,
                        host_buffer, 0, NULL, NULL);
    
    // 验证
    clEnqueueReadBuffer(queue2, buffer2, CL_TRUE, 0, buffer_size,
                       output_data, 0, NULL, NULL);
    
    // 清理
    clReleaseMemObject(buffer1);
    clReleaseMemObject(buffer2);
    clReleaseContext(context1);
    clReleaseContext(context2);
    
    return 0;
}

31.5 跨设备内核执行

31.5.1 同一内核在多设备执行

c 复制代码
// 创建程序对象(关联到多设备上下文)
cl_program program = clCreateProgramWithSource(context, 1,
                                               &kernel_source, NULL, &err);

// 为所有设备构建
clBuildProgram(program, num_devices, devices, NULL, NULL, NULL);

// 创建内核对象
cl_kernel kernel = clCreateKernel(program, "my_kernel", &err);

// 在多个设备上并行执行
for (cl_uint i = 0; i < num_devices; i++) {
    cl_mem device_buffer = clCreateBuffer(context, CL_MEM_READ_WRITE,
                                         buffer_size, NULL, &err);
    
    clSetKernelArg(kernel, 0, sizeof(cl_mem), &device_buffer);
    
    size_t global_size = num_elements;
    clEnqueueNDRangeKernel(queues[i], kernel, 1, NULL, &global_size,
                          NULL, 0, NULL, NULL);
}

// 等待所有设备完成
for (cl_uint i = 0; i < num_devices; i++) {
    clFinish(queues[i]);
}

31.5.2 流水线式跨设备处理

c 复制代码
// 生产者-消费者模式
cl_mem intermediate_buffer = clCreateBuffer(context, CL_MEM_READ_WRITE,
                                           buffer_size, NULL, &err);

// 设备0: 预处理
cl_kernel preprocess = clCreateKernel(program, "preprocess", &err);
clSetKernelArg(preprocess, 0, sizeof(cl_mem), &input_buffer);
clSetKernelArg(preprocess, 1, sizeof(cl_mem), &intermediate_buffer);
clEnqueueNDRangeKernel(queue_dev0, preprocess, 1, NULL, &global_size,
                      NULL, 0, NULL, &event1);

// 设备1: 主处理(等待设备0完成)
cl_kernel process = clCreateKernel(program, "process", &err);
clSetKernelArg(process, 0, sizeof(cl_mem), &intermediate_buffer);
clSetKernelArg(process, 1, sizeof(cl_mem), &output_buffer);
clEnqueueNDRangeKernel(queue_dev1, process, 1, NULL, &global_size,
                      NULL, 1, &event1, &event2);

clWaitForEvents(1, &event2);

31.6 性能优化策略

31.6.1 负载均衡

按设备能力分配任务:

c 复制代码
// 查询各设备的计算单元数
cl_uint compute_units[MAX_DEVICES];
for (cl_uint i = 0; i < num_devices; i++) {
    clGetDeviceInfo(devices[i], CL_DEVICE_MAX_COMPUTE_UNITS,
                    sizeof(cl_uint), &compute_units[i], NULL);
}

// 计算总计算能力
cl_uint total_compute = 0;
for (cl_uint i = 0; i < num_devices; i++) {
    total_compute += compute_units[i];
}

// 按比例分配工作量
for (cl_uint i = 0; i < num_devices; i++) {
    size_t device_workload = (total_workload * compute_units[i]) / total_compute;
    size_t offset = /* 计算偏移 */;
    
    clEnqueueNDRangeKernel(queues[i], kernel, 1, &offset,
                          &device_workload, NULL, 0, NULL, NULL);
}

31.6.2 减少设备间通信

数据本地化:

c 复制代码
// 为每个设备创建独立的输入/输出缓冲区
cl_mem* device_inputs = malloc(num_devices * sizeof(cl_mem));
cl_mem* device_outputs = malloc(num_devices * sizeof(cl_mem));

for (cl_uint i = 0; i < num_devices; i++) {
    device_inputs[i] = clCreateBuffer(context, CL_MEM_READ_ONLY,
                                     chunk_size, NULL, &err);
    device_outputs[i] = clCreateBuffer(context, CL_MEM_WRITE_ONLY,
                                      chunk_size, NULL, &err);
    
    // 写入各自的输入数据
    clEnqueueWriteBuffer(queues[i], device_inputs[i], CL_FALSE, 0,
                        chunk_size, &host_data[i * chunk_size],
                        0, NULL, NULL);
}

// 并行处理
for (cl_uint i = 0; i < num_devices; i++) {
    clSetKernelArg(kernel, 0, sizeof(cl_mem), &device_inputs[i]);
    clSetKernelArg(kernel, 1, sizeof(cl_mem), &device_outputs[i]);
    clEnqueueNDRangeKernel(queues[i], kernel, 1, NULL, &chunk_workload,
                          NULL, 0, NULL, NULL);
}

31.6.3 异步操作

c 复制代码
// 使用事件进行细粒度同步
cl_event write_events[MAX_DEVICES];
cl_event kernel_events[MAX_DEVICES];
cl_event read_events[MAX_DEVICES];

// 异步写入
for (cl_uint i = 0; i < num_devices; i++) {
    clEnqueueWriteBuffer(queues[i], buffers[i], CL_FALSE, 0, size,
                        input_data, 0, NULL, &write_events[i]);
}

// 异步执行(等待各自的写入完成)
for (cl_uint i = 0; i < num_devices; i++) {
    clEnqueueNDRangeKernel(queues[i], kernel, 1, NULL, &global_size,
                          NULL, 1, &write_events[i], &kernel_events[i]);
}

// 异步读取(等待各自的内核完成)
for (cl_uint i = 0; i < num_devices; i++) {
    clEnqueueReadBuffer(queues[i], buffers[i], CL_FALSE, 0, size,
                       output_data, 1, &kernel_events[i], &read_events[i]);
}

// 等待所有操作完成
clWaitForEvents(num_devices, read_events);

31.7 常见陷阱

31.7.1 设备能力差异

问题: 不同设备可能支持不同的特性。

c 复制代码
// 检查所有设备是否支持所需特性
for (cl_uint i = 0; i < num_devices; i++) {
    cl_bool image_support;
    clGetDeviceInfo(devices[i], CL_DEVICE_IMAGE_SUPPORT,
                    sizeof(image_support), &image_support, NULL);
    
    if (!image_support) {
        log_error("Device %d does not support images\n", i);
        return -1;
    }
}

31.7.2 内存一致性

问题: 跨设备访问同一缓冲区时需要显式同步。

c 复制代码
// 设备0写入
clEnqueueNDRangeKernel(queue_dev0, write_kernel, 1, NULL, &global_size,
                      NULL, 0, NULL, &write_event);

// 必须等待写入完成才能在设备1读取
clEnqueueNDRangeKernel(queue_dev1, read_kernel, 1, NULL, &global_size,
                      NULL, 1, &write_event, NULL);

31.7.3 上下文切换开销

问题: 频繁在多个上下文间切换会带来性能开销。

建议: 尽量使用单个多设备上下文,而非多个单设备上下文。


31.8 总结

多设备上下文是OpenCL的强大特性,支持:

  1. 资源共享: 内存对象在多设备间共享
  2. 并行执行: 充分利用系统中的所有计算资源
  3. 灵活调度: 根据设备能力分配任务
  4. 高效协作: 设备间可以直接传输数据

最佳实践:

  • 使用单个多设备上下文代替多个单设备上下文
  • 根据设备能力进行负载均衡
  • 最小化设备间数据传输
  • 使用异步操作和事件同步
  • 验证所有设备的特性兼容性

CTS测试覆盖:

  • 多设备上下文创建和查询
  • 跨设备内存对象访问
  • 多上下文隔离性验证
  • 设备间数据传输正确性

多设备上下文使OpenCL应用能够充分发挥异构系统的全部潜力,实现更高的性能和吞吐量。

相关推荐
DeeplyMind7 天前
第27章 Device_partition - 设备分区测试
opencl·opencl-cts
DeeplyMind8 天前
第24章 Workgroups - 工作组测试
opencl·workgroup·opencl-cts
晚晶1 个月前
【Linux】opencv4.9.0静态库编译,开启opencl和EIGEN矩阵运算
linux·c++·opencv·矩阵·opencl
Hi202402179 个月前
PoCL环境搭建
opencl
Hi202402171 年前
RK3588上CPU和GPU算力以及opencv resize的性能对比测试
linux·opencv·arm·gpu·opencl·算力测试·mali-gpu
安全二次方security²1 年前
基于RISC-V的开源通用GPU指令集架构--乘影OpenGPGPU
risc-v·opencl·gpgpu·乘影·向量扩展指令集·gpgpu微架构·开源通用gpu
千里马-horse1 年前
在OpenCL 中输出CLinfo信息
开发语言·c++·算法·opencl·1024程序员节
千里马-horse1 年前
OpenCL内存模型
opencl·内存模型
qianbo_insist2 年前
opencl色域变换,处理传递显存数据
c++·opencl