混合OpenMP与OpenACC的Fortran代码编译报错,如何解决?
错误原因
- 编译器版本缺陷:你使用的NVHPC 20.7属于较老的发行版本,对OpenMP与OpenACC混合编程场景的支持存在已知bug,在OpenMP并行域内调用
acc_set_device_num接口时,编译器前端生成LLVM中间代码逻辑错误,直接触发了invalid type for alloca的报错。 - 代码逻辑错误:
acc_set_device_num调用位置错误:你把设备切换接口放在了!$acc data指令之后,!$acc data执行时会使用当前默认的设备ID完成数据拷贝,后续再切换设备不会改变已经完成的设备数据映射,导致计算和数据不在同一个设备上。- 第二个OpenMP section的计算循环缺少
!$acc kernels指令,编译器不会将这部分循环卸载到NVIDIA设备执行,只会在当前OpenMP线程所在的CPU核心运行,完全浪费了第二张卡的算力。 - 两个
!$acc data子句都对整个T数组做copyout,会导致两个设备的返回结果互相覆盖,最终只能拿到最后一个完成的section的计算结果。 - 硬编码设备ID为1和2,而OpenACC的NVIDIA设备编号从0开始,当节点只有2张NVIDIA卡时会直接触发设备ID越界错误。
- 开头冗余的
CALL OMP_SET_NUM_THREADS(2)和parallel子句的num_threads参数冲突,可能导致线程数不符合预期。
解决方法
- 升级编译器版本:将NVHPC SDK至少升级到22.3及以上版本,该版本已经修复了OpenMP+OpenACC混合编程场景下的LLVM中间代码生成bug,可以解决你当前遇到的编译报错。
- 调整代码逻辑,修改后的参考代码如下:
integer :: dev_id, j_start, j_end, j_half j_half = (nj-1)/2 !$omp parallel num_threads(acc_get_num_devices(acc_device_nvidia)) private(dev_id, j_start, j_end) dev_id = omp_get_thread_num() call acc_set_device_num(dev_id, acc_device_nvidia ) ! 按线程号拆分计算范围 if (dev_id == 0) then j_start = 2 j_end = j_half else j_start = j_half + 1 j_end = nj-1 endif ! 只拷贝当前线程负责的数组分片,避免数据冲突 !$acc data copy(T(j_start:j_end, 2:ni-1)) copyin(T_o(j_start:j_end, 2:ni-1)) !$acc kernels do j=j_start,j_end do i=2,ni-1 T(i,j)=0.25*(T_o(i+1,j)+T_o(i-1,j)+ T_o(i,j+1)+T_o(i,j-1)) enddo enddo !$acc end kernels !$acc end data !$omp end parallel
修改点说明:
- 用OpenMP线程号动态分配设备ID,适配任意数量的NVIDIA卡
- 提前切换设备再执行数据拷贝,保证数据和计算在同一个设备上
- 按设备拆分数组拷贝范围,避免数据覆盖和冗余拷贝
- 所有设备的计算循环都添加了
!$acc kernels指令,确保正确卸载到GPU - 去掉了冗余的sections语法,用线程号拆分计算逻辑,代码扩展性更强
内容的提问来源于stack exchange,提问作者Harshad bhusare
相关产品推荐
相关产品推荐

