详细摘要 摘要

生成:2025-05-16 20:58

摘要详情

音频文件
Stanford CS224N: NLP w/ DL | Spring 2024 | Lecture 12 - Efficient Training, Shikhar Murty
摘要类型
详细摘要
LLM 提供商
openai
LLM 模型
gemini-2.5-pro-exp-03-25
已创建
2025-05-16 20:58:45

标题: Stanford CS224N: NLP w/ DL | Spring 2024 | Lecture 12 - Efficient Training, Shikhar Murty
描述: 本讲座(Spring 2024)旨在帮助学生掌握大规模神经网络的高效训练方法,以应对课程项目中的实际计算资源和内存限制挑战。
副标题: 该讲座主要讨论了大规模神经网络的高效训练方法。首先,讲师发布了课程项目提案的评分即将公布以及项目里程碑要求的通知。

概览/核心摘要 (Executive Summary)

本讲座 (Stanford CS224N Lecture 12, Spring 2024) 首先通告了课程项目提案评分即将发布及项目里程碑要求等重要节点安排,随后深入探讨了在GPU上高效训练大规模神经网络的多种关键技术。讲座的核心目标是帮助学生应对期末项目中可能遇到的计算资源和内存限制问题,内容不直接涉及自然语言处理理论,而是聚焦于机器学习系统的实践层面。

讲座首先介绍了混合精度训练 (Mixed Precision Training),详细阐释了FP32、FP16及BFloat16等不同浮点数表示的特性、显存占用和数值范围差异。重点讨论了如何通过梯度缩放 (Gradient Scaling) 或直接使用BFloat16来在节省显存的同时避免训练不稳定的问题,从而显著加速训练过程并减少显存占用。

其次,讲座详细讨论了多GPU训练 (Multi-GPU Training) 的策略。从基础的分布式数据并行 (DDP) 及其固有的显存瓶颈开始,过渡到微软DeepSpeed项目提出的Zero Redundancy Optimizer (ZeRO) 技术。ZeRO通过其三个不同阶段(Stage 1, 2, 3),特别是FSDP (Fully Sharded Data Parallel),实现了对模型参数、梯度和优化器状态不同程度的切分与分布式存储,从而在多GPU环境下更有效地利用总体显存资源。

最后,讲座介绍了参数高效微调 (Parameter-Efficient Fine-tuning, PEFT) 的概念和方法,重点讲解了LoRA (Low-Rank Adaptation) 技术。LoRA通过冻结大部分预训练模型参数,仅训练少量为特定任务添加的低秩适配器矩阵,能够在大幅减少训练所需计算资源和参数存储开销的同时,保持甚至有时提升模型在下游任务上的性能。讲座还探讨了PEFT在应对模型过参数化、计算资源限制、促进AI普惠化及降低环境影响等方面的积极意义。讲座以一个实用的决策流程图总结了在不同硬件和模型规模情况下,应如何选择和组合运用这些高效训练技术。

数字表示与浮点数基础

讲座首先回顾了计算机中数字(特别是浮点数)的表示方法,这对理解后续的深度学习训练技术至关重要。

  • FP32 (单精度浮点数):
    • 占用32位(4字节)内存。
    • 具有较广的表示范围 (range) 和较高的精度 (precision)。
    • 结构:1个符号位,指数位(绿色部分,决定范围),尾数位(蓝色部分,决定精度)。
  • FP16 (半精度浮点数):
    • 占用16位(2字节)内存,是FP32的一半。
    • 通过减少指数位和尾数位来实现内存压缩,导致:
      • 表示范围减小:过小的数会变为0,过大的数会变为NaN (Not a Number)。
      • 精度降低:例如,1.0001 可能被舍入为 1.0
    • 关键属性:
      • epsilon: 最小的数(\epsilon),使得 (1 + \epsilon \neq 1)。小于此值的数加到1上会被舍入。
      • smallest normal: FP16能表示的最小正规数,小于此值则变为0。

混合精度训练 (Mixed Precision Training)

