[Distributed w/ TorchTitan] Semi synchronous training using TorchFT

username: H-Huang
cooked: <p>with <a href="https://github.com/h-huang" rel="noopener nofollow ugc">Howard Huang</a>, <a href="https://github.com/d4l3k" rel="noopener nofollow ugc">Tristan Rice</a>, <a href="https://github.com/fegin" rel="noopener nofollow ugc">Chien-Chin Huang</a></p>
<h2><a name="p-470352-summary-1" class="anchor" href="#p-470352-summary-1"></a>Summary</h2>
<p>We recently integrated TorchFT into torchtitan and have added support for semi synchronous training on TorchTitan through the LocalSGD and DiLoCo algorithms. We wrapped the entire training loop in TorchTitan with an optional context manager to control the training method. In this post, we talk about the API and some of the changes we made. We also provide a visual demo and talk about future steps.</p>
<h1><a name="p-470352-distributed-with-torchtitan-series-2" class="anchor" href="#p-470352-distributed-with-torchtitan-series-2"></a>Distributed with TorchTitan Series</h1>
<p>The GitHub repository <a href="https://github.com/pytorch/torchtitan" rel="noopener nofollow ugc">torchtitan</a> is a proof of concept for large-scale LLM training using native PyTorch, designed to be easy to understand, use, and extend for different training purposes, supporting multi-dimensional parallelisms with modular components. In this series of topics, we introduce the latest PyTorch features for distributed training enabled in Torchtitan.</p>
<ul>
<li>Topic 1: <a href="https://discuss.pytorch.org/t/distributed-w-torchtitan-enabling-float8-all-gather-in-fsdp2/209323">Enabling Float8 All-Gather in FSDP2</a></li>
<li>Topic 2: <a href="https://discuss.pytorch.org/t/distributed-w-torchtitan-introducing-async-tensor-parallelism-in-pytorch/209487">Introducing Async Tensor Parallelism in PyTorch</a></li>
<li>Topic 3: <a href="https://discuss.pytorch.org/t/distributed-w-torchtitan-optimizing-checkpointing-efficiency-with-pytorch-dcp/211250">Optimizing Checkpointing Efficiency with PyTorch DCP</a></li>
<li>Topic 4: <a href="https://discuss.pytorch.org/t/distributed-w-torchtitan-training-with-zero-bubble-pipeline-parallelism/214420">Training with Zero-Bubble Pipeline Parallelism</a></li>
<li>Topic 5: <a href="https://discuss.pytorch.org/t/distributed-w-torchtitan-breaking-barriers-training-long-context-llms-with-1m-sequence-length-in-pytorch-using-context-parallel/215082">Breaking Barriers: Training Long Context LLMs with 1M Sequence Length in PyTorch Using Context Parallel</a></li>
<li>→ <strong>Topic 6:</strong> Semi synchronous training in combination using TorchFT</li>
</ul>
<h2><a name="p-470352-background-3" class="anchor" href="#p-470352-background-3"></a>Background</h2>
<p><a href="https://github.com/pytorch/torchft" rel="noopener nofollow ugc">TorchFT</a> is a project aimed at providing per-step fault tolerance for PyTorch training jobs. It implements techniques that allow training to continue in the event of errors, without interrupting the entire process. TorchFT supports a range of distributed algorithms including DDP, HSDP, along with reusable components like coordination primitives and fault-tolerant ProcessGroup implementations.</p>
<p>Two popular semi-synchronous training algorithms are the LocalSGD and DiLoCo. These algorithms are termed “semi-synchronous” because they blend aspects of both asynchronous and synchronous training. In an asynchronous manner, workers compute gradients and update their parameters independently, without waiting for other workers. However, they also incorporate periodic synchronization, similar to fully synchronous methods, to ensure identical model parameters across all workers. The synchronization method differs for each algorithm:</p>
<p><strong>LocalSGD</strong>: Synchronization is achieved by averaging each worker’s parameters.<br>
<strong>DiLoCo</strong>: Synchronization involves averaging pseudogradients, which are the differences between the current model’s parameters and those from the last global update.</p>
<p>Given synchronization every N steps, when performing T training steps, each with C communication time, this would cut the total communication from T<em>C to T</em>C/N compared to fully synchronous methods. Because N is a hyperparameter, the staleness of the weights upon synchronization may affect convergence, so total training time is not calculated so cleanly and is an open area of research.</p>
<h2><a name="p-470352-localsgd-diloco-api-4" class="anchor" href="#p-470352-localsgd-diloco-api-4"></a>LocalSGD / DiLoCo API</h2>
<p>We implemented LocalSGD and DiLoCo as <a href="https://github.com/pytorch/torchft/blob/f93d5e1f27694244b3efefb1cf1d0cf590c87995/torchft/local_sgd.py#L41" rel="noopener nofollow ugc">context managers</a> in TorchFT. In doing so, we don’t require any model changes or wrapping of the model which would affect the parameter FQNs, however we do need to make changes to the training loop in order to use these algorithms.</p>
<pre data-code-wrap="python"><code class="lang-python">with semi_sync_training(
    manager=ft_manager,
    model=model
    optimizer=optimizer,
    sync_every=40
):
    # training loop...
