趋近智
训练深度学习 (deep learning)模型,特别是那些拥有大量数据集的大型模型,可能是一个耗时的过程。缩短此训练时间的一种非常有效的方法是,通过使用图形处理单元(GPU)的并行处理能力。提供了在 Julia 中通过 CUDA.jl 包使用 NVIDIA GPU 的详细信息,并展示了它如何与 Flux.jl 结合以加速深度学习工作流程。
如果您的机器配备了兼容的 NVIDIA GPU,您可以发挥其强大的计算能力。Julia 的 CUDA.jl 包为 NVIDIA CUDA 平台提供了一个全面的接口,允许直接进行 GPU 编程,对我们来说更重要的是,它使 Flux.jl 等深度学习框架能够在 GPU 上运行操作。
在将 GPU 与 Flux 结合使用之前,您需要确保系统已正确设置并安装了 CUDA.jl。
using Pkg
Pkg.add("CUDA")
using CUDA
if CUDA.functional()
println("CUDA 正常工作,GPU 可用!")
CUDA.versioninfo() # 打印有关 CUDA 工具包和驱动程序的信息
# 您也可以查询设备属性
# 例如,查看可用设备:
# for (i, dev) in enumerate(CUDA.devices())
# println("$i: $(CUDA.name(dev))")
# end
else
println("CUDA 功能不正常。请确保驱动程序和工具包已正确设置。")
end
如果 CUDA.functional() 返回 true,则可以继续。如果不是,您可能需要排查 NVIDIA 驱动程序或 CUDA 工具包的安装问题。CUDA.jl 的错误消息通常会提供线索。Flux.jl 在设计时就考虑了 GPU 计算,使得从 CPU 到 GPU 的转换非常顺畅。将模型或数据移至 GPU 的主要机制是 Flux 提供的 gpu 函数(它通常在底层使用 CUDA.jl 的 cu 函数来处理 NVIDIA GPU)。相反地,cpu 函数将它们移回 CPU。
移动模型:
要将整个 Flux 模型移至 GPU,您只需对其应用 gpu 函数:
using Flux
# 定义一个简单模型
model_cpu = Chain(
Dense(10, 20, relu),
Dense(20, 5, relu),
Dense(5, 1)
)
# 检查 GPU 是否可用并移动模型
if CUDA.functional()
model_gpu = model_cpu |> gpu
println("模型已移至 GPU。")
else
model_gpu = model_cpu # 回退到 CPU
println("GPU 不可用,模型保留在 CPU 上。")
end
当您将 gpu 应用于 Chain 或任何自定义 Flux 模型结构时,Flux 会智能地遍历模型的层和参数 (parameter),将它们转换为与 GPU 兼容的结构(例如,权重 (weight)和偏置 (bias)的 CuArray)。
移动数据:
同样,您的输入数据和目标标签需要位于 GPU 上,模型才能在那里处理它们。数据通常存储在 CPU 上的标准 Julia Array 中。要将它们移至 GPU,您也使用 gpu 函数(或直接使用 CUDA.cu):
# 示例 CPU 数据
x_cpu = rand(Float32, 10, 128) # 10 个特征,128 个样本
y_cpu = rand(Float32, 1, 128)
# 移动数据到 GPU
if CUDA.functional()
x_gpu = x_cpu |> gpu # 或 CUDA.cu(x_cpu)
y_gpu = y_cpu |> gpu # 或 CUDA.cu(y_cpu)
println("数据已移至 GPU。")
# 检查类型
# println(typeof(x_gpu)) # 应显示 CuArray{Float32, 2}
else
x_gpu = x_cpu
y_gpu = y_cpu
println("GPU 不可用,数据保留在 CPU 上。")
end
移至 GPU 的数据通常表示为 CUDA.jl 的 CuArray 对象。这些数组驻留在 GPU 的内存中。
一旦您的模型和数据可以移至 GPU,调整训练循环将涉及一些重要更改:
让我们看一个简化的训练循环结构:
using Flux, CUDA, Optimisers
using MLUtils: DataLoader # 用于批处理
# 0. 定义模型和数据(如前所示)
features = 10
outputs = 1
n_samples = 1000
model_cpu = Chain(Dense(features => 64, relu), Dense(64 => outputs))
X_train_cpu = rand(Float32, features, n_samples)
Y_train_cpu = rand(Float32, outputs, n_samples)
# 1. 如果可用,则为 GPU 设置
use_gpu = CUDA.functional()
if use_gpu
model = model_cpu |> gpu
println("在 GPU 上训练。")
else
model = model_cpu
println("在 CPU 上训练。")
end
# 优化器
opt_state = Optimisers.setup(Optimisers.Adam(1e-3), model)
# 损失函数
loss(m, x, y) = Flux.mse(m(x), y)
# 用于批处理的数据加载器
batch_size = 64
train_loader = DataLoader((X_train_cpu, Y_train_cpu), batchsize=batch_size, shuffle=true)
# 2. 训练循环
epochs = 10
for epoch in 1:epochs
epoch_loss = 0.0
num_batches = 0
for (x_batch_cpu, y_batch_cpu) in train_loader
# 将当前批次移至 GPU
x_batch = use_gpu ? (x_batch_cpu |> gpu) : x_batch_cpu
y_batch = use_gpu ? (y_batch_cpu |> gpu) : y_batch_cpu
# 计算损失和梯度
val, grads = Flux.withgradient(model) do m
loss(m, x_batch, y_batch)
end
# 更新模型参数
Optimisers.update!(opt_state, model, grads[1])
# 累加损失(如果在 GPU 上,则移至 CPU 进行聚合)
epoch_loss += use_gpu ? cpu(val) : val # 确保 'val' 在 CPU 上进行累加
num_batches += 1
end
avg_loss = epoch_loss / num_batches
println("周期: $epoch, 平均损失: $avg_loss")
end
# 训练结束后,如果您需要 CPU 上的模型:
# model_final_cpu = model |> cpu
在此循环中:
DataLoader 的每个小批次 (x_batch_cpu, y_batch_cpu)(产生 CPU 数组)在传递给模型之前,都使用 x_batch_cpu |> gpu 和 y_batch_cpu |> gpu 明确地移至 GPU。val 在 GPU 上计算。为了聚合或打印,最好使用 cpu(val) 将其移回 CPU。Flux 和 Zygote 将确保梯度也在 GPU 上计算。虽然使用 GPU 可以大幅提升速度,但为了高效利用 GPU,有几个因素需要记住:
CuArray 占用 GPU 内存。CUDA.memory_status() 查看 GPU 内存状态。Float32 而不是 Float64)。CuArray 配合使用,或者实现自定义 CUDA 内核(这是一个更高级的主题)。Float32)方面比双精度(Float64)具有明显更好的性能。大多数深度学习 (deep learning)模型使用 Float32 也能很好地训练,因此这是常见的选择。在面向 GPU 时,请确保您的数据和模型参数为 Float32。Flux 层通常默认为 Float32 或适应输入数据类型。使用 Flux.jl 进行 GPU 加速时的数据流和模型放置。重要步骤包括将模型一次性移至 GPU,然后在训练循环中将数据批次传输到 GPU。
通过理解这些原则,您可以有效地修改您的 Flux.jl 训练脚本以使用 GPU,减少复杂模型的训练时间,并使您能够更快地迭代深度学习项目。接下来的章节将介绍如何分析您的模型以找到瓶颈和进一步的优化技巧。
这部分内容有帮助吗?
© 2026 ApX Machine LearningAI伦理与透明度•