导出设置

预览

Stanford CS224N: NLP w/ DL | Spring 2024 | Lecture 12 - Efficient Training, Shikhar Murty

类型: 音频媒体 上传时间: 2025-05-16 12:37 摘要时间: 2025-06-06 06:48

标题: Stanford CS224N: NLP与深度学习 | 2024春季 | 第12讲 - 高效训练
描述: 本讲座是斯坦福CS224N课程中一次纯粹的技术实践分享,旨在揭秘如何利用有限的GPU资源训练动辄数十亿参数的大型模型。内容涵盖从混合精度、分布式训练到参数高效微调(如LoRA)等一系列核心工程技术,为任何希望动手实践大模型的学习者提供了一份宝贵的“省钱省显存”实战指南。讲座开始时,讲师还通告了课程项目的相关安排。

概览/核心摘要 (Executive Summary)

本讲座 (Stanford CS224N Lecture 12, Spring 2024) 深入探讨了在GPU上高效训练大规模神经网络的多种关键技术,旨在帮助学生应对期末项目中可能遇到的计算资源和内存限制问题。内容不直接涉及自然语言处理理论,而是聚焦于机器学习系统的实践层面。

讲座首先介绍了混合精度训练 (Mixed Precision Training),通过使用FP16或BFloat16等低精度浮点数,在保证模型性能的同时,大幅降低显存占用并加速计算。重点讨论了如何通过梯度缩放 (Gradient Scaling) 或直接使用BFloat16来避免训练不稳定的问题。

其次,讲座详细拆解了多GPU训练 (Multi-GPU Training) 的策略。以“团队协作写书”为喻,从基础的分布式数据并行 (DDP)——即“每人拿一份完整的书稿副本,各自审阅后开会同步所有修改意见”——过渡到更高效的ZeRO技术。ZeRO通过分阶段切分优化器状态、梯度乃至模型参数,好比“书稿副本每人一份,但参考资料分章节保管”,最终演进到FSDP——“书稿本身也被拆分,每个人只负责自己的章节,需要时再临时借阅”,从而在多GPU环境下极致地利用总体显存资源。

最后,讲座介绍了参数高效微调 (Parameter-Efficient Fine-tuning, PEFT),并重点讲解了LoRA技术。这好比“给一个功能强大的瑞士军刀(预训练模型)增加一个微调旋钮(LoRA模块),而不是重新设计整个刀具”。通过冻结大部分参数,仅训练少量适配器,LoRA能以极低的资源开销实现优异的下游任务性能,并探讨了其在促进AI普惠化及降低环境影响等方面的积极意义。讲座以一个实用的决策流程图收尾,指导学习者如何根据实际情况组合运用这些高效训练技术。

数字表示与浮点数基础

讲座首先回顾了计算机中浮点数的表示方法,这是理解后续训练技术的基础。

  • FP32 (单精度浮点数):
    • 占用32位(4字节)内存,是标准的浮点数格式。
    • 拥有广阔的表示范围和高精度,像一把刻度精密的尺子。
  • FP16 (半精度浮点数):
    • 占用16位(2字节)内存,显存需求减半。
    • 代价是牺牲了范围和精度,像一把刻度更粗、长度更短的尺子。
      • 范围减小: 过小的数值会直接变成0,过大的数会变成无效值(NaN)。
      • 精度降低: 像 1.0001 这样的数可能会被四舍五入成 1.0
    • 这对训练很关键,因为许多梯度值非常小,在FP16下它们会直接“消失”(变为0),导致模型无法学习。

混合精度训练 (Mixed Precision Training)

