<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>并行训练 on Ganko Space</title><link>https://ganko.asia/tags/%E5%B9%B6%E8%A1%8C%E8%AE%AD%E7%BB%83/</link><description>Recent content in 并行训练 on Ganko Space</description><generator>Hugo</generator><language>zh-cn</language><lastBuildDate>Mon, 27 Apr 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://ganko.asia/tags/%E5%B9%B6%E8%A1%8C%E8%AE%AD%E7%BB%83/index.xml" rel="self" type="application/rss+xml"/><item><title>多卡并行训练和微调技术</title><link>https://ganko.asia/posts/%E5%A4%9A%E5%8D%A1%E5%B9%B6%E8%A1%8C%E8%AE%AD%E7%BB%83%E5%92%8C%E5%BE%AE%E8%B0%83%E6%8A%80%E6%9C%AF/</link><pubDate>Mon, 27 Apr 2026 00:00:00 +0000</pubDate><guid>https://ganko.asia/posts/%E5%A4%9A%E5%8D%A1%E5%B9%B6%E8%A1%8C%E8%AE%AD%E7%BB%83%E5%92%8C%E5%BE%AE%E8%B0%83%E6%8A%80%E6%9C%AF/</guid><description>&lt;blockquote>
&lt;p>这是使用 Google DeepResearch 生成的偏理论分析报告，内容基于2026/4/27之前的公开资料和技术文献，主要是方便自己学习，准确性和时效性可能无法完全保证。&lt;/p>&lt;/blockquote>
&lt;hr>
&lt;h2 id="第一章-深度学习分布式训练的底层基石显存与通信的博弈">第一章 深度学习分布式训练的底层基石：显存与通信的博弈&lt;/h2>
&lt;p>随着大语言模型（LLM）参数规模从十亿级别飙升至万亿级别，深度学习的训练与微调已经彻底演变为一项复杂的系统工程。在这一演进过程中，单张图形处理器（GPU）的显存容量与计算能力已无法满足动辄数千吉字节（GB）的内存需求，多卡并行架构成为必然选择。&lt;/p>
&lt;p>在多卡分布式训练中，架构设计的核心挑战始终围绕着**“显存墙（Memory Wall）”&lt;strong>与&lt;/strong>“通信墙（Communication Wall）”**的博弈展开。&lt;/p>
&lt;h3 id="11-显存消耗模型解构">[1].1 显存消耗模型解构&lt;/h3>
&lt;p>要深刻理解分布式训练的演进，必须首先解构模型在训练过程中的显存消耗。在采用混合精度训练（Mixed Precision Training）及 Adam 优化器的情况下，每个模型参数通常需要消耗约 &lt;strong>20 字节&lt;/strong>的显存：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>模型状态（Model States）：&lt;/strong>
&lt;ul>
&lt;li>&lt;strong>权重与梯度：&lt;/strong> 半精度（16-bit）各占用 [2] 字节（共 [4] 字节）。&lt;/li>
&lt;li>&lt;strong>权重与梯度副本：&lt;/strong> 全精度（32-bit）各占用 [4] 字节（共 [8] 字节）。&lt;/li>
&lt;li>&lt;strong>优化器状态：&lt;/strong> Adam 优化器的一阶与二阶矩估计状态各占用 [4] 字节（共 [8] 字节）。&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;strong>剩余消耗：&lt;/strong> 前向传播过程中产生的**残差状态（Residual States，即激活值 Activations）**和各类临时缓冲区，会随着批次大小（Batch Size）和序列长度呈线性甚至二次方增长。&lt;/li>
&lt;/ul>
&lt;blockquote>
&lt;p>&lt;strong>示例：&lt;/strong> 一个 [75] 亿参数的模型，仅静态模型状态就需要消耗约 150GB 显存，远超单张 H100（80GB）的物理极限。&lt;/p>&lt;/blockquote>
&lt;h3 id="12-通信原语与同步机制">[1].2 通信原语与同步机制&lt;/h3>
&lt;p>在多 GPU 协同计算时，节点间必须通过特定的**集体通信原语（Collective Communications）**进行数据交互：&lt;/p>
&lt;ol>
&lt;li>&lt;strong>基础原语：&lt;/strong> 包括广播（Broadcast）、收集（Gather）、分散（Scatter）以及规约（Reduce）。&lt;/li>
&lt;li>&lt;strong>核心同步机制：&lt;/strong> &lt;strong>全规约（All-Reduce）&lt;/strong>。它确保了所有 GPU 在反向传播后能够获得一致的梯度总和。&lt;/li>
&lt;/ol>
&lt;p>从底层实现来看，最优的 All-Reduce 操作通常被分解为两个连续步骤：&lt;strong>Reduce-Scatter（规约-分散）&lt;/strong> 与 &lt;strong>All-Gather（全收集）&lt;/strong>。理解这种分解机制是掌握高级状态切分策略（如 ZeRO 和 FSDP）的技术基石。&lt;/p></description></item><item><title>CS336 Lecture 7-8：Parallelism</title><link>https://ganko.asia/posts/cs336-lecture-7-8-parallelism/</link><pubDate>Thu, 09 Apr 2026 00:00:00 +0000</pubDate><guid>https://ganko.asia/posts/cs336-lecture-7-8-parallelism/</guid><description>&lt;h2 id="本篇主题">本篇主题&lt;/h2>
&lt;p>这两节Lecture都是关于并行训练的，主要内容包括：&lt;/p>
&lt;ul>
&lt;li>理解训练超大模型时系统层面的复杂性&lt;/li>
&lt;li>掌握不同的 parallelization paradigms，以及为什么人们通常会同时使用多种并行方式&lt;/li>
&lt;li>了解大规模训练任务通常是如何进行的&lt;/li>
&lt;/ul>
&lt;h2 id="lecture-7---parallelism-1">Lecture 7 - Parallelism 1&lt;/h2>
&lt;p>单块GPU无法满足SCALING的需求，必须使用多块GPU进行训练,所以我们需要Multi-GPU、Multi-Machine的并行训练方法，如下图所示：&lt;/p>
&lt;p align="center">
&lt;img src="https://ganko.asia/images/并行.jpg" alt="Parallelism 架构" />
&lt;/p>
&lt;p>首先讲了一些Basic Concepts：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>All Reduce&lt;/strong>：通信操作，所有参与的进程都将输入数据进行规约（如求和、最大值等）后，将结果分发给所有进程。常用于分布式训练中的梯度同步。&lt;/li>
&lt;li>&lt;strong>Broadcast&lt;/strong>：通信操作，数据从一个进程发送到所有其他进程。常用于分布式训练中的分发模型参数或者初始化数据。&lt;/li>
&lt;li>&lt;strong>All Gather&lt;/strong>：通信操作，所有参与的进程将各自的数据发送给所有其他进程，最终每个进程都获得所有数据的集合。常用于分布式训练中的收集模型输出或者中间结果。&lt;strong>和All Reduce的区别在于，All Reduce会对数据进行规约操作（如求和），而All Gather只是简单地收集数据，不进行任何计算。&lt;/strong>&lt;/li>
&lt;li>&lt;strong>Reduce Scatter&lt;/strong>：通信操作，所有参与的进程将各自的数据发送给所有其他进程，并对数据进行规约操作（如求和），最终每个进程都获得规约后的结果的一部分。常用于分布式训练中的分布式梯度更新。&lt;/li>
&lt;/ul>
&lt;p align="center">
&lt;img src="https://ganko.asia/images/basic.jpg" alt="Basic Concepts" />
&lt;/p>
&lt;p>了解了初始知识之后，可以正式进入核心部分，不同的并行方式：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Data Parallelism&lt;/strong>：最早的并行方式，模型复制到每个GPU上，每个GPU处理不同的数据batch，计算梯度后进行同步更新。优点是实现简单；缺点是通信开销大，尤其是模型参数较大时。
假设计算一个 SGD：我们会把 B 大小下的 batch 分给 M 个不同的机器，然后交换梯度去同步计算。这种情况对于 Compute scaling，每个 GPU 计算 B/M 个数据（不错！）；对于 Communication overhead，每个 batch 需要转移两次梯度（发送和接受）；对于 Memory scaling，完全没有，每个 GPU 都需要复制一次模型参数。
早期的data parallelism问题是只缓解了计算压力，内存压力没有缓解，需要每个GPU都复制一份模型参数，通信压力也很大。
Zero[HTTPS://arxiv.org/pdf/1910.02054]可以解决这个问题，核心的思想是将模型的state（参数和优化器状态）分布在不同的GPU上，每个GPU只存储模型的一部分，这样可以显著减少每个GPU的内存占用，同时通过通信操作来同步更新模型参数。
ZeRO分三个阶段：&lt;/li>
&lt;/ul>
&lt;ol>
&lt;li>&lt;strong>ZeRO-1&lt;/strong>：主要聚焦于 optimizer state sharding。把 optimizer state（first + second moments）分给每个 GPU，每个 GPU 都有 parameters + gradients，负责更新一部分 params。
每个 GPU 根据分配到的 batch 子集计算完整的梯度，此时只拥有局部梯度
利用 Reduce-Scatter 将所有 GPU 的局部梯度汇总并分散到每个设备上，现在每个 GPU 只持有全局梯度的一部分
每个 GPU 利用局部梯度和 optimizer state 更新该部分参数
利用 AllGather 将所有 GPU 的部分参数收集并分发给所有设备，确保每个 GPU 拥有完整的参数。
这种方法的通信开销并没有增加，而且 memory 减少了接近四倍（优化器状态是fp32，参数是fp16）。&lt;/li>
&lt;/ol>
&lt;p align="center">
&lt;img src="https://ganko.asia/images/zero-1.jpg" alt="ZeRO-1" />
&lt;/p></description></item><item><title>并行训练实践</title><link>https://ganko.asia/posts/%E5%B9%B6%E8%A1%8C%E8%AE%AD%E7%BB%83%E5%AE%9E%E8%B7%B5/</link><pubDate>Thu, 24 Jul 2025 00:00:00 +0000</pubDate><guid>https://ganko.asia/posts/%E5%B9%B6%E8%A1%8C%E8%AE%AD%E7%BB%83%E5%AE%9E%E8%B7%B5/</guid><description>&lt;h2 id="accelerate">Accelerate&lt;/h2>
&lt;p>Accelerate 是 Hugging Face 开发的深度学习多卡并行训练库，构建在 &lt;code>torch_xla&lt;/code> 和 &lt;code>torch.distributed&lt;/code> 之上。它提供了一个简单的 API，将与多 GPU、TPU、混合精度训练相关的样板代码抽离了出来，让用户可以在不同设备上轻松地进行分布式训练和混合精度训练，而无需重复编写大量样板代码。&lt;/p>
&lt;p>另外，Accelerate 还提供了很多性能优化的功能，使得大规模训练和推理变得简单、高效且适应性强。&lt;/p>
&lt;p>在学习如何使用 Accelerate 之前，首先回顾一下并行训练的其他方法。&lt;/p>
&lt;hr>
&lt;h2 id="dataparallel-dp">DataParallel (DP)&lt;/h2>
&lt;p>DP 是最常见的并行训练方式，通过将数据拆分（split）到各个 workers 中（每个 worker 拥有完整模型）进行并行计算，以解决 Batch Size 过大的问题。因为求导以及加和都是线性的，所以数据并行在数学上是等价的。&lt;/p>
&lt;p>&lt;code>DataParallel&lt;/code> 是 PyTorch 中最容易实现的并行方案，只需要增加一行代码：&lt;code>model = nn.DataParallel(model)&lt;/code>。&lt;/p>
&lt;h3 id="代码示例">代码示例&lt;/h3>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-python" data-lang="python">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">import&lt;/span> torch
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">import&lt;/span> torch.nn &lt;span style="color:#66d9ef">as&lt;/span> nn
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">from&lt;/span> torch.utils.data &lt;span style="color:#f92672">import&lt;/span> DataLoader, Dataset
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># 数据集的长度为 100，batch size 为 32&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># 模型是一个简单的 fc 层，输入长度是 5，输出是 2&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>input_size, output_size &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">5&lt;/span>, &lt;span style="color:#ae81ff">2&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>batch_size, data_size &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">32&lt;/span>, &lt;span style="color:#ae81ff">100&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>model &lt;span style="color:#f92672">=&lt;/span> Model(input_size, output_size)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">if&lt;/span> torch&lt;span style="color:#f92672">.&lt;/span>cuda&lt;span style="color:#f92672">.&lt;/span>device_count() &lt;span style="color:#f92672">&amp;gt;&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> print(&lt;span style="color:#e6db74">f&lt;/span>&lt;span style="color:#e6db74">&amp;#34;Detected &lt;/span>&lt;span style="color:#e6db74">{&lt;/span>torch&lt;span style="color:#f92672">.&lt;/span>cuda&lt;span style="color:#f92672">.&lt;/span>device_count()&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74"> GPUs!&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> model &lt;span style="color:#f92672">=&lt;/span> nn&lt;span style="color:#f92672">.&lt;/span>DataParallel(model)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>device &lt;span style="color:#f92672">=&lt;/span> torch&lt;span style="color:#f92672">.&lt;/span>device(&lt;span style="color:#e6db74">&amp;#34;cuda:0&amp;#34;&lt;/span> &lt;span style="color:#66d9ef">if&lt;/span> torch&lt;span style="color:#f92672">.&lt;/span>cuda&lt;span style="color:#f92672">.&lt;/span>is_available() &lt;span style="color:#66d9ef">else&lt;/span> &lt;span style="color:#e6db74">&amp;#34;cpu&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>model&lt;span style="color:#f92672">.&lt;/span>to(device)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>rand_loader &lt;span style="color:#f92672">=&lt;/span> DataLoader(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> dataset&lt;span style="color:#f92672">=&lt;/span>RandomDataset(input_size, data_size),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> batch_size&lt;span style="color:#f92672">=&lt;/span>batch_size,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> shuffle&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#66d9ef">True&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">for&lt;/span> data &lt;span style="color:#f92672">in&lt;/span> rand_loader:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> input &lt;span style="color:#f92672">=&lt;/span> data&lt;span style="color:#f92672">.&lt;/span>to(device)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> output &lt;span style="color:#f92672">=&lt;/span> model(input)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>在上述代码中，&lt;code>batch_size=32&lt;/code>。由于使用了 &lt;code>DataParallel&lt;/code>，在有 2 个 GPU 时，一个 batch 被划分成了 2 份，即 &lt;code>tensor.split(16)&lt;/code>，分别送往两个 GPU。&lt;/p></description></item></channel></rss>