混合精度训练旨在利用FP16的显存和计算速度优势,同时避免其数值不稳定性问题。

  • 问题背景:

    • 使用FP32训练大型模型时,常因模型参数和梯度占用过多GPU显存而导致“显存不足 (out of memory)”错误。
    • 直接将所有参数和梯度转换为FP16存在问题:
      1. 范围损失 (Loss of Range): 许多微小的梯度值(在FP32下非零)在FP16下会因超出表示范围下限而直接变为0。讲座引用NVIDIA的图表指出:“超过一半的梯度在FP16下会直接变成0”,这对模型学习非常不利。
      2. 精度损失 (Loss of Precision): 参数更新不够精确。
  • 初步解决方案及其缺陷:

    1. 维护一份FP32的模型权重副本(称为“主权重” Master Weights)。
    2. 前向传播 (Forward Pass) 时,将FP32权重转换为FP16进行计算。
    3. 反向传播 (Backward Pass) 时,计算得到FP16格式的梯度。
    4. 将FP16梯度上转换为FP32,用以上更新FP32主权重。
    5. 将更新后的FP32主权重复制回FP16模型。
    6. 缺陷: Speaker 1指出,由于梯度本身在FP16下计算,许多梯度在转换为FP32之前就已经是0了,问题依然存在。
  • 改进方案:梯度缩放 (Gradient Scaling):

    1. 获取一批数据,在FP16下进行前向传播,得到损失值 (Loss)。
    2. 将损失值乘以一个较大的缩放因子S (Scale Factor) (例如100, 1000)。
    3. 基于缩放后的损失计算梯度。此时,原本较小的梯度值也会相应增大,从而在FP16下保留下来,避免变为0。
    4. 得到的FP16梯度上转换为FP32。
    5. 将FP32梯度除以缩放因子S,恢复其原始量级。
    6. 使用恢复后的FP32梯度更新FP32主权重。
    7. PyTorch实现: 通过实例化 torch.cuda.amp.GradScaler 对象并在 torch.cuda.amp.autocast 上下文管理器中执行前向和反向传播来实现。
    8. 复杂性: 需要动态调整缩放因子S。若S过大可能导致梯度溢出 (NaN),若S过小则缩放效果不明显。
  • 更优方案:BFloat16 (Brain Floating Point 16):

    • BFloat16是另一种16位浮点格式,其设计思路是:牺牲部分精度,以保持与FP32相同的动态表示范围
    • 它拥有与FP32相同的指数位数(8位),但尾数位数较少(7位,而FP16有10位尾数,FP32有23位尾数)。
    • 由于其动态范围与FP32一致,使用BFloat16进行混合精度训练时通常不再需要梯度缩放
    • 实现更简单:只需将模型的前向和反向传播包裹在正确的上下文管理器中 (如 PyTorch 的 autocast(dtype=torch.bfloat16))。
    • 局限性: BFloat16并非在所有GPU上都可用,通常需要较新的NVIDIA Ampere架构 (如 H100, A100, A6000) 或更新的GPU。可以通过 torch.cuda.is_bf16_supported() 检查。
    • 效果: 实验结果表明 (以情感分类任务在单个A100上为例),使用BFloat16的混合精度训练相较于FP32 (讲座中为Float64作为对比基准),Speaker 1提到其“训练时间减少了约三分之一,精度大致相同甚至略好(可能源于半精度的正则化效应),且显存占用大大减少”。训练速度的提升主要因为半精度下的矩阵乘法运算更快。

多GPU训练 (Multi-GPU Training)