混合精度训练的目标是两全其美:既享受FP16的低显存和高速度,又避免其数值不稳定的陷阱。

  • 问题背景:

    • 用FP32训练大模型,很容易遇到“显存不足 (Out of Memory)”的窘境。
    • 直接用FP16训练,大量梯度会因范围太小而归零,导致训练失败。讲座引用图表称:“超过一半的梯度在FP16下会直接变成0”。
  • 解决方案1:梯度缩放 (Gradient Scaling)
    这是一个巧妙的“放大镜”技巧:

    1. 正常进行前向计算,得到损失值 (Loss)。
    2. 将损失值乘以一个很大的缩放因子S (比如1000)。
    3. 基于被放大的损失计算梯度。这样,原本微小的梯度也被同等倍数放大,大到足以在FP16的表示范围内“存活”下来。
    4. 在更新权重前,再将放大的梯度除以缩放因子S,将其还原回真实大小。
    5. 为了保持更新的精度,通常会保留一份FP32格式的“主权重”,用还原后的梯度来更新它。
    6. PyTorch实现: 通过 GradScalerautocast 上下文管理器实现。
    7. 缺点: 需要小心翼翼地调整缩放因子S,像是在走钢丝,S太大或太小都可能出问题。
  • 解决方案2:BFloat16 (Brain Float 16)
    BFloat16是更现代、更简单的解决方案,它的设计哲学是:“看得远比看得清更重要”

    • 它和FP16一样只占用16位,但内部比特分配不同:它保留了和FP32一样多的“指数位”(决定表示范围),但牺牲了更多的“尾数位”(决定精度)。
    • 结果是,BFloat16拥有和FP32几乎相同的动态范围,能轻松表示非常小或非常大的数,从而基本上无需梯度缩放这个复杂步骤
    • 局限性: 需要较新的GPU硬件支持,如NVIDIA Ampere架构 (A100, H100等)。
    • 效果: 实验证明,使用BFloat16的混合精度训练,训练时间减少约三分之一,显存占用大大降低,而模型精度几乎不受影响,有时甚至因其轻微的噪声带来正则化效果而略有提升。

多GPU训练 (Multi-GPU Training)

当一个GPU不够用时,就需要让多个GPU协同工作。

  • 分布式数据并行 (DDP - Distributed Data Parallel)

    • 工作方式: 这是最基础的多GPU训练方法。可以想象成一个写作团队,为了加速审稿:
      1. 分发任务: 将一大本书(数据集)分成几份,每个审稿人(GPU)拿到一份。
      2. 独立工作: 每个审稿人(GPU)都有一份完整的书稿副本(模型副本),并根据自己负责的章节(数据分片)提出修改意见(计算梯度)。
      3. 汇总意见: 所有审稿人开会,把各自的修改意见汇总并平均一下,得出一个统一的修改方案(同步梯度)。这一步通过一个叫 AllReduce 的操作完成,可以通俗理解为:“大家把各自的计算结果汇总求平均,然后每个人都拿到这份最终的平均结果。”
    • 显存瓶颈: DDP的缺点是每个审稿人(GPU)都需要保存一份完整的书稿(模型)、一份完整的参考资料(优化器状态)和自己的修改意见(梯度),非常浪费存储空间(显存)。
  • Zero Redundancy Optimizer (ZeRO)
    ZeRO是一套旨在解决DDP显存浪费问题的先进技术,它通过“分工”来节省每个GPU的负担。

    • ZeRO Stage 1: 切分优化器状态

      • 工作方式: 相当于审稿时,书稿(模型)还是每人一份,但厚厚的参考资料(优化器状态)被拆分了,每个审稿人只保管其中一部分。
      • 通信流程: 审稿后,大家需要交换意见,确保负责更新每一页的人能拿到所有相关的修改建议。这涉及到 ReduceScatter(“大家汇总意见,但每个人只拿走自己负责那一小部分的最终结果”)和 AllGather(“每个人把自己更新好的那一小块拼图拿出来,拼成完整的新版书稿,并确保每人都有一份新版副本”)两个步骤。
      • 效果: 通信量和DDP几乎一样,但“免费节省了显存”,因为每个GPU不再需要保存完整的优化器状态。
    • ZeRO Stage 2: 切分梯度和优化器状态

      • 工作方式: 更进一步,现在连每个人的修改意见草稿(梯度)都不需要完整写下来了。每当审阅完一页,就立刻把修改意见告诉负责那一页的同事,然后就把自己的草稿扔掉。
      • 效果: 同样,通信量没有显著增加,但又节省了存储完整梯度的显存。这“实际上也是免费的”。
    • ZeRO Stage 3 / FSDP: 完全切分

      • 工作方式: 这是最极致的模式,适用于连书稿(模型)本身都大到一个人(单个GPU)的桌子放不下的情况。现在,连书稿本身都被拆开了,每个审稿人只持有书的几个章节(模型参数分片)。
      • 通信流程: 这种模式下,通信变得无处不在。
        • 前向计算: 当要审阅第4章时,需要先通过 AllGather 从同事那里借来完整的第4章内容,审完后再还回去,以腾出桌面空间。
        • 反向计算: 提出修改意见时,同样需要先借来完整的第4章,计算出修改意见后,再通过 ReduceScatter 将所有关于第4章的意见汇总给专门负责第4章的同事。
      • 代价: 这种模式虽然极大地节省了单个GPU的显存,但通信开销显著增加,不再是“免费午餐”。
  • 被忽略的显存占用:模型激活值 (Model Activations)

    • 在训练过程中,还需要临时存储大量的中间计算结果(激活值),以便在反向传播时计算梯度。这部分显存占用与批量大小 (Batch Size) 成正比。
    • ZeRO等技术主要优化的是模型、梯度和优化器的存储,并未直接解决激活值的显存压力。讲座提及,解决这一问题的常用技术是梯度检查点 (Gradient Checkpointing),它通过在前向传播时不保存所有激活值,而在反向传播时重新计算它们的方式,用更多的计算时间换取宝贵的显存空间。

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

