在Ubuntu 20.04上搞定PGI Fortran + CUDA 11.7:一份给科学计算新手的避坑指南
在Ubuntu 20.04上搞定PGI Fortran + CUDA 11.7:一份给科学计算新手的避坑指南
如果你正在尝试将传统的Fortran科学计算代码迁移到GPU加速环境,可能会被各种版本依赖和配置问题搞得焦头烂额。Ubuntu 20.04 LTS作为当前最稳定的Linux发行版之一,搭配CUDA 11.7和PGI Fortran(现为NVIDIA HPC SDK)确实能带来显著的性能提升,但前提是你能顺利跨过安装和配置这道坎。
1. 环境准备与CUDA 11.7安装
在开始之前,确保你的系统满足以下基本要求:
- 支持CUDA的NVIDIA显卡(计算能力3.5及以上)
- Ubuntu 20.04 LTS 64位系统
- 至少20GB的可用磁盘空间
- 稳定的网络连接
1.1 系统检查与依赖安装
首先验证你的GPU是否支持CUDA:
lspci | grep -i nvidia输出应显示你的NVIDIA显卡型号。如果不确定显卡的计算能力,可以查阅NVIDIA官方文档。
接下来更新系统并安装必要依赖:
sudo apt update && sudo apt upgrade -y sudo apt install -y build-essential linux-headers-$(uname -r)关键步骤:禁用默认的nouveau驱动:
- 创建配置文件:
sudo bash -c "echo 'blacklist nouveau' > /etc/modprobe.d/blacklist-nouveau.conf" sudo bash -c "echo 'options nouveau modeset=0' >> /etc/modprobe.d/blacklist-nouveau.conf" - 更新initramfs并重启:
sudo update-initramfs -u sudo reboot - 验证nouveau是否已禁用:
如果没有输出,说明禁用成功。lsmod | grep nouveau
1.2 安装NVIDIA驱动和CUDA 11.7
Ubuntu 20.04提供了更简便的驱动安装方式:
sudo ubuntu-drivers autoinstall安装完成后验证驱动:
nvidia-smi你应该看到类似如下的输出,包含驱动版本和CUDA版本信息:
+-----------------------------------------------------------------------------+ | NVIDIA-SMI 450.80.02 Driver Version: 450.80.02 CUDA Version: 11.7 | |-------------------------------+----------------------+----------------------+现在安装CUDA 11.7工具包:
wget https://developer.download.nvidia.com/compute/cuda/11.7.0/local_installers/cuda_11.7.0_515.43.04_linux.run sudo sh cuda_11.7.0_515.43.04_linux.run在安装界面中:
- 取消勾选"NVIDIA Driver"(因为我们已经单独安装了驱动)
- 确保"CUDA Toolkit 11.7"被选中
- 接受许可协议并完成安装
1.3 环境配置与验证
将CUDA添加到环境变量:
echo 'export PATH=/usr/local/cuda-11.7/bin:$PATH' >> ~/.bashrc echo 'export LD_LIBRARY_PATH=/usr/local/cuda-11.7/lib64:$LD_LIBRARY_PATH' >> ~/.bashrc source ~/.bashrc验证CUDA安装:
nvcc --version应该显示CUDA 11.7的版本信息。
运行设备查询示例:
cd /usr/local/cuda/samples/1_Utilities/deviceQuery make ./deviceQuery如果看到"Result = PASS",说明CUDA安装成功。
2. NVIDIA HPC SDK(原PGI编译器)安装
PGI编译器现已整合到NVIDIA HPC SDK中,提供了更好的CUDA Fortran支持。
2.1 下载与安装
从NVIDIA官网下载HPC SDK:
wget https://developer.download.nvidia.com/hpc-sdk/22.7/nvhpc_2022_227_Linux_x86_64_cuda_11.7.tar.gz tar xzvf nvhpc_2022_227_Linux_x86_64_cuda_11.7.tar.gz cd nvhpc_2022_227_Linux_x86_64_cuda_11.7 sudo ./install按照提示完成安装,建议选择默认安装路径(/opt/nvidia/hpc_sdk)。
2.2 环境配置
设置HPC SDK环境变量:
echo 'export PATH=/opt/nvidia/hpc_sdk/Linux_x86_64/22.7/compilers/bin:$PATH' >> ~/.bashrc echo 'export MANPATH=/opt/nvidia/hpc_sdk/Linux_x86_64/22.7/compilers/man:$MANPATH' >> ~/.bashrc source ~/.bashrc验证安装:
pgfortran --version应显示类似如下的信息:
pgfortran 22.7-0 64-bit target on x86-64 Linux -tp zen2-642.3 解决常见兼容性问题
Ubuntu 20.04默认的gcc版本(9.4.0)可能与HPC SDK不完全兼容。解决方法:
- 安装兼容的gcc版本:
sudo apt install -y gcc-8 g++-8- 设置编译器替代:
sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-8 80 sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-8 80 sudo update-alternatives --config gcc # 选择gcc-8- 配置HPC SDK使用指定gcc版本:
export CC=gcc-8 export CXX=g++-83. CUDA Fortran开发环境配置
3.1 创建简单的CUDA Fortran项目
创建一个简单的向量加法示例:
vecadd.cuf:
module mathOps contains attributes(global) subroutine vectorAdd(a, b, c, n) implicit none real, device :: a(n), b(n), c(n) integer, value :: n integer :: i i = blockDim%x * (blockIdx%x - 1) + threadIdx%x if (i <= n) then c(i) = a(i) + b(i) end if end subroutine vectorAdd end module mathOps program main use mathOps use cudafor implicit none integer, parameter :: N = 1024 real :: a(N), b(N), c(N) real, device :: a_d(N), b_d(N), c_d(N) type(dim3) :: grid, block ! 初始化主机数据 a = 1.0 b = 2.0 ! 设置CUDA核执行配置 block = dim3(256, 1, 1) grid = dim3(ceiling(real(N)/block%x), 1, 1) ! 传输数据到设备 a_d = a b_d = b ! 调用CUDA核 call vectorAdd<<<grid, block>>>(a_d, b_d, c_d, N) ! 将结果传回主机 c = c_d ! 验证结果 if (all(abs(c - 3.0) < 1.0e-5)) then print *, "Test passed!" else print *, "Test failed!" end if end program main3.2 编译与运行
使用以下命令编译CUDA Fortran程序:
pgfortran -Mcuda -o vecadd vecadd.cuf运行程序:
./vecadd如果看到"Test passed!"输出,说明CUDA Fortran环境配置成功。
3.3 Makefile示例
对于更复杂的项目,建议使用Makefile管理编译过程:
Makefile:
FC = pgfortran FCFLAGS = -Mcuda -fast -Minfo=all SRCS = vecadd.cuf OBJS = $(SRCS:.cuf=.o) EXEC = vecadd all: $(EXEC) $(EXEC): $(OBJS) $(FC) $(FCFLAGS) -o $@ $^ %.o: %.cuf $(FC) $(FCFLAGS) -c $< clean: rm -f $(OBJS) $(EXEC)4. 性能优化与调试技巧
4.1 编译器优化选项
PGI编译器提供了多种优化选项:
-fast:启用常用优化组合-O3:高级优化级别-Mcuda=fastmath:启用CUDA快速数学运算-Minfo=accel:显示并行化信息
推荐组合:
pgfortran -Mcuda=fastmath -fast -Minfo=all -o program program.cuf4.2 CUDA Fortran特有功能
- Managed Memory:简化内存管理
real, managed :: array(N)- CUDA事件计时:
type(cudaEvent) :: startEvent, stopEvent real :: time ierr = cudaEventCreate(startEvent) ierr = cudaEventCreate(stopEvent) ierr = cudaEventRecord(startEvent, 0) ! ... 执行核函数 ... ierr = cudaEventRecord(stopEvent, 0) ierr = cudaEventSynchronize(stopEvent) ierr = cudaEventElapsedTime(time, startEvent, stopEvent) print *, "Kernel time:", time, "ms"4.3 调试工具
- CUDA-MEMCHECK:
cuda-memcheck ./vecadd- PGI编译器调试信息:
pgfortran -g -Mcuda -o vecadd vecadd.cuf- Nsight Systems:性能分析工具
nsys profile --stats=true ./vecadd4.4 多GPU编程
对于多GPU系统,可以使用以下模式:
use cudafor integer :: numDevices, ierr ierr = cudaGetDeviceCount(numDevices) do i = 0, numDevices-1 ierr = cudaSetDevice(i) ! 在每个设备上执行操作 end do5. 实际应用案例:矩阵乘法优化
让我们看一个更实际的例子:矩阵乘法优化。
matmul.cuf:
module matmul_kernels use cudafor contains attributes(global) subroutine simpleMatMul(A, B, C, N) real, device :: A(N,N), B(N,N), C(N,N) integer, value :: N integer :: i, j, k real :: sum i = (blockIdx%x-1)*blockDim%x + threadIdx%x j = (blockIdx%y-1)*blockDim%y + threadIdx%y if (i <= N .and. j <= N) then sum = 0.0 do k = 1, N sum = sum + A(i,k) * B(k,j) end do C(i,j) = sum end if end subroutine simpleMatMul attributes(global) subroutine sharedMatMul(A, B, C, N) real, device :: A(N,N), B(N,N), C(N,N) integer, value :: N integer :: i, j, k, tx, ty, bx, by real, shared :: As(16,16), Bs(16,16) real :: sum tx = threadIdx%x ty = threadIdx%y bx = blockIdx%x by = blockIdx%y i = (bx-1)*16 + tx j = (by-1)*16 + ty sum = 0.0 do k = 1, N, 16 if (i <= N .and. (k+ty-1) <= N) then As(tx,ty) = A(i, k+ty-1) else As(tx,ty) = 0.0 end if if ((k+tx-1) <= N .and. j <= N) then Bs(tx,ty) = B(k+tx-1, j) else Bs(tx,ty) = 0.0 end if call syncthreads() do kk = 1, 16 sum = sum + As(tx,kk) * Bs(kk,ty) end do call syncthreads() end do if (i <= N .and. j <= N) then C(i,j) = sum end if end subroutine sharedMatMul end module matmul_kernels program main use matmul_kernels use cudafor implicit none integer, parameter :: N = 512 real :: A(N,N), B(N,N), C1(N,N), C2(N,N) real, device :: A_d(N,N), B_d(N,N), C1_d(N,N), C2_d(N,N) type(dim3) :: grid, block integer :: i, j, ierr real :: time type(cudaEvent) :: startEvent, stopEvent ! 初始化矩阵 do j = 1, N do i = 1, N A(i,j) = real(i+j) B(i,j) = real(i-j) end do end do ! 创建CUDA事件 ierr = cudaEventCreate(startEvent) ierr = cudaEventCreate(stopEvent) ! 传输数据到设备 A_d = A B_d = B ! 简单矩阵乘法 block = dim3(16, 16, 1) grid = dim3(ceiling(real(N)/16), ceiling(real(N)/16), 1) ierr = cudaEventRecord(startEvent, 0) call simpleMatMul<<<grid, block>>>(A_d, B_d, C1_d, N) ierr = cudaEventRecord(stopEvent, 0) ierr = cudaEventSynchronize(stopEvent) ierr = cudaEventElapsedTime(time, startEvent, stopEvent) print *, "Simple matmul time:", time, "ms" ! 共享内存矩阵乘法 ierr = cudaEventRecord(startEvent, 0) call sharedMatMul<<<grid, block>>>(A_d, B_d, C2_d, N) ierr = cudaEventRecord(stopEvent, 0) ierr = cudaEventSynchronize(stopEvent) ierr = cudaEventElapsedTime(time, startEvent, stopEvent) print *, "Shared matmul time:", time, "ms" ! 验证结果 C1 = C1_d C2 = C2_d do j = 1, N do i = 1, N if (abs(C1(i,j) - C2(i,j)) > 1.0e-4) then print *, "Error at (", i, ",", j, ")" stop end if end do end do print *, "Results match!" ! 清理 ierr = cudaEventDestroy(startEvent) ierr = cudaEventDestroy(stopEvent) end program main编译并运行:
pgfortran -Mcuda=fastmath -fast -Minfo=all -o matmul matmul.cuf ./matmul这个示例展示了两种矩阵乘法实现:简单版本和使用了共享内存的优化版本。在实际应用中,共享内存版本通常能带来显著的性能提升。