</code></pre>
<p>The context managers require the model’s optimizer that is used in training because it attaches optimizer hooks and fires whenever opt.step() is called to record the steps taken so far. Under the hood at every step, we also perform quorum by querying the lighthouse server.</p>
<p><div class="lightbox-wrapper"><a class="lightbox" href="https://discuss.pytorch.org/uploads/default/original/3X/4/9/4969926f2372ef2b246157f95363c3d594604320.png" data-download-href="https://discuss.pytorch.org/uploads/default/4969926f2372ef2b246157f95363c3d594604320" title="torchft"><img src="https://discuss.pytorch.org/uploads/default/optimized/3X/4/9/4969926f2372ef2b246157f95363c3d594604320_2_657x500.png" alt="torchft" data-base62-sha1="atr2CHP6OoQH8KKBSHDCkjJ8wec" width="657" height="500" srcset="https://discuss.pytorch.org/uploads/default/optimized/3X/4/9/4969926f2372ef2b246157f95363c3d594604320_2_657x500.png, https://discuss.pytorch.org/uploads/default/optimized/3X/4/9/4969926f2372ef2b246157f95363c3d594604320_2_985x750.png 1.5x, https://discuss.pytorch.org/uploads/default/optimized/3X/4/9/4969926f2372ef2b246157f95363c3d594604320_2_1314x1000.png 2x" data-dominant-color="F7F7F7"><div class="meta"><svg class="fa d-icon d-icon-far-image svg-icon" aria-hidden="true"><use href="#far-image"></use></svg><span class="filename">torchft</span><span class="informations">2083×1583 222 KB</span><svg class="fa d-icon d-icon-discourse-expand svg-icon" aria-hidden="true"><use href="#discourse-expand"></use></svg></div></a></div></p>
<p>When integrating with TorchTitan we wrapped the entire training loop with these context managers. To handle the failure case, these algorithms also need to load the correct weights when recovering. For LocalSGD we can simply recover the weights from another currently training replica group. For DiLoCo, we need to start the model weights from the point of the last global model weights.</p>
<p>If TorchTitan uses FSDP or TP as its parallelism strategy, then it converts the model parameters into DTensors. We also needed to account for this parameter change because if it is a DTensor we only want to exchange and synchronize the local shards of that tensor.</p>
<h2><a name="p-470352-results-5" class="anchor" href="#p-470352-results-5"></a>Results</h2>
<p>Here is a screen recording of a simple UI built to demonstrate using torchtitan on 2 replica groups. We also have a separate process running the lighthouse server. The demo progresses as follows:</p>
<ol>
<li>Start lighthouse server</li>
<li>Start replica group 1, allow it to train for a bit</li>
<li>Start replica group 2, retrieves the state dict from group 1 and starts training</li>
<li>Kill replica group 2</li>
<li>Replica group 1 recovers and continues training</li>
</ol>
<p><img src="https://lh7-rt.googleusercontent.com/docsz/AD_4nXeskS-bA7OWpcneIrIzVZegRMKgt2LNWyv0SLl5vBpj7mBz7uZHVHSHZCdINzBL-eBPIe4yDL8-pF0-W_h33saNlGXidXQS3J19xnsc7aTdoXXmaaMrc_4s_vG-yjRbHOKrOu0m?key=VOeBVs-2p2OTHdlmSOLNx2Yg" alt="" width="690" height="388" role="presentation"></p>
<p>We also ran this for multiple hosts on our internal servers for LLama3 8B. We see similar MFU across the runs. With DiLoCo, we see slightly higher memory usage (+4%) which is expected because DiLoCo needs to keep a copy of its local shard of model parameters from the previous global update.</p>
<h2><a name="p-470352-next-steps-6" class="anchor" href="#p-470352-next-steps-6"></a>Next steps</h2>
<p>There are some clean up improvements that we could do for the torchft integration with torchtitan. We also can support and better test other process group variants in torchft. We can polish and land the UI as a beginner-friendly way to run torchtitan. There are improvements to the DiLoCo algorithm that can be added, for example supporting <a href="https://arxiv.org/pdf/2501.18512" rel="noopener nofollow ugc">streaming DiLoCo</a>.</p>