当单个GPU无法满足训练需求时,需要利用多GPU进行分布式训练。

  • GPU显存内容 (初步简化版):

    • 模型参数 (Model Parameters): 若使用混合精度,则为FP16 (每个参数2字节)。
    • 优化器状态 (Optimizer State): 例如Adam优化器需要存储动量 (momentum) 和方差 (variance) 项。如果使用混合精度训练,FP32主权重也属于优化器维护的状态。Speaker 1提到,对于混合精度训练中的Adam优化器,每参数需要额外的12字节 (FP32):包括FP32主参数备份 (4字节)、动量项 (4字节) 和方差项 (4字节)。
    • 梯度 (Gradients): 在混合精度下通常为FP16 (每个参数2字节)。
  • 分布式数据并行 (DDP - Distributed Data Parallel):

    • 基本流程:
      1. 将数据集划分为N份(N为GPU数量)。
      2. 在每个GPU上维护一个模型的同步副本。
      3. 每个模型副本处理其对应的数据分片,进行前向和反向传播,得到各自的梯度(由于数据不同,梯度也不同)。
      4. 梯度同步: 使用 AllReduce MPI原语操作。AllReduce会收集所有GPU上的梯度,将它们相加(或求平均),然后将聚合后的梯度分发回每个GPU。
        • FP16梯度的AllReduce通信开销为 每个参数2字节
      5. 每个GPU上的优化器使用同步后的全局梯度来更新其本地模型副本,从而保持模型同步。
    • DDP的显存问题: 显存扩展性差 (poor memory scaling)。每个GPU都需要存储:
      1. 完整的模型参数副本 (例如FP16)。
      2. 完整的梯度副本 (例如FP16)。
      3. 完整的优化器状态副本 (例如FP32)。
        这导致显存冗余较大。
  • Zero Redundancy Optimizer (ZeRO): 由微软DeepSpeed项目提出,旨在通过切分 (sharding) 状态来减少冗余,提升显存效率。

    • 核心思想: 将模型参数、梯度、优化器状态分片到不同的GPU上,而不是每个GPU都保存完整副本。

    • ZeRO Stage 1: 切分优化器状态 (Sharding Optimizer States)

      • 每个GPU拥有:
        • 完整的模型参数副本 (FP16)。
        • 其数据分片对应的完整梯度 (FP16)。
        • 仅一部分 (a shard) 的优化器状态 (FP32)
      • 每个GPU仅负责更新其拥有优化器状态分片所对应的那些模型参数。
      • 通信流程:
        1. 各GPU计算其数据分片的梯度。
        2. ReduceScatter (MPI原语): 每个GPU将其计算出的完整梯度中对应其他GPU负责更新的部分发送给相应的GPU,同时接收其他GPU发送给自己的梯度部分。最终,每个GPU只拥有其负责参数的聚合梯度。
        3. 各GPU使用其本地优化器状态分片和接收到的聚合梯度分片来更新其负责的那部分模型参数。
        4. AllGather (MPI原语): 所有GPU将各自更新后的参数分片收集起来,广播给所有GPU,使得每个GPU最终都拥有完整的、更新后的模型参数。
      • 通信开销: Speaker 1指出,由于 AllReduce 操作在逻辑上等价于 ReduceScatter 后接 AllGather,ZeRO Stage 1 的总通信量与DDP相同。“我们免费节省了显存”。
    • ZeRO Stage 2: 切分梯度和优化器状态 (Sharding Gradients and Optimizer States)

      • 除了优化器状态,梯度也被切分。
      • 完整的梯度向量不会在任何单个GPU上实例化
      • 通信流程:
        1. 反向传播逐层进行。
        2. 当计算出某层 (如Layer J) 参数的梯度后,立即通过 Reduce 操作(类似于ReduceScatter,但这里是将各GPU上对同一参数子集的梯度贡献聚合到负责该参数子集的特定GPU上)将其发送给负责该层参数分片的那个GPU。
        3. 发送后,立即释放用于存储该梯度分量的临时显存。
        4. 负责该参数分片的GPU在其本地聚合收到的梯度,并结合其优化器状态分片来更新参数。
        5. 最后通过 AllGather 同步所有更新后的参数。
      • 通信开销: 同样地,Speaker 1认为这“实际上也是免费的”,因为通信模式(Reduce + AllGather)与DDP的AllReduce(ReduceScatter + AllGather)在总量上相似。
    • ZeRO Stage 3 / FSDP (Fully Sharded Data Parallel): 切分模型参数、梯度和优化器状态

      • 当模型参数本身大到单个GPU都无法容纳时使用。这是ZeRO的最终阶段,通常称为FSDP。
      • 通信开销会增加,不再是“免费”的显存节省。
      • 高级流程:
        1. 模型被划分为多个FSDP单元 (FSDP units)。这些单元内的参数被“拉平” (flat parameters) 并分片到各个GPU。每个GPU只持有模型参数的一部分。
        2. 前向传播 (Forward Pass) (以某一层为例,如 Layer 4):
          • 执行 AllGather 操作,使得每个GPU都获取到执行当前层计算所需的完整参数(例如Layer 4的参数)。
          • 执行该层的前向计算。
          • 计算完成后,可以丢弃非本地分片的参数副本以节省显存。讲座中提到可以“预取 (prefetch)”下一计算单元所需参数,同时在计算当前单元时进行,并释放已计算完毕单元的非本地参数。
        3. 反向传播 (Backward Pass) (以Layer 4为例):
          • 再次执行 AllGather 获取当前层(Layer 4)的完整参数(因为前向传播后可能已丢弃非本地部分)。
          • 计算梯度。由于各GPU数据不同,梯度也不同。
          • 执行 ReduceScatter 操作,将Layer 4各参数分片对应的完整梯度(已聚合各GPU的贡献)发送到负责该分片的GPU。
        4. 各GPU使用其收到的对应分片的完整梯度和其拥有的优化器状态分片来更新其负责的模型参数分片。
        5. (讲座未明确指定FSDP最后一步的同步操作,但逻辑上在参数更新后,下一轮迭代开始前需要确保模型状态的一致性,通常通过AllGather实现参数同步,或依赖后续前向传播中的AllGather重建参数)。
      • 通信开销: 每层大致涉及两次 AllGather (一次用于前向,一次用于反向) 和一次 ReduceScatter (用于反向),比Stage 1和2的开销大。
      • FSDP 实现细节:
        • 通信与计算重叠: 通过预取下一层参数、在当前层计算的同时进行通信等方式隐藏通信延迟。
        • 显存管理: 及时释放不再需要的参数或激活值占用的显存。FSDP Unit 0(通常是网络的第一部分)在某些实现中可能永不被释放。
        • FSDP单元划分策略 (Sharding Policy): 如何将网络划分为FSDP单元对效率至关重要,通常具有架构特异性。例如,Transformers架构有专门优化的划分策略。不合适的策略可能导致效率低下。
  • 被忽略的显存占用:模型激活值 (Model Activations):

    • 在反向传播过程中需要存储前向传播的激活值。
    • 其占用的显存与批量大小 (Batch Size) 线性相关
    • 即使使用混合精度,激活值通常以FP16或BFloat16存储。
    • ZeRO的各个阶段主要解决参数、梯度、优化器状态的显存问题,并未直接解决激活值带来的显存压力。 (注:梯度检查点/激活检查点技术可以缓解此问题,讲座中简要提及)。

