安装 CUDA 驱动与工具包
我们已经用 CuPy 和 PyCUDA 编写并成功运行了首批 GPU kernel!到目前为止,我们的工作依赖于一个正确配置的开发环境。但随着深入 GPU 编程,并开始使用更高级的 CUDA 功能,务必确保底层环境稳固可靠、可复现,并能支持未来的各种库和工具。
回顾 CUDA 技术栈
要从 Python 调用 GPU 代码,需要以下三层协同工作:
- NVIDIA 驱动:将 Linux 操作系统与 GPU 硬件连接起来。
- CUDA 工具包:提供命令行工具(如
nvcc)、开发库和运行时组件。 - Python 库(CuPy、PyCUDA):使用工具包将我们的高层代码编译为高性能 GPU 指令。
我们需要确保这三者在版本和配置上匹配,以便利用最新特性并保持驱动稳定。
安装 NVIDIA 驱动
在运行任何重度 GPU 任务前,先确认驱动版本与 GPU 及 CUDA 工具包兼容。即便第 1 章的示例顺利运行,也建议执行:
nvidia-smi
该命令会显示 GPU 型号、已安装驱动版本及当前使用情况。若需更新或驱动缺失,可在 [NVIDIA 驱动下载页面] 下载合适的安装包,或者通过包管理器:
sudo apt update
sudo apt install nvidia-driver-535
安装完成后重启,以使更改生效。
安装 CUDA 工具包
驱动就绪后,继续安装 CUDA 工具包。该工具包不仅支持 kernel 编译,还提供开发头文件和示例项目,部分 Python 库可能会引用。
先检查是否已安装:
nvcc --version
若能看到版本信息,则已配置完成;否则可下载官方安装程序,或通过以下方式安装:
sudo apt install nvidia-cuda-toolkit
安装时请留意默认路径,通常是 /usr/local/cuda。
配置环境变量
要让终端和 Python 库定位 CUDA 工具和运行时,需要在 shell 配置文件(如 ~/.bashrc 或 ~/.zshrc)中添加:
export PATH=/usr/local/cuda/bin:$PATH
export LD_LIBRARY_PATH=/usr/local/cuda/lib64:$LD_LIBRARY_PATH
然后重载配置:
source ~/.bashrc
这样每次打开新终端时,都能访问完整的 CUDA 技术栈。
验证安装
最后,务必验证环境无误。先用:
nvcc --version
查看工具包版本;再用:
nvidia-smi
确认 GPU 状态正常。我们也可以通过 CuPy 或 PyCUDA 在 Python 中编程验证:
import cupy as cp
print("CuPy 版本:", cp.__version__)
print("CUDA 运行时版本:", cp.cuda.runtime.runtimeGetVersion())
print("可用设备:", cp.cuda.Device(0).name)
若这些命令均能正确输出,则说明我们的硬件与软件栈已准备就绪,可安全运行自定义 kernel,乃至进行大规模机器学习任务。
完成以上步骤后,第 1 章的基础环境与此处的深度配置相互印证。现在,我们的 Linux 系统、NVIDIA GPU 及 Python 工具已紧密集成、稳健可靠,随时可投入后续项目开发。
通过设备查询验证 GPU
我们已经安装了 CUDA 驱动和工具包,并确认系统能够识别 GPU。在深入细节之前,让我们更全面地了解一下机器中的硬件配置。设备查询(device query)可以让我们看到 GPU 的所有重要信息,例如多处理器数量、可用内存、支持的计算能力以及其他会影响 CUDA 编程的特性。
运行设备查询不仅能验证安装是否成功,还能帮助我们根据硬件特性调整 kernel 和库设置,使后续编程更加稳健、可预测且高效。
运行 Device Query
CUDA 工具包自带一个名为 deviceQuery 的示例二进制文件,用于打印系统中每块可见 GPU 的完整报告。
通常在 Linux 系统中,可在以下路径找到它:
/usr/local/cuda/samples/1_Utilities/deviceQuery/deviceQuery
如果未找到,可先编译示例目录:
cd /usr/local/cuda/samples/1_Utilities/deviceQuery
sudo make
./deviceQuery
输出会列出大量属性,其中对我们最有用的几项包括:
- Device Name(设备名称)
- Total Global Memory(全局内存总量)
- Multiprocessors Count(多处理器数量)
- CUDA Cores per SM(每个 SM 的 CUDA 核心数)
- Maximum Threads per Block(每块最大线程数)
- Shared Memory per Block(每块共享内存)
- Compute Capability(计算能力)
在 Python 中验证设备属性
为了让代码适配不同硬件,我们常常需要在程序中直接获取这些设置。使用 CuPy 可以轻松做到:
import cupy as cp
device = cp.cuda.Device(0)
attr = device.attributes
print("Device Name:", device.name)
print("Compute Capability:", f"{attr['ComputeCapabilityMajor']}.{attr['ComputeCapabilityMinor']}")
print("Total Global Memory (MB):", device.mem_info[1] // (1024 * 1024))
print("Multiprocessors:", attr['MultiProcessorCount'])
print("Max Threads per Block:", attr['MaxThreadsPerBlock'])
print("Shared Memory per Block (KB):", attr['MaxSharedMemoryPerBlock'] // 1024)
这样就能获取到所有影响我们 kernel 配置的关键参数,比如线程块大小、可分配内存量以及支持的特性。
验证计算能力(Compute Capability)
计算能力(如 6.x、7.x、8.x)代表 GPU 架构世代(Pascal、Volta、Turing、Ampere 等),每一次提升都会带来新的指令、更大共享内存和新增加速特性。大多数现代 CUDA 库要求计算能力至少 6.0 以上;如果你的设备报告为 7.5、8.6 等,就可以支持几乎所有主流 CUDA 库和深度学习框架。如果计算能力低于 3.x,有些功能可能不可用,这时要么使用兼容旧架构的库,要么考虑升级硬件。
在 PyCUDA 中也能查询:
import pycuda.autoinit
import pycuda.driver as drv
device = drv.Device(0)
print("Device Name:", device.name())
print("Compute Capability:", f"{device.compute_capability()[0]}.{device.compute_capability()[1]}")
print("Total Global Memory (MB):", device.total_memory() // (1024 * 1024))
for key, value in device.get_attributes().items():
print(f"{key}: {value}")
这样可以列出所有设备属性,帮助我们根据硬件情况灵活调整 kernel 配置。
每次更换机器、GPU 或驱动后,第一步都应运行一次设备查询,确保兼容性。通过这种做法,CUDA 编程始终能与当前硬件完美契合,保持高可靠性和灵活性。
在 PyCUDA 中实现 Kernel
我们已经用 PyCUDA 编写了一个基础的向量加法 kernel,现在想更深入地了解 PyCUDA 如何让我们在 Python 脚本中嵌入并运行自定义的 CUDA C 代码。我们要熟悉整个主机–设备工作流:分配内存、搬移数据、编写更灵活的 kernel、动态编译、启动并最终检索和验证结果。
这个示例比之前的更具扩展性。我们将在参数化、尝试新操作等方面多下功夫,充分展示 PyCUDA 作为 Python 与 CUDA 生态桥梁的强大之处。
在主机上准备数据
首先,用 NumPy 在主机上创建两个输入数组和一个用于存放结果的占位符:
import numpy as np
N = 8
a_host = np.arange(N, dtype=np.float32)
b_host = np.arange(N, 0, -1, dtype=np.float32)
print("输入数组 A:", a_host)
print("输入数组 B:", b_host)
这里用小数组演示,但对于海量数据集,同样的流程同样适用。
在设备上分配内存
接着,用 PyCUDA 将主机数组传输到 GPU,并为结果数组分配空间:
import pycuda.autoinit
import pycuda.driver as drv
import pycuda.gpuarray as gpuarray
a_device = gpuarray.to_gpu(a_host)
b_device = gpuarray.to_gpu(b_host)
c_device = gpuarray.empty_like(a_device)
此时,a_device 和 b_device 已在 GPU 上,而 c_device 则是待写入结果的显存空间。
编写自定义 CUDA C Kernel
PyCUDA 的亮点之一是可以在运行时将 CUDA C 代码作为字符串编译并加载。下面的 kernel 接收三个数组指针和长度 n,当前操作是逐元素相加,但你可以随意修改:
from pycuda.compiler import SourceModule
kernel_code = """
__global__ void add_arrays(float *a, float *b, float *c, int n)
{
int idx = threadIdx.x + blockDim.x * blockIdx.x;
if (idx < n)
{
c[idx] = a[idx] + b[idx]; // 可以在此修改为其他操作!
}
}
"""
mod = SourceModule(kernel_code)
add_arrays = mod.get_function("add_arrays")
启动 Kernel 并验证结果
我们需要指定每个线程块(block)中的线程数和网格(grid)中块的数量。为简单起见,这里将每个 block 设置为 4 个线程,并计算出所需的 block 数:
threads_per_block = 4
blocks_per_grid = (N + threads_per_block - 1) // threads_per_block
然后调用 kernel:
add_arrays(
a_device, b_device, c_device, np.int32(N),
block=(threads_per_block, 1, 1), grid=(blocks_per_grid, 1)
)
Kernel 会在 GPU 上异步执行所有线程,并将结果写入 c_device。
计算结束后,将结果拷回主机并与 CPU 计算对比:
c_host = c_device.get()
print("结果数组 C:", c_host)
expected = a_host + b_host
if np.allclose(c_host, expected):
print("结果与 CPU 计算一致。")
else:
print("发现不匹配!")
小结此流程
- 按需分配显存:只在设备上申请所需大小,无需担心主机 RAM。
- 自动搬移数据:
to_gpu()与.get()分别完成主机→设备和设备→主机的数据传输。 - 动态编译:CUDA C 代码嵌入字符串,脚本运行时即时编译,方便快速迭代。
- 灵活的启动参数:可根据问题规模调整 block 与 grid 大小。
- 早期错误检查:与 CPU 计算结果对比,及时发现问题并建立调试信心。
通过这一模式,我们在 Python 项目中用 PyCUDA 嵌入并验证 CUDA C kernel,实现了可靠且灵活的高性能编程基础。未来要展开更具挑战性和创造性的 GPU 应用时,这将是一项关键技能。
管理 Python 虚拟环境
为什么需要虚拟环境?
随着我们 GPU 编程工具包的不断扩充,Python 项目将依赖越来越多的库——例如 CuPy、PyCUDA、NumPy 以及其他各类工具。每个库可能需要特定版本,不同项目或教程之间的版本要求也会发生变化。如果不加以管理,全局 Python 环境很快就会变得混乱,导致版本冲突或依赖损坏。虚拟环境能够解决这一问题。
虚拟环境为我们提供了一个独立的、可自定义的 Python 工作空间,与系统其余部分完全隔离。在这个空间里,我们可以安装所需的精确库及其版本。这种隔离确保了结果可重现、环境易于共享,也能避免在安装新包时意外升级或冲突。同时,我们可以放心实验或“折腾”而不用担心破坏全局配置。
创建虚拟环境
在数据科学和 GPU 计算领域,Conda 是最流行的环境管理工具之一,支持 Linux、macOS 和 Windows,并为许多 GPU 库提供预编译二进制包。
我们可以为 GPU 项目专门创建一个新环境:
conda create -n mygpu python=3.10
以上命令会创建一个名为 mygpu、Python 版本为 3.10 的环境。你也可以根据需要选择其他名称和 Python 版本。
创建完成后,通过以下命令激活该环境:
conda activate mygpu
此时,所有后续安装的库都仅限于当前虚拟环境,不会影响系统的全局配置。
安装 GPU 库
在激活的环境中,我们可以安装 CuPy、PyCUDA 等必备工具。Conda-Forge 通道通常提供为 CUDA 优化的版本:
conda install -c conda-forge cupy pycuda
以上命令会自动拉取与本地 CUDA 工具包兼容的库版本。如果某些包在 Conda 中不可用,也可在该环境下使用 pip:
pip install <library-name>
通过这种方式安装的所有包都限定在当前虚拟环境内。若要查看已安装包及其版本,可运行:
conda list
保持项目可复现
为了保存并共享我们的环境配置,可以将其导出到 YAML 文件:
conda env export > environment.yml
他人只需执行:
conda env create -f environment.yml
即可在新机器或团队中重现完全相同的环境。
将工作流程构建在专用的 Conda 虚拟环境中,能够确保项目稳健、可复现且易于维护。这一最佳实践将极大助力于我们日后在 GPU 编程或其它复杂 Python 项目中的开发效率和可靠性。
总结
总而言之,我们已经成功搭建了一个可靠且专业级的 GPU 编程环境,确保了工作流程的每一步都稳健、可复现并满足需求。首先,我们加深了对 CUDA 驱动、CUDA 工具包与 NVIDIA 硬件之间关联的理解,确保各组成部分版本匹配、配置到位,以获得最佳性能。接着,我们学习了如何安装并验证驱动与工具包,更新环境变量,以及使用 nvidia-smi 与 nvcc 等工具来确认系统能承载高强度的 GPU 任务。
通过运行 CUDA 设备查询并在命令行与 Python 中查看 GPU 属性,我们深入了解了设备的计算能力、可用内存和架构限制,从而能够针对硬件优化代码与库选择。借助 PyCUDA 的实践,我们在 Python 中嵌入、编译并执行了 CUDA C kernel,全面掌握了主机–设备交互流程。最后,我们使用 Conda 创建了虚拟环境,学会了隔离依赖并构建可完全复现的开发空间,这不仅避免了冲突,还大大提升了项目的共享性、可维护性和可扩展性。