在深度学习中,数据集和模型通常都很大,导致计算量也会很大。因此,计算的性能非常重要。本章将集中讨论影响计算性能的主要因素:命令式编程、符号编程、异步计算、自动并行和多 GPU 计算
12.1. 编译器和解释器
12.1.1. 命令式编程(imperative programming)
-
按顺序执行语句
-
优点:易于使用和调试,可利用 Python 生态
-
缺点:可能效率低,Python 解释器可能成为瓶颈
12.1.2. 符号式编程(symbolic programming)
-
步骤:定义计算流程 → 编译为可执行程序 → 输入数据执行
-
优点:运行效率高、易于移植,可在编译时优化代码
-
缺点:灵活性较低
12.1.3. 混合式编程
-
结合两种模式优点:用命令式编程开发调试,转换为符号式提升性能
-
PyTorch 通过 torchscript 实现:
-
使用
torch.jit.script转换模型 -
不改变模型计算结果,仅优化执行效率
-
12.1.3.1. 关键操作
-
模型定义:用
nn.Sequential构建网络 -
转换优化:
net = torch.jit.script(net) -
性能提升:转换后通过符号式编程加速计算
-
序列化:
net.save('模型名')保存模型及参数,便于部署
12.2. 异步计算
-
异步编程(asynchronous programming)模型:深度学习框架将 Python 前端控制与后端执行解耦,前端发出的操作排队到后端执行,无需等待操作完成就返回控制权,提高性能
-
并行性:允许同时执行多个计算(如 CPU 与 GPU 操作、不同 GPU 间操作),后端管理线程收集并执行排队任务
12.2.1. PyTorch 特性
-
默认行为:GPU 操作是异步的,调用 GPU 函数时操作会排队到特定设备,不立即执行
-
依赖跟踪:后端能跟踪计算图中各步骤的依赖关系,无法并行化相互依赖的操作
12.2.2. 性能影响
-
异步优势:减少总计算时间(假设 10000 次计算,时间从约 $10000 (t_1+ t_2 + t_3)$ 减至 $t_1 + 10000 t_2 + t_3$,$t_1, t_2, t_3$ 分别为前端指令时间,后端计算时间,结果返回时间)
-
注意事项:过度填充任务队列可能导致内存消耗过多,建议每个小批量进行同步以保持前后端大致同步
12.3. 自动并行
深度学习框架(如 PyTorch)通过构建计算图自动识别任务依赖,并行执行无依赖任务以提升速度
12.3.1. 基于 GPU 的并行计算
-
多 GPU 上的无依赖任务可自动并行执行
-
关键操作:
-
torch.cuda.synchronize(device):等待指定 GPU 上所有计算完成,用于准确计时 -
预热操作:测量前先执行一次任务,避免缓存影响结果
-
-
并行执行总时间小于各设备单独执行时间之和
12.3.2. 并行计算与通信
-
设备间(CPU 与 GPU、GPU 间)需数据传输(如分布式优化中的梯度聚合)
-
计算与通信可部分并行:计算
y[i]时可传输y[i-1] -
关键函数:
y.to('cpu', non_blocking=True):非阻塞式复制,允许计算与传输并行
-
总耗时小于计算与通信单独耗时之和
12.4. 硬件
12.4.1. 计算机
-
关键组件:
-
处理器(CPU):含 8 个及以上核心,运行操作系统等
-
内存(RAM):存储权重、激活参数、训练数据等
-
以太网连接:速度 1GB/s 到 100GB/s,高端服务器有更高级互连
-
高速扩展总线(PCIe):用于连接 GPU,服务器最多 8 个加速卡,桌面 1-2 个
-
持久性存储设备:如磁盘驱动器、固态驱动器,提供训练数据和中间检查点存储
-
12.4.2. 内存
-
CPU 内存:通常为 DDR4 类型,每个模块 20-25Gb/s 带宽,64 位宽总线,2-4 个内存通道,峰值带宽 40GB/s 到 100GB/s
-
内存访问:发送地址(address)并设置传输约 100ns,后续传输 0.2ns,应避免随机访问,使用突发模式
-
多物理存储体:可独立读取,随机读操作均匀分布时有效次数可提高,但突发读取(burst read)仍更优,数据结构需与 64 位边界对齐
-
GPU 内存:带宽要求更高,通过加宽内存总线和使用高性能内存实现,容量通常小于 CPU 内存
12.4.3. 存储器
关键特性:带宽(bandwidth)、延迟(latency)
-
硬盘驱动器(hard disk drive,HDD):含旋转盘片,容量可达 16TB(9 个盘片),成本低,有灾难性故障模式,读取延迟高,IOPs 约 100,带宽 100-200MB/s
-
固态驱动器(solid state drives,SSD):用闪存存储,IOPs 10 万到 50 万,带宽 1-3GB/s,以块(256KB 或更大)存储,写入比读取慢,存储单元易磨损,NVMe 类型通过 PCIe 连接,PCIe4.0 上最高 8GB/s
-
云存储:性能可配置,延迟高时可增加 IOPs 配置
12.4.4. CPU
组成:处理器核心(执行机器代码)、总线(连接组件)、缓存(提供更高带宽和更低延迟)、向量处理单元(辅助线性代数和卷积运算)
-
微体系结构:前端加载指令并预测路径,解码指令为微指令,执行核心可同时执行多个操作,分支预测单元重要
-
矢量化:通过向量处理单元(ARM 的 NEON、x86 的 AVX2)实现 SIMD 操作,寄存器最长可达 512 位,可组合多对数字
-
缓存:
-
寄存器:非缓存,CPU 可时钟速度访问,数量几十个
-
一级缓存:32-64KB,分数据和指令,访问速度快
-
二级缓存:每个核心 256-512KB,速度慢于一级,需先检查一级缓存
-
三级缓存:多核心共享,可以非常大,常见 4-8MB
缓存未命中代价高,错误共享(false sharing)会降低多处理器性能
-
12.4.5. GPU 和其他加速卡
张量核(tensor core):针对小型矩阵运算优化,具体取决于数值精度
局限性:不擅长处理稀疏数据和中断
12.4.6. 网络和总线
-
PCIe:点到点连接,16 通道 PCIe4.0 上高达 32GB/s,延迟 5μs,通道数量有限制,适合大批量数据传输
-
以太网:连接计算机常用方式,带宽低于 PCIe,低级服务器 1GBit/s,安装成本低、弹性强、距离长
-
交换机:连接多个设备,支持点对点全带宽连接,PCIe 通道也可交换
-
NVLink:PCIe 替代品,每条链路高达 300Gbit/s,服务器 GPU(Volta V100)有 6 个链路,消费级(RTX 2080Ti)1 个链路(100Gbit/s)
12.5. 多 GPU 训练
单 GPU 计算能力有限,多 GPU 可提升训练速度,支持更大批量和更复杂模型
12.5.1. 数据并行
-
原理:将模型复制到多个 GPU,每个 GPU 处理数据的不同子集,计算局部梯度后聚合更新
-
$k$ 个 GPU 并行训练过程:
- 在每次训练迭代中,给定随机小批量样本被分成 $k$ 个部分,均匀分配到各 GPU 上
- 每个 GPU 根据分配给它的小批量子集,计算模型参数的损失和梯度
- 将 $k$ 个 GPU 中的局部梯度聚合,获得当前小批量的随机梯度
- 聚合梯度被重新分发到每个 GPU 中
- 每个 GPU 使用这个小批量随机梯度,来更新它所维护的完整模型参数集
12.6. 多 GPU 的简洁实现
12.6.1. 简单网络
对 ResNet-18 模型稍作修改:更小的卷积核、步长和填充,删除最大汇聚层
-
网络结构:
- 初始卷积层(3x3 卷积、批归一化、ReLU 激活)
- 4 个残差块(分别含 2 个残差单元,通道数 64→128→256→512)
- 全局平均池化层和全连接层
12.6.2. 训练
用于训练的代码需要执行几个基本功能才能实现高效并行:
- 在所有设备上初始化网络参数
- 迭代时将小批量数据分配到所有设备
- 跨设备并行计算损失及梯度
- 聚合梯度并更新参数
实现要点:
- 使用
nn.DataParallel实现多 GPU 并行 - 优化器采用 SGD
- 损失函数为交叉熵损失
- 训练过程中记录时间和测试精度
12.7. 参数服务器
12.7.1. 数据并行训练
数据并行是分布式训练中实现相对简单的方法,在 GPU 显存充足的实际场景(除图深度学习外)值得推荐
核心是梯度聚合后将更新参数广播给所有 GPU,聚合可在特定 GPU、CPU 或分不同部分在不同 GPU 上进行
同步策略影响效率,受硬件总线带宽差异影响,相同参数同步操作时间可能在 15 毫秒到 80 毫秒之间
可在计算部分梯度时同步已准备好的参数以提升性能
12.7.2. 环同步(Ring Synchronization)
现代深度学习硬件存在定制网络连接,NVLink 聚合带宽高于 PCIe
最优同步策略是将网络分解为两个环直接同步数据
环同步通过将梯度分为 n 个块,从节点 i 开始同步块 i,使聚合梯度时间不随环大小增加而增长
与其他同步算法本质差异在于同步路径更精细
12.7.3. 多机训练
多机训练需处理服务器间低带宽连接及设备同步问题
步骤:读取数据并分配到 GPU 计算梯度→本地 GPU 梯度聚合→梯度发送到本地 CPU→CPU 将梯度发送到中央参数服务器聚合→更新参数并广播回各 CPU→参数发送到本地 GPU→所有 GPU 参数更新完成
单参数服务器会成瓶颈,增加参数服务器数量(每个存储 1/n 参数)可突破瓶颈,实际中机器可同时作为工作节点和服务器
12.7.4. 键值存储
为简化分布式多 GPU 训练实现,使用键值存储抽象
梯度计算是交换归约(commutative reduction),运算顺序无关,不同梯度间独立
核心操作:
-
push(key,value):工作节点发送梯度值到公共存储并聚合
-
pull(key,value):从公共存储获取聚合值
可解耦统计建模人员与系统工程师的关注点