参数高效微调 (Parameter-Efficient Fine-tuning - PEFT)

当全量微调 (Full Fine-tuning) 因资源限制不可行时,PEFT提供了一种替代方案。

  • 动机:

    • 资源限制: 即便使用batch size为1和各种优化技巧,模型也可能无法在可用硬件上进行全量微调。
    • 泛化性: 对于过参数化的大模型和小型下游数据集,PEFT可能通过限制更新参数数量来获得更好的泛化性能。
    • 可持续性与AI普惠化: 训练超大模型的计算需求增长迅速,可能超过全球计算能力,并使AI研发集中在少数资金雄厚的组织手中。PEFT有助于降低门槛。讲座引用数据称:“训练GPT-3的成本相当于110万吨碳排放”,以及“一个强化学习课程作业中,若所有学生使用最高效算法,可节省约880千瓦时电力,相当于一个美国家庭一个月的用电量”。
    • 避免模型偏见: 减少少数机构主导模型开发可能带来的价值体系偏见。
    • 研究焦点转变: 鼓励从单纯追求精度转向关注效率。
    • 环境成本: 降低训练大型模型的巨大隐性环境成本。
  • 核心思想: 只更新模型参数的一小部分子集,而非全部参数。

  • LoRA (Low-Rank Adaptation):

    • 观察: 大型语言模型在微调时,其权重更新矩阵 ((\Delta W)) 往往具有“低内在秩 (low intrinsic rank)”。
    • 方法: 对于预训练权重矩阵 (W_0),LoRA不直接更新(W_0),而是学习一个低秩的更新量 (BA),即输出 (h = W_0x + \alpha \frac{s}{r} BAx) (讲座幻灯片公式为 (W_0x + \alpha BAx),其中 (B) (D x R 维),(A) (R x K 维))。
      其中:
      • (W_0) (D x K 维) 被冻结。
      • (A) (R x K 维) 和 (B) (D x R 维) 是两个低秩矩阵,R是秩 (rank),且 (R \ll \min(D, K))。
      • 只有 (A) 和 (B)是可训练的参数。
      • (\alpha) 是一个缩放因子,用于平衡预训练知识和新任务知识(讲座中提到代码实现时,有时会有一个额外的缩放 (s/r),但基本思想是(\alpha)控制新知识的比例)。
    • 推理: 训练完成后,可以将 (\alpha BA) 加到 (W_0) 上形成新的 (W_{new})进行推理,也可以在推理时动态计算 (W_0x + \alpha B(Ax))。讲座中的代码示例显示了后者,即不合并权重,在每次前向传播时额外计算LoRA部分的贡献。
    • 应用位置: 通常应用于Transformer模型中的自注意力 (Self-Attention) 模块的权重矩阵,特别是查询 (Query, Q) 和值 (Value, V) 矩阵
    • 超参数:
      • 秩 (Rank, R): 实验表明,即使是很小的R值(如R=8)也能取得良好性能。
      • 缩放因子 ((\alpha)): 通常设为1。可以根据任务与预训练模型的差异程度调整。
      • 应用LoRA的模块: 如前所述,Q和V矩阵是常见且有效的选择。
    • 性能:
      • 与许多其他PEFT方法相比,LoRA在多种任务和模型上表现出色。
      • 相较于全量微调,LoRA可以用极少的额外可训练参数(仅A和B)达到相当甚至更好的性能。
      • 有时,仅微调少量参数会产生正则化效应,有助于提升泛化性。
      • 存储开销小:只需为每个任务存储小的A、B矩阵,方便切换适配器。