当硬件资源极其有限,连全量微调(Full Fine-tuning)都跑不动时,PEFT就是救星。

  • 核心思想: 与其更新模型中全部数十亿个参数,不如只“微调”其中一小部分,四两拨千斤。

  • LoRA (Low-Rank Adaptation)

    • 工作方式: LoRA是PEFT中最流行的方法之一。它好比给一个功能强大的瑞士军刀(预训练模型)增加一个精密的“微调旋钮”(LoRA模块),而不是重新设计整个刀具。
    • 技术原理: LoRA基于一个观察:模型微调时的参数变化(更新矩阵)通常是“低秩”的,意味着其核心信息可以用更少的参数来表示。因此,LoRA冻结原始模型的全部参数,并在模型的特定层(如Transformer的Q和V矩阵)旁边增加两个小小的、可训练的低秩矩阵(A和B)。训练时,只更新这两个小矩阵。
    • 优势:
      • 极省资源: 可训练参数量可能只有全量微调的0.01%,大大降低了显存和计算需求。
      • 性能优异: 在许多任务上,LoRA能达到与全量微调相当甚至更好的性能,因为只微调少量参数有时能起到正则化作用,防止过拟合。
      • 部署灵活: 只需为每个下游任务存储一对小小的A、B矩阵,就可以轻松切换模型功能,无需保存多个庞大的完整模型。
    • 推荐配置:
      • 应用位置: 应用于Transformer的查询(Q)和值(V)矩阵
      • 秩 (Rank, R): 从一个很小的值开始,如 R=8,通常效果就很好。
      • 缩放因子 ((\alpha)): 通常设为1即可。

实用训练流程总结

讲座最后提供了一个清晰的决策流程图,指导如何在项目中选择高效训练策略:

  1. 第一步:始终使用混合精度训练

    • 如果GPU支持(如A100),优先使用BFloat16,因为它更简单高效。
  2. 第二步:检查单GPU能否容纳最小批量 (Batch Size=1)

    • 如果可以: 恭喜!你可以尝试增大批量,并(在多GPU环境下)直接使用 ZeRO Stage 2 来进一步节省显存,压榨GPU性能。
    • 如果不行 (OOM) (且你拥有多GPU):
      • 尝试使用 ZeRO Stage 3 (FSDP),它通过切分模型本身来解决OOM问题。
      • 如果还不行,可以考虑 梯度检查点 技术,用计算换显存。
  3. 第三步:如果全量微调之路走不通:

    • 果断转向 参数高效微调 (PEFT),首选 LoRA
    • LoRA推荐起始配置: 将LoRA应用于Q和V矩阵,设置Rank=8,Alpha=1。

注意: ZeRO相关策略主要适用于多GPU环境。对于单GPU且显存不足的情况,除了混合精度和梯度检查点,PEFT (如LoRA) 是最关键的解决方向。