ROSE 编译器框架/OpenMP 加速器模型实现
我们正在试验 OpenMP 4.0 规范中 OpenMP 加速器扩展的试用实现。由于规范快速变化的性质和我们的资源限制,它并非官方或完整的实现。
该实现基于 ROSE 中现有的 OpenMP 实现。
该实现大体遵循技术报告 http://www.openmp.org/mp-documents/TR1_167.pdf 和后来的 http://www.openmp.org/mp-documents/OpenMP4.0.0.pdf 。
- pragma omp target + pragma omp parallel for
- map 子句
- GPU 上的 reduction
- pragma omp target data
- collapse 子句
我们逐步发布这项工作,作为托管在 https://github.com/rose-compiler/rose-develop 的 ROSE(基于 EDG 4.x 的版本)编译器的一部分。
- 克隆它
- 在 buildtree/src 下配置和构建 librose.so
- 通过键入 "make roseompacc" 构建翻译器 roseompacc,位于 tests/nonsmoke/functional/roseTests/ompLoweringTests/
翻译器源代码位于 rose-omp-accelerator/tests/nonsmoke/functional/roseTests/ompLoweringTests/roseompacc.C
使用 roseompacc 的命令行(目前分为两步)在 rose-omp-accelerator/tests/nonsmoke/functional/roseTests/ompLoweringTests/Makefile.am 中举例说明。
# Experimental translation for OpenMP accelerator model directives # no final compilation for now, which requires CUDA compiler for the generated code test_omp_acc:axpy_ompacc.o matrixmultiply-ompacc.o jacobi-ompacc.o rose_axpy_ompacc.cu:roseompacc ./roseompacc$(EXEEXT) ${TEST_FLAGS} -rose:openmp:lowering -rose:skipfinalCompileStep -c $(TEST_DIR)/axpy_ompacc.c rose_matrixmultiply-ompacc.cu:roseompacc ./roseompacc$(EXEEXT) ${TEST_FLAGS} -rose:openmp:lowering -rose:skipfinalCompileStep -c $(TEST_DIR)/matrixmultiply-ompacc.c rose_jacobi-ompacc.cu:roseompacc ./roseompacc$(EXEEXT) ${TEST_FLAGS} -rose:openmp:lowering -rose:skipfinalCompileStep -c $(TEST_DIR)/jacobi-ompacc.c # build executables using nvcc axpy_ompacc.out:rose_axpy_ompacc.cu nvcc $< $(TEST_INCLUDES) $(top_srcdir)/src/midend/programTransformation/ompLowering/xomp_cuda_lib.cu -o $@ matrixmultiply-ompacc.out:rose_matrixmultiply-ompacc.cu nvcc $< $(TEST_INCLUDES) $(top_srcdir)/src/midend/programTransformation/ompLowering/xomp_cuda_lib.cu -o $@ jacobi-ompacc.out:rose_jacobi-ompacc.cu nvcc $< $(TEST_INCLUDES) $(top_srcdir)/src/midend/programTransformation/ompLowering/xomp_cuda_lib.cu -o $@
flex/bison
- https://github.com/rose-compiler/rose-develop/blob/master/src/frontend/SageIII/omplexer.ll
- https://github.com/rose-compiler/rose-develop/blob/master/src/frontend/SageIII/ompparser.yy
存储解析结果的 AST 属性
- https://github.com/rose-compiler/rose-develop/blob/master/src/frontend/SageIII/OmpAttribute.h
- https://github.com/rose-compiler/rose-develop/blob/master/src/frontend/SageIII/OmpAttribute.C
IR 生成:ROSETTA,将属性转换为专用 AST
翻译
- https://github.com/rose-compiler/rose-develop/blob/master/src/midend/programTransformation/ompLowering/omp_lowering.cpp
- void transOmpTargetParallel() 翻译与目标指令关联的并行区域
- void transOmpTargetLoop() 翻译与目标指令关联的 omp for 循环
- void transOmpMapVariables() 翻译数据映射子句
在内部,它调用以经典模式(传递单个参数,没有任何包装数组或结构)运行的轮廓器
- Outliner::enable_classic = true;
运行时支持
- https://github.com/rose-compiler/rose-develop/blob/master/src/midend/programTransformation/ompLowering/libxomp.h
- https://github.com/rose-compiler/rose-develop/blob/master/src/midend/programTransformation/ompLowering/xomp.c
- https://github.com/rose-compiler/rose-develop/blob/master/src/midend/programTransformation/ompLowering/xomp_cuda_lib.cu
- https://github.com/rose-compiler/rose-develop/blob/master/src/midend/programTransformation/ompLowering/xomp_cuda_lib_inlined.cu
关于将什么放入每个文件的规则
- libxomp.h 编译器编写者的运行时接口
- xomp.c CPU 线程的运行时支持
- xomp_cuda_lib.cu 提供了由主机代码调用的包装 CUDA 代码(没有 __device__ 关键字)的常规 C 函数。
- xomp_cuda_lib_inlined.cu 提供了由 CUDA 内核函数(具有 __global__ 关键字)调用的 __device__ 函数。
在进行这项工作时,CUDA 和/或 nvcc 没有针对设备代码的链接器(链接来自不同文件的 __global__ 和 _device_ 函数)。我们必须将一些常见的设备函数放入此文件中。这样,它们就可以包含在生成 CUDA 内核的同一个文件中(__global__ 函数只能调用来自同一个文件的 __device__ 函数)。
运行时提供两种管理设备数据的方法
- 显式管理数据分配、复制到、复制回和释放
- 使用设备数据环境 (DDE) 函数自动管理
- xomp_deviceDataEnvironmentEnter(); // 进入环境,推送到环境堆栈
- xomp_deviceDataEnvironmentPrepareVariable(); // 如果已在父环境中,则重用数据,或分配,并向当前环境堆栈注册
- xomp_deviceDataEnvironmentExit(); // 自动释放,复制回数据。弹出环境堆栈
显式数据管理示例
int mmm() { { float *_dev_a; int _dev_a_size = sizeof(float ) * N * M; _dev_a = ((float *)(xomp_deviceMalloc(_dev_a_size))); xomp_memcpyHostToDevice(((void *)_dev_a),((const void *)a),_dev_a_size); float *_dev_b; int _dev_b_size = sizeof(float ) * M * K; _dev_b = ((float *)(xomp_deviceMalloc(_dev_b_size))); xomp_memcpyHostToDevice(((void *)_dev_b),((const void *)b),_dev_b_size); float *_dev_c; int _dev_c_size = sizeof(float ) * N * M; _dev_c = ((float *)(xomp_deviceMalloc(_dev_c_size))); xomp_memcpyHostToDevice(((void *)_dev_c),((const void *)c),_dev_c_size); /* Launch CUDA kernel ... */ int _threads_per_block_ = xomp_get_maxThreadsPerBlock(); int _num_blocks_ = xomp_get_max1DBlock(1023 - 0 + 1); OUT__1__9221__<<<_num_blocks_,_threads_per_block_>>>(_dev_a,_dev_b,_dev_c); xomp_freeDevice(_dev_a); xomp_freeDevice(_dev_b); xomp_memcpyDeviceToHost(((void *)c),((const void *)_dev_c),_dev_c_size); xomp_freeDevice(_dev_c); } return 0; }
自动数据管理支持重用嵌套设备数据环境
int mmm() { { xomp_deviceDataEnvironmentEnter(); float *_dev_a; int _dev_a_size = sizeof(float ) * 1024 * 1024; _dev_a = ((float *)(xomp_deviceDataEnvironmentPrepareVariable(((void *)a),_dev_a_size,1,0))); float *_dev_b; int _dev_b_size = sizeof(float ) * 1024 * 1024; _dev_b = ((float *)(xomp_deviceDataEnvironmentPrepareVariable(((void *)b),_dev_b_size,1,0))); float *_dev_c; int _dev_c_size = sizeof(float ) * 1024 * 1024; _dev_c = ((float *)(xomp_deviceDataEnvironmentPrepareVariable(((void *)c),_dev_c_size,1,1))); /* Launch CUDA kernel ... */ int _threads_per_block_ = xomp_get_maxThreadsPerBlock(); int _num_blocks_ = xomp_get_max1DBlock(1023 - 0 + 1); OUT__1__9221__<<<_num_blocks_,_threads_per_block_>>>(_dev_a,_dev_b,_dev_c); xomp_deviceDataEnvironmentExit(); } return 0; }
默认情况下控制在多 GPU 支持中使用的设备数量
export OMP_NUM_DEVICES=5
运行时会自动检测此环境变量并使用它来控制 GPU 计数。
你可以在代码中检索此计数,例如
int GPU_N = xomp_get_num_devices(); // this function will obtain the env variable.
如果指定了 OMP_NUM_DEVICES,xomp_get_num_devices() 将在内部调用 int omp_get_max_devices(void) 来获取硬件支持的最大设备数量。
相关 提交
必须提供运行时循环调度,因为 CUDA 教程中简单的 1-迭代到 1-线程映射对于大型迭代空间将不起作用。(迭代计数 > 总 GPU 线程)。
// Jacobi 2-D kernel: // Naive 1-iteration to 1 GPU thread scheduling: each GPU thread gets one iteration to work on. // Problem: won't scale to large iteration space. int i = blockIdx.x * blockDim.x + threadIdx.x + 1; int j = blockIdx.y * blockDim.y + threadIdx.y + 1; newa[j*m+i] = w0*a[j*m+i] + w1 * (a[j*m+i-1] + a[(j-1)*m+i] + a[j*m+i+1] + a[(j+1)*m+i]) + w2 * (a[(j-1)*m+i-1] + a[(j+1)*m+i-1] + a[(j-1)*m+i+1] + a[(j+1)*m+i+1]);
我们在运行时提供了两个循环调度器,因此每个 CUDA 线程都可以处理多个迭代来处理大型迭代空间。
- 静态偶数调度器:此调度器将迭代空间均匀地划分为大致相等大小的块。然后,它将每个块分配给一个 CUDA 线程。这种调度策略可能会过度使用内存,因为每个线程将接触由迭代块带来的很大范围的数据。
- 循环调度器:每个线程一次获取一个迭代(或多个迭代)。它与 OpenMP 的 schedule(static, chunk) 策略相同。
调度器的测试使用(使用相应的 CPU 版本)在以下位置给出:
GPU 版本的使用示例:循环调度器(现在是新的默认调度器)
void OUT__2__10550__(int n,int *_dev_u) { int ij; int _dev_lower, _dev_upper; // variables for adjusted loop info considering both original chunk size and step(strip) int _dev_loop_chunk_size; int _dev_loop_sched_index; int _dev_loop_stride; // 1-D thread block: int _dev_thread_num = omp_get_num_threads(); int _dev_thread_id = omp_get_thread_num(); printf ("thread count = %d, current thread id = %d\n", _dev_thread_num, _dev_thread_id); int orig_start =0; // must be correct!! int orig_end = n-1; // use inclusive bound int orig_step = 1; int orig_chunk_size = 1; XOMP_static_sched_init (orig_start, orig_end, orig_step, orig_chunk_size, _dev_thread_num, _dev_thread_id, \ & _dev_loop_chunk_size , & _dev_loop_sched_index, & _dev_loop_stride); printf ("Initialized chunk size = %d, sched indx =%d, stride = %d\n",_dev_loop_chunk_size, _dev_loop_sched_index, _dev_loop_stride); while (XOMP_static_sched_next (&_dev_loop_sched_index, orig_end, orig_step, _dev_loop_stride, _dev_loop_chunk_size, _dev_thread_num, _dev_thread_id, & _dev_lower , & _dev_upper)) { printf ("Thread ID: %d Allocated lower = %d upper = %d\n", _dev_thread_id, _dev_lower, _dev_upper); for (ij = _dev_lower ; ij <= _dev_upper; ij ++) { // using inclusive bound here _dev_u[ij] += (n - ij); } } }
如何使用静态-偶调度器的示例(由于性能缺陷,在最近的版本中不再是默认调度器。)
// using a scheduler __global__ void OUT__1__11058__(int j,int k,float *_dev_a,float *_dev_b,float *_dev_c) { int _dev_i; long _dev_lower, _dev_upper; XOMP_accelerator_loop_default (0, MSIZE -1 , 1, &_dev_lower, &_dev_upper); for (_dev_i = _dev_lower; _dev_i<= _dev_upper; _dev_i ++) { for (j = 0; j < MSIZE; j++) { float c= 0.0; for (k = 0; k < MSIZE; k++) c += _dev_a[_dev_i * MSIZE + k] * _dev_b[k * MSIZE + j]; _dev_c[_dev_i * MSIZE + j] = c; } } }
- axpy_ompacc.c
- axpy_ompacc2.c
- matrixmultiply-ompacc.c
- jacobi-ompacc.c
- jacobi-ompacc-opt1.c // 使用“目标数据”区域
- 测试翻译器:roseompacc.C
- 测试驱动程序:Makefile.am
- 在构建树中执行 make ompacc_test 将触发相关测试
- 确保您已安装 nvidia nvcc 编译器以生成可执行文件。否则,只会生成 cuda 文件,最终编译将失败。
一些示例生成的 CUDA 文件可以在以下位置找到:
- C. Liao, Y. Yan, B. de Supinsky, D. Quinlan, B. Chapman, OpenMP 加速器模型的早期经验,IWOMP 2013,https://e-reports-ext.llnl.gov/pdf/755563.pdf