实用训练流程总结 (根据讲座末尾流程图)

Speaker 1提供了一个决策流程图,用于指导在期末项目中如何选择高效训练策略:

  1. 始终使用混合精度训练 (Mixed Precision Training)
    • 如果GPU支持 (NVIDIA Ampere架构如A100, H100等),优先使用BFloat16,因为它通常不需要梯度缩放,实现更简单。可通过 torch.cuda.is_bf16_supported()检查。
  2. 检查单GPU上的Batch Size为1是否可行: (此流程主要针对拥有多GPU的情况进行扩展,单GPU则跳过ZeRO步骤)
    • 如果Batch Size 1能运行 (fits) 在单个GPU上:
      • 尝试增大Batch Size以提升训练效率。
      • 如果有多于一个GPU,并且/或者 (AND/OR) 使用ZeRO Stage 2。ZeRO Stage 2能在不增加额外通信开销的情况下节省显存。
    • 如果Batch Size 1在单个GPU上导致显存不足 (OOM - Out Of Memory) (且你拥有多GPU):
      • 尝试使用 ZeRO Stage 3 (FSDP),它会切分模型参数,可能解决OOM问题。
      • 如果ZeRO Stage 3仍然OOM,或遇到其他问题,可以考虑梯度检查点 (Gradient Checkpointing) / 激活检查点 (Activation Checkpointing) (讲座中提及但未详述)。
  3. 如果全量微调(即使应用了上述策略)仍然不可行:
    • 尝试 参数高效微调 (PEFT),例如 LoRA
    • LoRA推荐起始配置:
      • 将LoRA应用于Q (查询) 和 V (值) 矩阵。
      • 设置秩 Rank = 8
      • 设置缩放因子 (\alpha = 1)

注意: 上述流程中,ZeRO相关的策略(Stage 2, Stage 3/FSDP)明确适用于多GPU环境。对于单GPU且显存不足的情况,除了混合精度和可能的梯度检查点,PEFT (如LoRA) 是主要考虑方向。

结论

本讲座系统性地介绍了从单GPU的混合精度优化,到多GPU环境下的DDP、ZeRO/FSDP等高级分布式训练策略,再到资源极度受限时的参数高效微调技术LoRA。这些技术的核心目标都是在有限的计算和显存资源下,实现对大规模神经网络的高效训练和微调。通过理解这些技术的原理、优缺点及适用场景,研究者和开发者可以更有针对性地选择合适的工具来克服实践中遇到的挑战,从而推动更大、更复杂模型的发展和应用。讲座强调,尤其对于学生项目,合理运用这些高效训练方法至关重要。