|
| 1 | +--- |
| 2 | +layout: post |
| 3 | +title: "《动手学深度学习(第二版)》学习笔记之 12. 计算性能" |
| 4 | +date: 2025-12-25 |
| 5 | +tags: [AI, notes] |
| 6 | +toc: true |
| 7 | +comments: true |
| 8 | +author: Pianfan |
| 9 | +--- |
| 10 | + |
| 11 | +在深度学习中,数据集和模型通常都很大,导致计算量也会很大。因此,计算的性能非常重要。本章将集中讨论影响计算性能的主要因素:命令式编程、符号编程、异步计算、自动并行和多 GPU 计算<!-- more --> |
| 12 | + |
| 13 | +## 12.1. 编译器和解释器 |
| 14 | + |
| 15 | +### 12.1.1. 命令式编程(imperative programming) |
| 16 | + |
| 17 | +- 按顺序执行语句 |
| 18 | + |
| 19 | +- 优点:易于使用和调试,可利用 Python 生态 |
| 20 | + |
| 21 | +- 缺点:可能效率低,Python 解释器可能成为瓶颈 |
| 22 | + |
| 23 | +### 12.1.2. 符号式编程(symbolic programming) |
| 24 | + |
| 25 | +- 步骤:定义计算流程 → 编译为可执行程序 → 输入数据执行 |
| 26 | + |
| 27 | +- 优点:运行效率高、易于移植,可在编译时优化代码 |
| 28 | + |
| 29 | +- 缺点:灵活性较低 |
| 30 | + |
| 31 | +### 12.1.3. 混合式编程 |
| 32 | + |
| 33 | +- 结合两种模式优点:用命令式编程开发调试,转换为符号式提升性能 |
| 34 | + |
| 35 | +- PyTorch 通过 torchscript 实现: |
| 36 | + |
| 37 | + - 使用 `torch.jit.script` 转换模型 |
| 38 | + |
| 39 | + - 不改变模型计算结果,仅优化执行效率 |
| 40 | + |
| 41 | +#### 12.1.3.1. 关键操作 |
| 42 | + |
| 43 | +1. 模型定义:用 `nn.Sequential` 构建网络 |
| 44 | + |
| 45 | +2. 转换优化:`net = torch.jit.script(net)` |
| 46 | + |
| 47 | +3. 性能提升:转换后通过符号式编程加速计算 |
| 48 | + |
| 49 | +4. 序列化:`net.save('模型名')` 保存模型及参数,便于部署 |
| 50 | + |
| 51 | +## 12.2. 异步计算 |
| 52 | + |
| 53 | +- **异步编程(asynchronous programming)**模型:深度学习框架将 Python 前端控制与后端执行解耦,前端发出的操作排队到后端执行,无需等待操作完成就返回控制权,提高性能 |
| 54 | + |
| 55 | +- 并行性:允许同时执行多个计算(如 CPU 与 GPU 操作、不同 GPU 间操作),后端管理线程收集并执行排队任务 |
| 56 | + |
| 57 | +### 12.2.1. PyTorch 特性 |
| 58 | + |
| 59 | +- 默认行为:GPU 操作是异步的,调用 GPU 函数时操作会排队到特定设备,不立即执行 |
| 60 | + |
| 61 | +- 依赖跟踪:后端能跟踪计算图中各步骤的依赖关系,无法并行化相互依赖的操作 |
| 62 | + |
| 63 | +### 12.2.2. 性能影响 |
| 64 | + |
| 65 | +- 异步优势:减少总计算时间(假设 10000 次计算,时间从约 $10000 (t_1+ t_2 + t_3)$ 减至 $t_1 + 10000 t_2 + t_3$,$t_1, t_2, t_3$ 分别为前端指令时间,后端计算时间,结果返回时间) |
| 66 | + |
| 67 | +- 注意事项:过度填充任务队列可能导致内存消耗过多,建议每个小批量进行同步以保持前后端大致同步 |
| 68 | + |
| 69 | +## 12.3. 自动并行 |
| 70 | + |
| 71 | +深度学习框架(如 PyTorch)通过构建计算图自动识别任务依赖,并行执行无依赖任务以提升速度 |
| 72 | + |
| 73 | +### 12.3.1. 基于 GPU 的并行计算 |
| 74 | + |
| 75 | +- 多 GPU 上的无依赖任务可自动并行执行 |
| 76 | + |
| 77 | +- 关键操作: |
| 78 | + |
| 79 | + - `torch.cuda.synchronize(device)`:等待指定 GPU 上所有计算完成,用于准确计时 |
| 80 | + |
| 81 | + - 预热操作:测量前先执行一次任务,避免缓存影响结果 |
| 82 | + |
| 83 | +- 并行执行总时间小于各设备单独执行时间之和 |
| 84 | + |
| 85 | +### 12.3.2. 并行计算与通信 |
| 86 | + |
| 87 | +- 设备间(CPU 与 GPU、GPU 间)需数据传输(如分布式优化中的梯度聚合) |
| 88 | + |
| 89 | +- 计算与通信可部分并行:计算 `y[i]` 时可传输 `y[i-1]` |
| 90 | + |
| 91 | +- 关键函数: |
| 92 | + |
| 93 | + - `y.to('cpu', non_blocking=True)`:非阻塞式复制,允许计算与传输并行 |
| 94 | + |
| 95 | +- 总耗时小于计算与通信单独耗时之和 |
| 96 | + |
| 97 | +## 12.4. 硬件 |
| 98 | + |
| 99 | +### 12.4.1. 计算机 |
| 100 | + |
| 101 | +- 关键组件: |
| 102 | + |
| 103 | + - 处理器(CPU):含 8 个及以上核心,运行操作系统等 |
| 104 | + |
| 105 | + - 内存(RAM):存储权重、激活参数、训练数据等 |
| 106 | + |
| 107 | + - 以太网连接:速度 1GB/s 到 100GB/s,高端服务器有更高级互连 |
| 108 | + |
| 109 | + - 高速扩展总线(PCIe):用于连接 GPU,服务器最多 8 个加速卡,桌面 1-2 个 |
| 110 | + |
| 111 | + - 持久性存储设备:如磁盘驱动器、固态驱动器,提供训练数据和中间检查点存储 |
| 112 | + |
| 113 | +### 12.4.2. 内存 |
| 114 | + |
| 115 | +- CPU 内存:通常为 DDR4 类型,每个模块 20-25Gb/s 带宽,64 位宽总线,2-4 个内存通道,峰值带宽 40GB/s 到 100GB/s |
| 116 | + |
| 117 | +- 内存访问:发送**地址(address)**并设置传输约 100ns,后续传输 0.2ns,应避免随机访问,使用突发模式 |
| 118 | + |
| 119 | +- 多物理存储体:可独立读取,随机读操作均匀分布时有效次数可提高,但**突发读取(burst read)**仍更优,数据结构需与 64 位边界对齐 |
| 120 | + |
| 121 | +- GPU 内存:带宽要求更高,通过加宽内存总线和使用高性能内存实现,容量通常小于 CPU 内存 |
| 122 | + |
| 123 | +### 12.4.3. 存储器 |
| 124 | + |
| 125 | +关键特性:**带宽(bandwidth)**、**延迟(latency)** |
| 126 | + |
| 127 | +- **硬盘驱动器(hard disk drive,HDD)**:含旋转盘片,容量可达 16TB(9 个盘片),成本低,有灾难性故障模式,读取延迟高,IOPs 约 100,带宽 100-200MB/s |
| 128 | + |
| 129 | +- **固态驱动器(solid state drives,SSD)**:用闪存存储,IOPs 10 万到 50 万,带宽 1-3GB/s,以块(256KB 或更大)存储,写入比读取慢,存储单元易磨损,NVMe 类型通过 PCIe 连接,PCIe4.0 上最高 8GB/s |
| 130 | + |
| 131 | +- 云存储:性能可配置,延迟高时可增加 IOPs 配置 |
| 132 | + |
| 133 | +### 12.4.4. CPU |
| 134 | + |
| 135 | +组成:处理器核心(执行机器代码)、总线(连接组件)、缓存(提供更高带宽和更低延迟)、向量处理单元(辅助线性代数和卷积运算) |
| 136 | + |
| 137 | +- 微体系结构:前端加载指令并预测路径,解码指令为微指令,执行核心可同时执行多个操作,分支预测单元重要 |
| 138 | + |
| 139 | +- 矢量化:通过向量处理单元(ARM 的 NEON、x86 的 AVX2)实现 SIMD 操作,寄存器最长可达 512 位,可组合多对数字 |
| 140 | + |
| 141 | +- 缓存: |
| 142 | + |
| 143 | + - 寄存器:非缓存,CPU 可时钟速度访问,数量几十个 |
| 144 | + |
| 145 | + - 一级缓存:32-64KB,分数据和指令,访问速度快 |
| 146 | + |
| 147 | + - 二级缓存:每个核心 256-512KB,速度慢于一级,需先检查一级缓存 |
| 148 | + |
| 149 | + - 三级缓存:多核心共享,可以非常大,常见 4-8MB |
| 150 | + |
| 151 | + 缓存未命中代价高,**错误共享(false sharing)**会降低多处理器性能 |
| 152 | + |
| 153 | +### 12.4.5. GPU 和其他加速卡 |
| 154 | + |
| 155 | +**张量核(tensor core)**:针对小型矩阵运算优化,具体取决于数值精度 |
| 156 | + |
| 157 | +局限性:不擅长处理稀疏数据和中断 |
| 158 | + |
| 159 | +### 12.4.6. 网络和总线 |
| 160 | + |
| 161 | +- PCIe:点到点连接,16 通道 PCIe4.0 上高达 32GB/s,延迟 5μs,通道数量有限制,适合大批量数据传输 |
| 162 | + |
| 163 | +- 以太网:连接计算机常用方式,带宽低于 PCIe,低级服务器 1GBit/s,安装成本低、弹性强、距离长 |
| 164 | + |
| 165 | +- 交换机:连接多个设备,支持点对点全带宽连接,PCIe 通道也可交换 |
| 166 | + |
| 167 | +- NVLink:PCIe 替代品,每条链路高达 300Gbit/s,服务器 GPU(Volta V100)有 6 个链路,消费级(RTX 2080Ti)1 个链路(100Gbit/s) |
| 168 | + |
| 169 | +## 12.5. 多 GPU 训练 |
| 170 | + |
| 171 | +单 GPU 计算能力有限,多 GPU 可提升训练速度,支持更大批量和更复杂模型 |
| 172 | + |
| 173 | +### 12.5.1. 数据并行 |
| 174 | + |
| 175 | +- 原理:将模型复制到多个 GPU,每个 GPU 处理数据的不同子集,计算局部梯度后聚合更新 |
| 176 | + |
| 177 | +- $k$ 个 GPU 并行训练过程: |
| 178 | + |
| 179 | + - 在每次训练迭代中,给定随机小批量样本被分成 $k$ 个部分,均匀分配到各 GPU 上 |
| 180 | + - 每个 GPU 根据分配给它的小批量子集,计算模型参数的损失和梯度 |
| 181 | + - 将 $k$ 个 GPU 中的局部梯度聚合,获得当前小批量的随机梯度 |
| 182 | + - 聚合梯度被重新分发到每个 GPU 中 |
| 183 | + - 每个 GPU 使用这个小批量随机梯度,来更新它所维护的完整模型参数集 |
| 184 | + |
| 185 | +## 12.6. 多 GPU 的简洁实现 |
| 186 | + |
| 187 | +### 12.6.1. 简单网络 |
| 188 | + |
| 189 | +对 ResNet-18 模型稍作修改:更小的卷积核、步长和填充,删除最大汇聚层 |
| 190 | + |
| 191 | +- 网络结构: |
| 192 | + |
| 193 | + - 初始卷积层(3x3 卷积、批归一化、ReLU 激活) |
| 194 | + - 4 个残差块(分别含 2 个残差单元,通道数 64→128→256→512) |
| 195 | + - 全局平均池化层和全连接层 |
| 196 | + |
| 197 | +### 12.6.2. 训练 |
| 198 | + |
| 199 | +用于训练的代码需要执行几个基本功能才能实现高效并行: |
| 200 | + |
| 201 | +- 在所有设备上初始化网络参数 |
| 202 | +- 迭代时将小批量数据分配到所有设备 |
| 203 | +- 跨设备并行计算损失及梯度 |
| 204 | +- 聚合梯度并更新参数 |
| 205 | + |
| 206 | +实现要点: |
| 207 | + |
| 208 | +- 使用 `nn.DataParallel` 实现多 GPU 并行 |
| 209 | +- 优化器采用 SGD |
| 210 | +- 损失函数为交叉熵损失 |
| 211 | +- 训练过程中记录时间和测试精度 |
| 212 | + |
| 213 | +## 12.7. 参数服务器 |
| 214 | + |
| 215 | +### 12.7.1. 数据并行训练 |
| 216 | + |
| 217 | +数据并行是分布式训练中实现相对简单的方法,在 GPU 显存充足的实际场景(除图深度学习外)值得推荐 |
| 218 | + |
| 219 | +核心是梯度聚合后将更新参数广播给所有 GPU,聚合可在特定 GPU、CPU 或分不同部分在不同 GPU 上进行 |
| 220 | + |
| 221 | +同步策略影响效率,受硬件总线带宽差异影响,相同参数同步操作时间可能在 15 毫秒到 80 毫秒之间 |
| 222 | + |
| 223 | +可在计算部分梯度时同步已准备好的参数以提升性能 |
| 224 | + |
| 225 | +### 12.7.2. 环同步(Ring Synchronization) |
| 226 | + |
| 227 | +现代深度学习硬件存在定制网络连接,NVLink 聚合带宽高于 PCIe |
| 228 | + |
| 229 | +最优同步策略是将网络分解为两个环直接同步数据 |
| 230 | + |
| 231 | +环同步通过将梯度分为 n 个块,从节点 i 开始同步块 i,使聚合梯度时间不随环大小增加而增长 |
| 232 | + |
| 233 | +与其他同步算法本质差异在于同步路径更精细 |
| 234 | + |
| 235 | +### 12.7.3. 多机训练 |
| 236 | + |
| 237 | +多机训练需处理服务器间低带宽连接及设备同步问题 |
| 238 | + |
| 239 | +步骤:读取数据并分配到 GPU 计算梯度→本地 GPU 梯度聚合→梯度发送到本地 CPU→CPU 将梯度发送到中央参数服务器聚合→更新参数并广播回各 CPU→参数发送到本地 GPU→所有 GPU 参数更新完成 |
| 240 | + |
| 241 | +单参数服务器会成瓶颈,增加参数服务器数量(每个存储 1/n 参数)可突破瓶颈,实际中机器可同时作为工作节点和服务器 |
| 242 | + |
| 243 | +### 12.7.4. 键值存储 |
| 244 | + |
| 245 | +为简化分布式多 GPU 训练实现,使用键值存储抽象 |
| 246 | + |
| 247 | +梯度计算是**交换归约(commutative reduction)**,运算顺序无关,不同梯度间独立 |
| 248 | + |
| 249 | +核心操作: |
| 250 | + |
| 251 | +- push(key,value):工作节点发送梯度值到公共存储并聚合 |
| 252 | + |
| 253 | +- pull(key,value):从公共存储获取聚合值 |
| 254 | + |
| 255 | +可解耦统计建模人员与系统工程师的关注点 |
0 commit comments