<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: pytorch</title>
    <description>The latest articles tagged 'pytorch' on DEV Community.</description>
    <link>https://dev.to/t/pytorch</link>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/tag/pytorch"/>
    <language>en</language>
    <item>
      <title>PyTorch vs. TensorFlow for Diffusion Models: A Dev Leader's Guide to Tooling and Productivity</title>
      <dc:creator>Oleg</dc:creator>
      <pubDate>Wed, 14 Oct 2026 13:00:29 +0000</pubDate>
      <link>https://dev.to/devactivity/pytorch-vs-tensorflow-for-diffusion-models-a-dev-leaders-guide-to-tooling-and-productivity-31mp</link>
      <guid>https://dev.to/devactivity/pytorch-vs-tensorflow-for-diffusion-models-a-dev-leaders-guide-to-tooling-and-productivity-31mp</guid>
      <description>&lt;h2&gt;
  
  
  The Great Debate: PyTorch vs. TensorFlow for Diffusion Models
&lt;/h2&gt;

&lt;p&gt;In the rapidly evolving landscape of Artificial Intelligence, choosing the right tools is paramount for any development team. A recent GitHub Community discussion (Discussion #208944) brought to light a critical decision point for AI developers: selecting between PyTorch and TensorFlow for diffusion models. Making an informed choice here can significantly impact project velocity and output quality, factors often highlighted by effective &lt;strong&gt;&lt;a href="https://dev.to/posts/apps-tools/unraveling-github-pages-dns-failures-dnsruby-timeouts-and-development-measurement-tools/"&gt;development measurement tools&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The consensus from the community is clear: for diffusion models, &lt;strong&gt;PyTorch is emphatically recommended&lt;/strong&gt;. Here’s why:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdrive.google.com%2Fthumbnail%3Fid%3D1KAX---kVQjffESlS1HIsWwR0wfhM6ikL%26sz%3Dw751" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdrive.google.com%2Fthumbnail%3Fid%3D1KAX---kVQjffESlS1HIsWwR0wfhM6ikL%26sz%3Dw751" alt="Diagram illustrating the PyTorch ecosystem for diffusion models, including Hugging Face diffusers and optimization tools." width="751" height="429"&gt;&lt;/a&gt;Diagram illustrating the PyTorch ecosystem for diffusion models, including Hugging Face diffusers and optimization tools.### Ecosystem &amp;amp; Library Dominance&lt;/p&gt;

&lt;p&gt;The industry standard for diffusion models, Hugging Face &lt;code&gt;diffusers&lt;/code&gt;, is built natively on PyTorch. This means that pretrained weights, pipeline schedulers (like Euler, DPM-Solver, UniPC), and fine-tuning scripts (LoRA, DreamBooth, ControlNet) are primarily optimized for PyTorch. This deep integration simplifies development and accelerates time-to-market for new features. Furthermore, PyTorch 2.x's native SDPA (Scaled Dot-Product Attention) and &lt;code&gt;torch.compile&lt;/code&gt; offer significant speedups (2x–3x) without external dependencies, directly contributing to a more efficient development lifecycle.&lt;/p&gt;

&lt;h3&gt;
  
  
  Dynamic Computation Graphs for Agile Development
&lt;/h3&gt;

&lt;p&gt;Diffusion sampling involves iterative denoising loops with dynamic timestep embeddings and conditioning tensors. PyTorch’s imperative design and dynamic computation graphs simplify the implementation and debugging of these multi-step reverse processes. This offers a more straightforward, intuitive experience compared to the often more rigid graph compilation workflows in TensorFlow, fostering agility and reducing debugging overhead for your engineering teams.&lt;/p&gt;

&lt;h3&gt;
  
  
  Research &amp;amp; Community Leadership
&lt;/h3&gt;

&lt;p&gt;Over 90% of generative AI papers, checkpoints, and open weights released on platforms like arXiv and Hugging Face prioritize PyTorch implementations. This makes PyTorch the go-to framework for staying current with cutting-edge research and leveraging the latest advancements, ensuring your team remains at the forefront of AI innovation.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdrive.google.com%2Fthumbnail%3Fid%3D1mI7l-tE7uRt8U_nv2SmcMknYG0YYWv6L%26sz%3Dw751" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdrive.google.com%2Fthumbnail%3Fid%3D1mI7l-tE7uRt8U_nv2SmcMknYG0YYWv6L%26sz%3Dw751" alt="Visual representation of a classical state-space search problem, showing states, actions, and a goal path." width="751" height="429"&gt;&lt;/a&gt;Visual representation of a classical state-space search problem, showing states, actions, and a goal path.While TensorFlow remains a viable option if your team has existing TensorFlow serving/training infrastructure, it is not the path with the broadest examples or the most current ecosystem support for diffusion models. For new diffusion projects, starting with &lt;code&gt;torch&lt;/code&gt; + &lt;code&gt;diffusers&lt;/code&gt; + &lt;code&gt;accelerate&lt;/code&gt; for multi-GPU training and inference is the recommended approach.&lt;/p&gt;

&lt;h2&gt;
  
  
  Beyond Frameworks: Understanding State-Space Concepts in AI
&lt;/h2&gt;

&lt;p&gt;The discussion also touched upon "state-space searches," a term that can have different meanings in classical AI versus modern machine learning. Clarifying this distinction is crucial for technical leaders guiding their teams.&lt;/p&gt;

&lt;h3&gt;
  
  
  Classical State-Space Search: The Foundation of Problem Solving
&lt;/h3&gt;

&lt;p&gt;A &lt;strong&gt;State-Space Search&lt;/strong&gt; is a foundational AI problem-solving paradigm where a problem is formulated as a set of discrete states, transitions (operators), and a target objective. Imagine a maze: each square is a state, moving between squares is an action, and reaching the exit is the goal.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;State Space:&lt;/strong&gt; The graph or tree of all possible configurations the system can be in.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Initial State:&lt;/strong&gt; Where the search begins.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Actions/Transitions:&lt;/strong&gt; Valid moves taking state S to successor state S'.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Goal Test:&lt;/strong&gt; A function verifying if the current state satisfies the objective.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Path Cost:&lt;/strong&gt; The cumulative cost incurred to reach a given state.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Common search strategies include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Uninformed (Blind) Search:&lt;/strong&gt;&lt;strong&gt;Breadth-First Search (BFS):&lt;/strong&gt; Explores level-by-level; optimal if all step costs are equal.&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Depth-First Search (DFS):&lt;/strong&gt; Explores deep paths first; memory efficient but not guaranteed optimal.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Informed (Heuristic) Search:&lt;/strong&gt;&lt;strong&gt;A* Search:&lt;/strong&gt; Uses an evaluation function &lt;em&gt;f(n) = g(n) + h(n)&lt;/em&gt;, where &lt;em&gt;g(n)&lt;/em&gt; is the actual cost so far and &lt;em&gt;h(n)&lt;/em&gt; is an admissible heuristic estimate to the goal. A* is both complete and optimal.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdrive.google.com%2Fthumbnail%3Fid%3D1mWmFMbLI-dcH8kdgLq5pfLy_sur5vzMT%26sz%3Dw751" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdrive.google.com%2Fthumbnail%3Fid%3D1mWmFMbLI-dcH8kdgLq5pfLy_sur5vzMT%26sz%3Dw751" alt="Illustration comparing the linear processing of State-Space Models (Mamba) with the quadratic complexity of traditional Transformer attention." width="751" height="429"&gt;&lt;/a&gt;Illustration comparing the linear processing of State-Space Models (Mamba) with the quadratic complexity of traditional Transformer attention.### Modern State-Space Models (SSMs): A New Paradigm for Sequence Processing&lt;/p&gt;

&lt;p&gt;In modern generative AI and sequence modeling, the term "State Space Models" (SSMs) refers to architectures like &lt;strong&gt;Mamba&lt;/strong&gt; and &lt;strong&gt;S4&lt;/strong&gt;. These models are distinct from classical search algorithms. They model continuous sequence state transitions in &lt;em&gt;O(L)&lt;/em&gt; linear time, offering significant computational advantages over Transformer-based models with their &lt;em&gt;O(L2)&lt;/em&gt; attention mechanisms, especially for very long sequences. Understanding this distinction is vital for teams exploring cutting-edge sequence processing techniques.&lt;/p&gt;

&lt;h2&gt;
  
  
  Strategic Impact: Tooling Choices, Productivity, and Delivery
&lt;/h2&gt;

&lt;p&gt;For dev team members, product/project managers, delivery managers, and CTOs, these technical decisions directly influence your team's productivity and project delivery metrics. Leveraging the right tools can drastically improve outcomes, making it easier to track progress with robust &lt;strong&gt;development measurement tools&lt;/strong&gt; and achieve your targets on a &lt;strong&gt;&lt;a href="https://dev.to/posts/productivity-tips/beyond-the-limit-mastering-github-codespaces-for-uninterrupted-productivity/"&gt;performance KPI dashboard&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;An optimal framework choice, like PyTorch for diffusion models, translates into faster iteration cycles, reduced debugging time, and quicker adoption of new research. This directly impacts your team's efficiency and the quality of your output. Furthermore, streamlined workflows, enabled by optimal framework choices, positively impact aspects like &lt;strong&gt;&lt;a href="https://dev.to/posts/apps-tools/reclaiming-commit-control-why-dev-teams-need-an-opt-out-for-copilot-s-co-authored-by-trailer/"&gt;pull request analytics for GitHub&lt;/a&gt;&lt;/strong&gt;, offering clearer insights into team efficiency and code velocity. By making informed decisions based on community insights and technical merits, leaders can empower their teams to build more effectively and deliver innovative AI solutions with greater confidence and speed.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>machinelearning</category>
      <category>pytorch</category>
      <category>tensorflow</category>
    </item>
    <item>
      <title>Top 5 Dev Tools &amp; Tutorials of the Week (October 14, 2026)</title>
      <dc:creator>karleeov</dc:creator>
      <pubDate>Wed, 14 Oct 2026 00:47:13 +0000</pubDate>
      <link>https://dev.to/karleeov/top-5-dev-tools-tutorials-of-the-week-october-14-2026-e4c</link>
      <guid>https://dev.to/karleeov/top-5-dev-tools-tutorials-of-the-week-october-14-2026-e4c</guid>
      <description>&lt;h3&gt;
  
  
  1. TensorFlow Quantum 3.0
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;What it is&lt;/strong&gt;: TensorFlow Quantum 3.0 is a framework designed for building and experimenting with hybrid quantum-classical machine learning models.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why now&lt;/strong&gt;: Released on October 10, 2026, this version brings enhanced support for hybrid quantum-classical models and improved performance for large-scale quantum simulations.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tutorial angle&lt;/strong&gt;: Learn how to set up and train a hybrid quantum-classical model using TensorFlow Quantum 3.0.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Difficulty&lt;/strong&gt;: Intermediate&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Start today&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;tensorflow-quantum&lt;span class="o"&gt;==&lt;/span&gt;3.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  2. PyTorch Lightning CLI 2.0
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;What it is&lt;/strong&gt;: The PyTorch Lightning CLI 2.0 is a command-line interface for managing and scaling PyTorch models with ease.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why now&lt;/strong&gt;: Launched on October 12, 2026, it features a more intuitive interface, multi-GPU training support, and enhanced logging capabilities.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tutorial angle&lt;/strong&gt;: Discover how to streamline multi-GPU training and logging using the new CLI features.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Difficulty&lt;/strong&gt;: Intermediate&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Start today&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;pytorch-lightning&lt;span class="o"&gt;==&lt;/span&gt;2.0
lightning &lt;span class="nt"&gt;--help&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  3. OpenAI Codex SDK for JavaScript
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;What it is&lt;/strong&gt;: The OpenAI Codex SDK for JavaScript allows developers to integrate Codex's code generation capabilities into their applications.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why now&lt;/strong&gt;: Released on October 8, 2026, this SDK enables the automation of code generation and the creation of intelligent coding assistants.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tutorial angle&lt;/strong&gt;: Learn how to set up and use the Codex SDK to generate code snippets within a JavaScript application.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Difficulty&lt;/strong&gt;: Beginner&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Start today&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install &lt;/span&gt;openai-codex
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  4. Django AI Framework 1.0
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;What it is&lt;/strong&gt;: Django AI Framework 1.0 integrates AI capabilities into the Django web framework, allowing developers to add machine learning features with minimal configuration.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why now&lt;/strong&gt;: Released on October 9, 2026, this framework makes it easier to incorporate AI-driven features like personalized recommendations into Django applications.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tutorial angle&lt;/strong&gt;: Explore how to add AI-driven features to your Django web application using the Django AI Framework.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Difficulty&lt;/strong&gt;: Intermediate&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Start today&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;django-ai-framework&lt;span class="o"&gt;==&lt;/span&gt;1.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  5. LangChain SDK for Python
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;What it is&lt;/strong&gt;: The LangChain SDK for Python is a set of tools for building language model-driven applications, offering support for chain-of-thought reasoning and multi-hop questions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why now&lt;/strong&gt;: Released on October 13, 2026, it enhances the capabilities of language model applications by supporting more complex reasoning and query processes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tutorial angle&lt;/strong&gt;: Build a language model application using chain-of-thought reasoning with the LangChain SDK.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Difficulty&lt;/strong&gt;: Intermediate&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Start today&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;&lt;span class="nv"&gt;langchain&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;1.0.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Building in public: my lead-gen engine
&lt;/h2&gt;

&lt;p&gt;This report was generated using a custom lead-generation engine that aggregates data from various sources, including web searches, GitHub releases, and community platforms like Hacker News. The engine filters out recent topics to focus on the most relevant and impactful tools for developers.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Data Sources&lt;/strong&gt;: MiniMax-Text-01, web_search, Hacker News, GitHub, sources.py crawl.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Exclusions&lt;/strong&gt;: Recent 27 topics filtered out to ensure freshness.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Focus&lt;/strong&gt;: High-impact tools and tutorials for immediate use.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Start with this today
&lt;/h2&gt;

&lt;p&gt;If you're looking to dive into something immediately, try setting up the &lt;strong&gt;OpenAI Codex SDK for JavaScript&lt;/strong&gt; with this one-liner:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install &lt;/span&gt;openai-codex
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  Picks of the week
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;TensorFlow Quantum 3.0&lt;/strong&gt;: Perfect for those interested in the intersection of quantum computing and machine learning.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PyTorch Lightning CLI 2.0&lt;/strong&gt;: Essential for anyone scaling PyTorch models across multiple GPUs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;OpenAI Codex SDK for JavaScript&lt;/strong&gt;: Ideal for developers looking to automate code generation in their applications.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Question for readers
&lt;/h3&gt;

&lt;p&gt;Which of these tools are you most excited to try out? Share your thoughts and projects in the comments below!&lt;/p&gt;




&lt;h4&gt;
  
  
  Connect
&lt;/h4&gt;

&lt;p&gt;If this kind of post is useful, the easiest way to support the work is to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Star / follow&lt;/strong&gt; on &lt;a href="https://dev.to/karleeov"&gt;dev.to&lt;/a&gt; (you're already here 🙂)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Follow on X&lt;/strong&gt;: &lt;a href="https://x.com/devkingov" rel="noopener noreferrer"&gt;@devkingov&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reach out for HK-based dev work&lt;/strong&gt; — .NET / Azure / system integration / IT security: &lt;a href="https://studio.resurrects.co" rel="noopener noreferrer"&gt;studio.resurrects.co&lt;/a&gt; or email &lt;a href="mailto:devkingov@gmail.com"&gt;devkingov@gmail.com&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Subscribe to weekly HK tech posts&lt;/strong&gt; → &lt;a href="https://studio.resurrects.co/blog" rel="noopener noreferrer"&gt;studio.resurrects.co/blog&lt;/a&gt; (one email a week, no spam)&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>tensorflow</category>
      <category>pytorch</category>
      <category>openai</category>
      <category>django</category>
    </item>
    <item>
      <title>MPS backend out of memory on a Mac: what the numbers mean and how to set the watermark</title>
      <dc:creator>Rodion Kazennov</dc:creator>
      <pubDate>Wed, 14 Oct 2026 00:01:59 +0000</pubDate>
      <link>https://dev.to/nefayran/mps-backend-out-of-memory-on-a-mac-what-the-numbers-mean-and-how-to-set-the-watermark-43dp</link>
      <guid>https://dev.to/nefayran/mps-backend-out-of-memory-on-a-mac-what-the-numbers-mean-and-how-to-set-the-watermark-43dp</guid>
      <description>&lt;p&gt;When a PyTorch job runs out of memory on a Mac, the error ends with a piece of advice:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;RuntimeError: MPS backend out of memory (MPS allocated: 18.50 GiB, other allocations: 448.00 KiB, max allowed: 18.72 GiB). Tried to allocate 512.00 MiB on shared pool. Use PYTORCH_MPS_HIGH_WATERMARK_RATIO=0.0 to disable upper limit for memory allocations (may cause system failure).
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I wanted to know what following that advice does, so I filled MPS memory in 512 MiB steps on my 48 GB M5 Pro (macOS 26.5.2, PyTorch 2.14.0), once per setting, under a guard that stops the process when swap grows by 2 GB. Two things came out of it. On this Mac the default limit is higher than the RAM, so a job that outgrows memory slides into swap and never gets to this error. And lowering the limit takes two variables, not one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where "max allowed" comes from
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;max allowed&lt;/code&gt; is the limit: &lt;code&gt;PYTORCH_MPS_HIGH_WATERMARK_RATIO&lt;/code&gt; times what &lt;code&gt;torch.mps.recommended_max_memory()&lt;/code&gt; returns. On my Mac that is 37.44 GiB, and 0.5 × 37.44 = 18.72 GiB, the number in the error above. &lt;code&gt;MPS allocated&lt;/code&gt; matched &lt;code&gt;torch.mps.current_allocated_memory()&lt;/code&gt; in my log, the memory held by tensors. To see your own base number:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mps&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;recommended_max_memory&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;GiB&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Why a 48 GB Mac swaps first
&lt;/h2&gt;

&lt;p&gt;PyTorch's documentation gives the default high ratio as 1.7. On my Mac that puts the limit at about 63.6 GiB, more memory than the machine has. This is what each setting did:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqpqvawycwc57andf1ro2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqpqvawycwc57andf1ro2.png" alt="Tensors held when each run stopped: with the default ratio, 0.0 and 1.0 the Mac went into swap at 31 to 34 GiB and the guard stopped the run; with 0.8 and 0.5 PyTorch raised out of memory at its limit" width="800" height="448"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;setting&lt;/th&gt;
&lt;th&gt;what stopped the fill&lt;/th&gt;
&lt;th&gt;tensors held&lt;/th&gt;
&lt;th&gt;swap during the run&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;default (1.7)&lt;/td&gt;
&lt;td&gt;the guard&lt;/td&gt;
&lt;td&gt;38.5 GiB&lt;/td&gt;
&lt;td&gt;6.1 → 12.0 GB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.0&lt;/td&gt;
&lt;td&gt;the guard&lt;/td&gt;
&lt;td&gt;36.0 GiB&lt;/td&gt;
&lt;td&gt;5.1 → 9.8 GB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1.0, low 1.0&lt;/td&gt;
&lt;td&gt;the guard&lt;/td&gt;
&lt;td&gt;34.5 GiB&lt;/td&gt;
&lt;td&gt;4.6 → 8.7 GB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.8, low 0.8&lt;/td&gt;
&lt;td&gt;PyTorch, max allowed 29.95 GiB&lt;/td&gt;
&lt;td&gt;29.5 GiB&lt;/td&gt;
&lt;td&gt;no change&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.5, low 0.5&lt;/td&gt;
&lt;td&gt;PyTorch, max allowed 18.72 GiB&lt;/td&gt;
&lt;td&gt;18.5 GiB&lt;/td&gt;
&lt;td&gt;no change&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;With the default, with 0.0 and with 1.0, swap started to grow once the process passed 33.5 to 36.2 GB, and PyTorch never raised: its limit was further away than the end of the RAM. So &lt;code&gt;0.0&lt;/code&gt;, the advice inside the error, changed nothing I could see here. The limit was already out of reach, and nothing in PyTorch stopped the run.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting it: two variables
&lt;/h2&gt;

&lt;p&gt;Setting only the high ratio fails at the first MPS call:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ PYTORCH_MPS_HIGH_WATERMARK_RATIO=0.8 python fill.py
RuntimeError: invalid low watermark ratio 1.4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The low ratio defaults to 1.4 on Apple Silicon and may not be above the high one (unless the high one is 0.0, which turns the limit off). Set both:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PYTORCH_MPS_HIGH_WATERMARK_RATIO=0.8 PYTORCH_MPS_LOW_WATERMARK_RATIO=0.8 python run.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From Python, set them before the first tensor goes to &lt;code&gt;mps&lt;/code&gt;. PyTorch reads them when MPS starts, not at &lt;code&gt;import torch&lt;/code&gt;, and ignores changes made after that:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;
&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;PYTORCH_MPS_HIGH_WATERMARK_RATIO&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;0.8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;PYTORCH_MPS_LOW_WATERMARK_RATIO&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;0.8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The documentation calls the low ratio a soft limit and says that "setting 0.0 disables adaptive commit and garbage collection". I set it equal to the high one and did not measure it on its own.&lt;/p&gt;

&lt;h2&gt;
  
  
  Picking a value
&lt;/h2&gt;

&lt;p&gt;The limit should stay below the memory your Mac really has free, with your usual apps open. On mine 1.0 (37.44 GiB) was already too much, and 0.8 (29.95 GiB) gave a clean error with no swap. After a clean error you can lower the batch size or the resolution and try again; a run sliding into swap slows down the whole Mac first.&lt;/p&gt;

&lt;h2&gt;
  
  
  A trap when you test this
&lt;/h2&gt;

&lt;p&gt;My first run filled the tensors with &lt;code&gt;torch.ones&lt;/code&gt;. With the default setting it reached the 46 GiB cap I had set, with no error and no swap at all: macOS compresses pages of identical values almost to nothing. Model weights don't compress like that, so a memory test on a Mac needs random data.&lt;/p&gt;

&lt;h2&gt;
  
  
  When the model really doesn't fit
&lt;/h2&gt;

&lt;p&gt;A clean error only tells you the model doesn't fit. For a pipeline of several models, loading only the models of the current stage does more than any ratio, which is why I wrote &lt;a href="https://github.com/nefayran/stageload" rel="noopener noreferrer"&gt;stageload&lt;/a&gt;. The fill script and the traces of every run above are in its repository, in &lt;a href="https://github.com/nefayran/stageload/tree/main/bench/results/2026-10-13-mps-watermark" rel="noopener noreferrer"&gt;&lt;code&gt;bench/results/2026-10-13-mps-watermark&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>pytorch</category>
      <category>python</category>
      <category>macos</category>
      <category>machinelearning</category>
    </item>
    <item>
      <title>PyTorch on a Mac: why memory looks freed after .to("cpu")</title>
      <dc:creator>Rodion Kazennov</dc:creator>
      <pubDate>Tue, 13 Oct 2026 08:43:56 +0000</pubDate>
      <link>https://dev.to/nefayran/pytorch-on-a-mac-why-memory-looks-freed-after-tocpu-1apl</link>
      <guid>https://dev.to/nefayran/pytorch-on-a-mac-why-memory-looks-freed-after-tocpu-1apl</guid>
      <description>&lt;p&gt;On a Mac, a tensor moved from &lt;code&gt;mps&lt;/code&gt; to the CPU seems to disappear from memory. The process footprint, the number Activity Monitor shows in its Memory column, drops to almost nothing, and so does the MPS driver count. The copy is still there and still takes the same memory. This is the minimal example I put together for a &lt;a href="https://github.com/huggingface/diffusers/issues/14833" rel="noopener noreferrer"&gt;diffusers discussion about offloading&lt;/a&gt;, with a 4 GiB tensor standing in for a model's weights (torch 2.14.0, macOS 26.5.2, M5 Pro with 48 GB):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ctypes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;

&lt;span class="c1"&gt;# rusage_info_v2 from &amp;lt;libproc.h&amp;gt;; phys_footprint is the Memory column of Activity Monitor
&lt;/span&gt;&lt;span class="n"&gt;FIELDS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user_time&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;system_time&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pkg_idle_wkups&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;interrupt_wkups&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pageins&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;wired_size&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;resident_size&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;phys_footprint&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;RUsageInfoV2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctypes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Structure&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;_fields_&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;uuid&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ctypes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;c_uint8&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ctypes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;c_uint64&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;FIELDS&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; \
        &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rest&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ctypes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;c_uint64&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;

&lt;span class="n"&gt;libproc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ctypes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;CDLL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/usr/lib/libproc.dylib&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;show&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;step&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mps&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;synchronize&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;info&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;RUsageInfoV2&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;libproc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;proc_pid_rusage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getpid&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ctypes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;byref&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;info&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;step&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;14&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; footprint &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;info&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;phys_footprint&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;4.1&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; GiB, &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
          &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mps driver &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mps&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;driver_allocated_memory&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;4.1&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; GiB&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;show&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;start&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;randn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;device&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mps&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;      &lt;span class="c1"&gt;# 4 GiB of float32, standing in for a model's weights
&lt;/span&gt;&lt;span class="nf"&gt;show&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;on mps&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cpu&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;                           &lt;span class="c1"&gt;# what model CPU offload does when a model's turn ends
&lt;/span&gt;&lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mps&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;empty_cache&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;show&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;moved to cpu&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;                            &lt;span class="c1"&gt;# read every page of the cpu copy
&lt;/span&gt;&lt;span class="nf"&gt;show&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cpu copy read&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;start          footprint  0.1 GiB, mps driver  0.0 GiB
on mps         footprint  4.2 GiB, mps driver  4.0 GiB
moved to cpu   footprint  0.1 GiB, mps driver  0.0 GiB
cpu copy read  footprint  4.2 GiB, mps driver  0.0 GiB
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I ran it three times and got the same output each time.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the numbers say
&lt;/h2&gt;

&lt;p&gt;Right after the move, both counters are back near zero, but &lt;code&gt;w&lt;/code&gt; holds all 4 GiB of its values. Once the CPU reads every page of the copy, the footprint is 4.2 GiB again. So a footprint taken between &lt;code&gt;.to("cpu")&lt;/code&gt; and the next time the CPU touches those weights does not show them.&lt;/p&gt;

&lt;p&gt;The reason the move frees nothing in the first place is the hardware. On Apple Silicon the CPU and the GPU use one pool of physical memory. On a PC, moving weights from the GPU to the CPU frees VRAM for the next model. On a Mac, the weights "on the CPU" sit in the same RAM the GPU needs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why it matters for model CPU offload
&lt;/h2&gt;

&lt;p&gt;diffusers' &lt;code&gt;enable_model_cpu_offload()&lt;/code&gt; keeps each model on the CPU and moves it to the GPU only while it runs. I ran Qwen-Image-2.1 at 1024 × 1024 that way on the same Mac (&lt;a href="https://allkeep.org/en/lab/model-cpu-offload-on-a-mac" rel="noopener noreferrer"&gt;trace and write-up&lt;/a&gt;). The process footprint never went above 18.5 GB, which would fit on a 48 GB Mac with room to spare. Swap grew by 3.5 GB during denoising and had grown by 9.6 GB by the VAE decode, and my memory guard stopped the run before it wrote an image.&lt;/p&gt;

&lt;p&gt;A chart of the footprint alone would have called that run a fit. The example above shows one way the footprint can stay low while the weights are still in memory. I didn't measure which pages went to swap in the offload run.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to check your own run
&lt;/h2&gt;

&lt;p&gt;Look at swap, not only at the footprint:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;sysctl vm.swapusage             &lt;span class="c"&gt;# compare "used" before and after the run&lt;/span&gt;
sysctl kern.memorystatus_level  &lt;span class="c"&gt;# system-wide free memory, in percent&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If swap grows while the job runs, it doesn't fit, whatever the footprint says. &lt;a href="https://github.com/nefayran/stageload" rel="noopener noreferrer"&gt;stageload&lt;/a&gt; (&lt;code&gt;pip install stageload&lt;/code&gt;), a small library I wrote for this, records both in a trace every 0.5 s with &lt;code&gt;MemoryMeter&lt;/code&gt;, and &lt;code&gt;stageload guard&lt;/code&gt; stops a command once swap grows past a budget:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;stageload guard &lt;span class="nt"&gt;--wait-free&lt;/span&gt; 40 &lt;span class="nt"&gt;--swap-budget&lt;/span&gt; 8G &lt;span class="nt"&gt;--&lt;/span&gt; python generate.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What does free the memory
&lt;/h2&gt;

&lt;p&gt;For a pipeline that runs its models one after another, the fix is to load a model when its stage starts and release it when the stage ends, instead of parking it on the CPU. stageload's &lt;code&gt;release(module)&lt;/code&gt; moves every parameter and buffer to PyTorch's &lt;code&gt;meta&lt;/code&gt; device, collects garbage and empties the MPS cache; a tensor on &lt;code&gt;meta&lt;/code&gt; has no storage, so the memory goes back even while other code still holds a reference to the module. With the text encoder (16.3 GB) and the transformer (13.3 GB) loaded one at a time this way, Qwen-Image-2.1 peaked at 19.0 GB on the same Mac, and swap did not grow in either of two runs. The full setup is in &lt;a href="https://allkeep.org/en/lab/qwen-image-on-a-48-gb-mac" rel="noopener noreferrer"&gt;How to run Qwen-Image-2.1 on a 48 GB Mac without swap&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I did not measure
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;The free memory of the whole system during the example: other jobs on this Mac move it by several GiB between runs, so I couldn't get a clean number.&lt;/li&gt;
&lt;li&gt;Whether &lt;code&gt;del&lt;/code&gt; plus &lt;code&gt;torch.mps.empty_cache()&lt;/code&gt; gives the memory back as fully as moving to &lt;code&gt;meta&lt;/code&gt;, and how quickly.&lt;/li&gt;
&lt;li&gt;Other macOS and torch versions, and Macs with more memory.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>pytorch</category>
      <category>python</category>
      <category>macos</category>
      <category>machinelearning</category>
    </item>
    <item>
      <title>I Trained a Language Model From Scratch on a 4 GB GPU</title>
      <dc:creator>Sandra Bamboo</dc:creator>
      <pubDate>Mon, 12 Oct 2026 08:07:10 +0000</pubDate>
      <link>https://dev.to/sandrabamboo/i-trained-a-language-model-from-scratch-on-a-4-gb-gpu-1kf5</link>
      <guid>https://dev.to/sandrabamboo/i-trained-a-language-model-from-scratch-on-a-4-gb-gpu-1kf5</guid>
      <description>&lt;p&gt;&lt;em&gt;A 4 GB GPU, a small corpus and a lot of things that went wrong in instructive ways.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I trained a 22-million-parameter language model from scratch on a laptop with 4 GB of VRAM. It learned to imitate technical documentation, got stuck repeating fragments of language, and never produced a working Fibonacci function. Near the end, CUDA crashed. The result wasn't a useful chatbot. The failures were the interesting part.&lt;/p&gt;

&lt;p&gt;Pulling a pretrained model through Ollama makes modern AI feel deceptively simple. Training even a tiny one from random weights removes that illusion very quickly.&lt;/p&gt;

&lt;p&gt;That’s why I started AI Lab, a separate repository for research code, datasets, experiment definitions, models and results. AI.001 was my first experiment: training a small language model from random weights on my laptop. This is the story behind that run, including what the model actually generated. The complete methodology, corpus breakdown and training curves live in the &lt;a href="https://sandrabamboo.com/experiments/training-a-language-model-from-scratch-on-4gb-of-vram/" rel="noopener noreferrer"&gt;AI.001 experiment write-up&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  I wanted to understand what happens before inference
&lt;/h2&gt;

&lt;p&gt;APIs and pretrained local models are wonderful, and they hide almost everything I wanted to see. What does the dataset actually look like? How much does cleaning matter? What happens between a document and a token? What is a checkpoint, really? What does loss tell me, and what does it quietly not tell me? What does generation look like before a model is useful at all?&lt;/p&gt;

&lt;p&gt;I couldn’t answer those by calling something that already worked. The only way I know to find out is to build the unglamorous version and watch it misbehave.&lt;/p&gt;

&lt;h2&gt;
  
  
  The constraint: 4 GB of VRAM
&lt;/h2&gt;

&lt;p&gt;The machine is a Lenovo IdeaPad Gaming laptop: an Intel i7-10750H, 16 GB of RAM and a GTX 1650 with 4 GB of VRAM, running PyTorch with CUDA under WSL2. It is not a good machine to train on, and I’m not presenting it as one.&lt;/p&gt;

&lt;p&gt;That’s the point. With a big GPU you can hide a lot of sloppy decisions behind headroom. With 4 GB you have to know why the context length is what it is, why the batch is split into microbatches, and what the optimizer state costs. I settled on a 256-token context and microbatches of 16 with gradient accumulation, so each update still sees a reasonably large batch. Peak memory for the main run was 1.54 GiB, which is comfortably under the card’s limit. I don’t read that as the card being generous. I read it as the result of designing the pipeline around the limit from the start rather than discovering it halfway through.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building the corpus
&lt;/h2&gt;

&lt;p&gt;Spanish and English Wikipedia, Python documentation and a handful of other technical documentation sets: collecting text is the easy part. Anyone can download a dump. The interesting part is deciding what is allowed into the training set and what that decision does later.&lt;/p&gt;

&lt;p&gt;I chose a controlled corpus over a large unfiltered web dataset, and judged sources on quality, licensing, language balance, duplication risk and how painful they were to preprocess. Cleaning and filtering is where the corpus stops being “everything I could download” and becomes something I made on purpose. I’m not going to dump corpus statistics here; the experiment page has them. What I’d rather say is that those choices came back later, in the evaluation, as behaviour I could point at.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5jh9veay0ncj1q7cqorl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5jh9veay0ncj1q7cqorl.png" alt="AI.001 workflow: select source text, clean and filter, tokenise, train from random weights, save a resumable checkpoint, then evaluate held-out text and inspect generated output." width="800" height="1263"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;The workflow behind AI.001. Checkpoints make recovery possible; held-out loss and generated samples answer different questions.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  A falling loss, a CUDA crash, and a checkpoint
&lt;/h2&gt;

&lt;p&gt;Validation loss started at 9.48. With a vocabulary of 12,000 tokens, uniformly guessing the next token would give a loss of about 9.39. After roughly one pass over the corpus, the model reached 4.27. That was progress, and it was satisfying to see. But it answered one narrow question — whether predictions on held-out text had improved — not whether the model could generate anything useful.&lt;/p&gt;

&lt;p&gt;Checkpoints stopped being an abstract idea at around the same time. I had tested resuming from a checkpoint on a short run before committing to the long one, and it printed exactly what I hoped to see:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Resumed from: results/EXP-001/resume-test/latest.pt
Resume step: 10
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That turned out to be sensible. Shortly after step 3,540 of the main run, CUDA reported an unknown sticky error and training stopped. It wasn’t an out-of-memory failure and the loss had been behaving. I restarted from the step-3,500 checkpoint, with the optimizer, mixed-precision scaler and random state restored, and it ran to the end.&lt;/p&gt;

&lt;p&gt;The more instructive surprise was generation. With sampling, later checkpoints produced fragments that looked like technical documentation; an early one mixed WordPress, MDN and Docker material incoherently. With greedy decoding — always taking the most likely next token — the final model often fell into loops. Validation loss did not reveal that failure mode. During generation the model has to continue from its own output, not from the real text used for evaluation. Here is what a few prompts produced:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Prompt: “A WordPress plugin can” → Sampled outputs (temperature 0.8, final checkpoint): “A WordPress plugin can be used when the plugin is loaded” and “A plugin contains a PHP file”&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Prompt: “Barcelona es una ciudad” → Greedy decoding: repeated variations of “de la ciudad”&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Prompt: “A language model learns” → Greedy decoding: “the language is the language language…”&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Prompt: “def fibonacci(n):” → None of the checkpoints I tested produced a valid implementation&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Evaluation was more interesting than training
&lt;/h2&gt;

&lt;p&gt;Those examples were more revealing than the loss curve on its own. So I evaluated the final checkpoint by corpus component instead of relying only on an aggregate score. Technical documentation had lower loss than general prose, but there was a wide spread between sources: MDN and WordPress text was more predictable to this model than Docker, Python or Git documentation. &lt;a href="https://sandrabamboo.com/experiments/training-a-language-model-from-scratch-on-4gb-of-vram/" rel="noopener noreferrer"&gt;The source-by-source results in AI.001&lt;/a&gt; show the actual measurements.&lt;/p&gt;

&lt;p&gt;I want to be careful with that ordering. It is an observation from one checkpoint of one run, and it is not evidence that the model is good. What it does is raise better questions than the aggregate loss did. Is a source easy because it is well-written, or because it is repetitive? How much of a low loss is the model knowing something and how much is a predictable format? Did I give some sources more room than they deserved? Is there overlap between domains that I haven’t noticed? Loss can’t separate those on its own.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tiny models are brutally honest
&lt;/h2&gt;

&lt;p&gt;A 22-million-parameter model, trained for about one pass, has nowhere to hide. It learned how technical documentation looks far better than what it means. It writes convincing headings and parameter lists for APIs that don’t exist, drifts between domains within a paragraph, and never produced a working &lt;code&gt;def fibonacci(n):&lt;/code&gt; at any checkpoint I tried.&lt;/p&gt;

&lt;p&gt;That is useful for learning precisely because it is bad. A strong model smooths over its mechanics. A weak one shows them: capacity, data mix, repetition, the gap between predicting text and knowing things. This is research, not product development, and I try to keep that straight in my own head.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I actually got from it
&lt;/h2&gt;

&lt;p&gt;Not a competitive model, and not something I would use to answer questions. What I got was a research workflow: repeatable corpus tooling, training scripts that can pause and resume, and evaluations broken down by source. More importantly, I now know to look at both held-out loss and actual generations before deciding what a model has learned. That feels more useful than a demo that only looks convincing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where it goes next
&lt;/h2&gt;

&lt;p&gt;I’m not promising a bigger model. The questions the first run left open are about corpus selection, better evaluation, and changing one decision at a time so I can tell what each change did. &lt;a href="https://sandrabamboo.com/experiments/training-a-language-model-from-scratch-on-4gb-of-vram/" rel="noopener noreferrer"&gt;The experiment page lists a more focused follow-up&lt;/a&gt;, with more source code in the mix and a fixed evaluation I can compare against this one. I’d rather make a small, controlled step that I understand than a large one that I can only describe.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on sandrabamboo.com: &lt;a href="https://sandrabamboo.com/training-my-first-language-model-on-a-gtx-1650/" rel="noopener noreferrer"&gt;https://sandrabamboo.com/training-my-first-language-model-on-a-gtx-1650/&lt;/a&gt; — for model architecture, corpus sources, training curves and per-source evaluation, see the complete AI.001 experiment: &lt;a href="https://sandrabamboo.com/experiments/training-a-language-model-from-scratch-on-4gb-of-vram/" rel="noopener noreferrer"&gt;https://sandrabamboo.com/experiments/training-a-language-model-from-scratch-on-4gb-of-vram/&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>beginners</category>
      <category>machinelearning</category>
      <category>ai</category>
      <category>pytorch</category>
    </item>
    <item>
      <title>Model security on Hugging Face: what executes when you load</title>
      <dc:creator>Nikoloz Turazashvili (@axrisi)</dc:creator>
      <pubDate>Mon, 12 Oct 2026 02:30:00 +0000</pubDate>
      <link>https://dev.to/axrisi/model-security-on-hugging-face-what-executes-when-you-load-2eec</link>
      <guid>https://dev.to/axrisi/model-security-on-hugging-face-what-executes-when-you-load-2eec</guid>
      <description>&lt;p&gt;Local AI gives you control over where your model runs. Model security also depends on what your loader executes along the way. Researchers have documented Hugging Face model packages containing reverse-shell payloads, and an unsafe loading path can give that code the same access as your Python process. Your machine gets the model and, potentially, an ambitious new colleague.&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/Wk9gxtH5UuI" width="710" height="399"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Model weights are learned numerical data. A model repository can also carry configuration, executable serialization and Python files.&lt;/li&gt;
&lt;li&gt;JFrog's Goober Two example and ReversingLabs' nullifAI samples contained reverse-shell payloads. The selected reports do not establish credential theft from actual victims.&lt;/li&gt;
&lt;li&gt;A separate Checkmarx research demonstration showed how a missing local model path could resolve to a public repository. Remote-code trust was an important execution prerequisite.&lt;/li&gt;
&lt;li&gt;Safetensors, current patched loaders, reviewed revisions and isolation address different parts of the loading path.&lt;/li&gt;
&lt;li&gt;Readable secrets and available network access determine how much damage code running in the loader can do.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Model security begins before the first answer
&lt;/h2&gt;

&lt;p&gt;Hugging Face is a platform for sharing machine learning models, datasets and applications. A repository is a useful distribution unit: it can contain the weights, the configuration needed to interpret them and, for custom architectures, implementation code. The contents deserve separate questions because their loading behavior differs.&lt;/p&gt;

&lt;p&gt;The weights are the learned numbers. A tensor-data reader interprets their shapes and values. Python object serialization can describe how to rebuild objects, including calls to functions. Repository Python code introduces another execution path when your application authorizes it. Putting all these files under the heading “model” makes installation convenient and the trust boundary easy to miss.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://huggingface.co/docs/hub/en/security-pickle" rel="noopener noreferrer"&gt;Hugging Face's Pickle Scanning documentation&lt;/a&gt; states the central warning plainly: “There are dangerous arbitrary code execution attacks that can be perpetrated when you load a pickle file.” The significant verb is &lt;em&gt;load&lt;/em&gt;. Saving bytes to disk and handing those bytes to an unsafe deserializer are different actions.&lt;/p&gt;

&lt;p&gt;For the incidents discussed here, the file's instructions become dangerous when a loading path follows them. That can happen before inference begins. A malicious package does not need to persuade the model to reveal anything in its answer if the surrounding software already lets its code run. The benchmark can wait; the process has started accepting guests.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pickle security: opening the parcel
&lt;/h2&gt;

&lt;p&gt;Pickle stores Python objects with instructions for reconstructing them. Deserialization reads those instructions in sequence. An unsafe loader may resolve and call functions during reconstruction, which gives an untrusted file influence over the host process.&lt;/p&gt;

&lt;p&gt;Think of the package as a parcel and the loader as the person opening it. Downloading places the parcel on the shelf. Loading follows the enclosed assembly instructions. If those instructions can invoke arbitrary functions, the activity has crossed from reading data into executing the publisher's choices.&lt;/p&gt;

&lt;p&gt;The exact loading API, library version and settings matter. A security statement about one unsafe path cannot be extended to every model loader. Modern restricted loading modes deliberately reduce what object reconstruction is allowed to do. Conversely, overriding a rejection because an unfamiliar checkpoint “needs it” can reopen the boundary you were relying on.&lt;/p&gt;

&lt;p&gt;This is also why “poisoned weights” needs explanation. The phrase can refer to model behavior changed by training or editing numerical parameters. The cases in this episode concern executable packaging and repository code around model distribution. Those mechanisms have their own entry points, permissions and defenses.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three concrete examples, with their limits
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Goober Two: a documented reverse shell
&lt;/h3&gt;

&lt;p&gt;In February 2024, &lt;a href="https://jfrog.com/blog/data-scientists-targeted-by-malicious-hugging-face-ml-models-with-silent-backdoor/" rel="noopener noreferrer"&gt;JFrog analyzed &lt;code&gt;baller423/goober2&lt;/code&gt;&lt;/a&gt;. Its Pickle payload initiated a reverse shell during unsafe loading. A reverse shell connects outward and offers command access to the other end of the connection.&lt;/p&gt;

&lt;p&gt;The repository was deleted. JFrog also discussed clues suggesting possible researcher involvement, so attribution remains uncertain. The report supplies evidence for the payload's behavior. It does not establish that criminals stole a named victim's cloud keys, and it does not justify inventing a victim count.&lt;/p&gt;

&lt;p&gt;That distinction leaves the mechanism plenty worrying. Command access under the loader's account is a meaningful foothold. A development environment often gives that account more access than a model experiment needs.&lt;/p&gt;

&lt;h3&gt;
  
  
  nullifAI: unusual packaging and an error that arrived late
&lt;/h3&gt;

&lt;p&gt;In February 2025, &lt;a href="https://www.reversinglabs.com/blog/rl-identifies-malware-ml-model-hosted-on-hugging-face" rel="noopener noreferrer"&gt;ReversingLabs described two model samples&lt;/a&gt;, including &lt;code&gt;glockr1/ballr7&lt;/code&gt;, under the name nullifAI. Both contained platform-aware reverse-shell payloads.&lt;/p&gt;

&lt;p&gt;An important detail is the archive format. These samples used seven-zip packaging where ordinary PyTorch loading expected a different structure. Default &lt;code&gt;torch.load&lt;/code&gt; would reject that packaging. The dangerous path required extraction followed by unsafe Pickle deserialization. A diagram showing an ordinary model download immediately opening these samples would skip a real prerequisite.&lt;/p&gt;

&lt;p&gt;The report also examined broken Pickle streams and a historical scanner blind spot. Its harmless file-writing research test showed that an earlier instruction could execute before a later error stopped loading. The file already existed when the loader complained. Imagine releasing a raccoon from a parcel and then discovering that the shipping label is invalid. The complaint may be accurate; the timing is deeply unhelpful.&lt;/p&gt;

&lt;p&gt;ReversingLabs disclosed the finding on January 20, 2025. Hugging Face removed the reported samples within twenty-four hours and changed the scanner. This example describes a historical finding and its response. It supplies a reason to investigate scanner failures, rather than a claim that the same bypass remains unpatched.&lt;/p&gt;

&lt;h3&gt;
  
  
  Model Confusion: a missing folder resolves somewhere else
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://checkmarx.com/zero-post/hugs-from-strangers-ai-model-confusion-supply-chain-attack/" rel="noopener noreferrer"&gt;Checkmarx's January 2026 Model Confusion article&lt;/a&gt; describes a research demonstration involving an unnamed company's sample. A relative identifier such as &lt;code&gt;checkpoints/some-model&lt;/code&gt; could refer to a local path or a public model namespace. If the local path was missing, resolution could select a matching remote repository.&lt;/p&gt;

&lt;p&gt;Combine that ambiguity with insecure loading and remote-code trust, and the selected repository's code can execute. The company's sample was corrected within a day, according to Checkmarx. The report explicitly describes the demonstration and says it had not found malicious activity in the sensitive-name examples it highlighted.&lt;/p&gt;

&lt;p&gt;Two conditions are doing work here: selecting the unexpected remote package, and authorizing an execution-capable loading path. Checking only whether a string looks like a local folder misses the resolution step. A missing directory should not get to improvise its own landlord.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where readable credentials enter the flow
&lt;/h2&gt;

&lt;p&gt;Once code runs in a process, its access depends on the account, filesystem mounts and other controls around that process. An accessible cloud key, a token available in the environment or a readable private-key file can become a target. Secrets kept outside that access boundary remain outside the straightforward read path.&lt;/p&gt;

&lt;p&gt;Sending a secret elsewhere also requires an available network route. Read access and outbound connectivity are independent conditions. Blocking egress can constrain a transfer; keeping the secret out of the process can prevent the read in the first place. Numerical weights alone supply neither capability.&lt;/p&gt;

&lt;p&gt;The selected reverse-shell reports document command-access payloads. Credential collection and exfiltration are conditional consequences of that access, rather than proven victim outcomes in these reports. The episode's key movement is therefore labeled “possible consequence,” and the original key remains in place while a copy follows the possible route.&lt;/p&gt;

&lt;p&gt;This is where a familiar development setup gets awkward. A container with your home directory or credentials mounted inside exposes those mounted resources to code in the container. The namespace boundary can be useful, but the key you deliberately put inside it is still inside it. A container full of secrets is a gift basket with resource limits.&lt;/p&gt;

&lt;h2&gt;
  
  
  Safetensors, patched loaders and a useful isolation boundary
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://huggingface.co/docs/safetensors/en/index" rel="noopener noreferrer"&gt;Safetensors&lt;/a&gt; provides a tensor-data format designed for safe storage without Pickle's executable object reconstruction. Choosing it addresses that serialization route. A repository can still contain Python code alongside the tensor files, so review the code-loading path as a separate decision.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://huggingface.co/docs/transformers/en/models" rel="noopener noreferrer"&gt;Hugging Face's custom model loading documentation&lt;/a&gt; warns about inadvertently executing malicious code and describes &lt;code&gt;trust_remote_code=True&lt;/code&gt;. It also recommends pinning a specific revision. Review the exact revision you intend to run, then keep that decision stable while you evaluate the package.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://docs.pytorch.org/docs/2.14/notes/serialization.html" rel="noopener noreferrer"&gt;PyTorch's serialization documentation&lt;/a&gt; explains that &lt;code&gt;torch.load&lt;/code&gt; defaults to &lt;code&gt;weights_only=True&lt;/code&gt; starting with version 2.6 when no &lt;code&gt;pickle_module&lt;/code&gt; is passed. The restricted unpickler narrows the remote-code-execution surface. The documentation also retains limitations involving denial of service and possible memory corruption, so the setting deserves its actual scope.&lt;/p&gt;

&lt;p&gt;For unfamiliar packages, I would separate acquisition and inspection from execution. Prepare a clean environment with only the files the experiment needs. Leave personal keys outside it. Restrict network access according to the task, and make repository-code trust an explicit reviewed choice. The useful question is what a process could reach if its loading behavior surprised you.&lt;/p&gt;

&lt;h2&gt;
  
  
  Verdict: NEEDS REVIEW
&lt;/h2&gt;

&lt;p&gt;I'd keep open models and put the permission boundary around the loader. The documented examples show why model distribution needs the same attention we give other executable inputs. Free access to a model is valuable; deciding who gets access to the development environment is also valuable.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Can downloading a Hugging Face model steal my credentials?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The mechanisms discussed here execute during unsafe loading or authorized repository-code execution. Merely saving the model bytes is a different step. Review the path that will open them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Does safetensors make a whole repository safe?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It addresses tensor serialization. Review custom Python and dependencies separately, and keep the loader's access appropriately narrow.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Does a failed load mean nothing happened?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Earlier instructions may have executed before a later error. ReversingLabs' harmless test demonstrated exactly that ordering.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Were credentials proven stolen in these examples?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The selected reports establish reverse-shell payloads and a code-execution research demonstration. They do not establish credential theft from actual victims. Readable credentials are a conditional exposure worth preventing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://jfrog.com/blog/data-scientists-targeted-by-malicious-hugging-face-ml-models-with-silent-backdoor/" rel="noopener noreferrer"&gt;JFrog: malicious Hugging Face models with a silent backdoor&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.reversinglabs.com/blog/rl-identifies-malware-ml-model-hosted-on-hugging-face" rel="noopener noreferrer"&gt;ReversingLabs: nullifAI and malicious model samples&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://checkmarx.com/zero-post/hugs-from-strangers-ai-model-confusion-supply-chain-attack/" rel="noopener noreferrer"&gt;Checkmarx: Model Confusion research demonstration&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://huggingface.co/docs/hub/en/security-pickle" rel="noopener noreferrer"&gt;Hugging Face: Pickle Scanning&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://huggingface.co/docs/safetensors/en/index" rel="noopener noreferrer"&gt;Hugging Face: Safetensors&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://huggingface.co/docs/transformers/en/models" rel="noopener noreferrer"&gt;Hugging Face: loading custom models&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.pytorch.org/docs/2.14/notes/serialization.html" rel="noopener noreferrer"&gt;PyTorch: serialization semantics&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;This article expands on an episode of **The Daily Diff&lt;/em&gt;&lt;em&gt;, a five-minute daily video on what shipped and what broke in tech.&lt;br&gt;
&lt;a href="https://www.youtube.com/watch?v=Wk9gxtH5UuI" rel="noopener noreferrer"&gt;Watch the episode&lt;/a&gt; · &lt;a href="https://www.youtube.com/@dailydiffdev?sub_confirmation=1" rel="noopener noreferrer"&gt;Subscribe on YouTube&lt;/a&gt; · the written diff lands in your inbox every morning at &lt;a href="https://thedailydiff.dev" rel="noopener noreferrer"&gt;thedailydiff.dev&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://thedailydiff.dev/video/2026-10-12-huggingface-model-malware/" rel="noopener noreferrer"&gt;Video transcript and primary sources&lt;/a&gt;&lt;/p&gt;

</description>
      <category>security</category>
      <category>python</category>
      <category>machinelearning</category>
      <category>pytorch</category>
    </item>
    <item>
      <title>I Set Every Seed and Trained Twice. The Models Diverged at Step 1</title>
      <dc:creator>b1ackjack12</dc:creator>
      <pubDate>Mon, 12 Oct 2026 01:16:31 +0000</pubDate>
      <link>https://dev.to/b1ackjack12/i-set-every-seed-and-trained-twice-the-models-diverged-at-step-1-2h5b</link>
      <guid>https://dev.to/b1ackjack12/i-set-every-seed-and-trained-twice-the-models-diverged-at-step-1-2h5b</guid>
      <description>&lt;p&gt;There's a ritual at the top of every training script, mine included: &lt;code&gt;torch.manual_seed(0)&lt;/code&gt;, &lt;code&gt;np.random.seed(0)&lt;/code&gt;, and the comfortable belief that the run is now reproducible. I've typed it for years. Last week, while chasing a one-point accuracy difference I couldn't explain, I finally tested the belief directly: same seed, same data order, same model init, same machine, same everything — train ResNet18 for 200 steps, twice, and compare.&lt;/p&gt;

&lt;p&gt;The two loss curves matched at step 0 and &lt;strong&gt;diverged at step 1&lt;/strong&gt;. After 200 steps, the largest difference between corresponding weights was 0.237 — for parameters that mostly live between −0.1 and 0.1, that's not drift, that's a different model. Both runs were "seeded." Neither was reproducible.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the randomness sneaks in
&lt;/h2&gt;

&lt;p&gt;The seeds did their job: initialization, data order, and every explicit random draw were identical, which is why step 0 agreed perfectly. The divergence enters in the &lt;em&gt;backward pass&lt;/em&gt;, and it isn't randomness in the usual sense at all — it's floating-point arithmetic meeting parallel hardware.&lt;/p&gt;

&lt;p&gt;Floating-point addition is not associative: &lt;code&gt;(a + b) + c&lt;/code&gt; and &lt;code&gt;a + (b + c)&lt;/code&gt; can differ in the last bit. GPU kernels sum gradient contributions across thousands of threads, many using atomic operations whose completion &lt;em&gt;order depends on the scheduler&lt;/em&gt; — which threads won the race on this particular run, on this particular clock. Same numbers, different addition order, last-bit differences in the gradients. Then SGD amplifies them: a 1e-8 disagreement in a gradient becomes a slightly different weight, which becomes a different activation, and the gap compounds every step. By my run's end, noise that began around the last decimal place a float can represent had grown into 0.237. It's the butterfly effect with a commit hash.&lt;/p&gt;

&lt;p&gt;None of this is a PyTorch bug; it's documented behavior, and &lt;code&gt;cudnn.benchmark&lt;/code&gt; (which I've &lt;a href="https://b1ackjack12.github.io/posts/tf32-cudnn-benchmark-measured" rel="noopener noreferrer"&gt;previously measured&lt;/a&gt; and left enabled) adds a second layer by autotuning to potentially different — nondeterministic — conv algorithms per run.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fl3sjg27j1wjq8tdv65ko.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fl3sjg27j1wjq8tdv65ko.webp" alt="Two identical glowing racing lines leaving one starting gate and slowly fanning apart into visibly different paths across a dark circuit board landscape" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Does 0.237 matter? Only when you're asking small questions
&lt;/h2&gt;

&lt;p&gt;For final accuracy, this nondeterminism behaves like one more seed you didn't choose — my &lt;a href="https://b1ackjack12.github.io/posts/batch-level-gpu-augmentation-accuracy" rel="noopener noreferrer"&gt;augmentation experiment&lt;/a&gt; put ordinary seed-to-seed spread at ±0.3–0.5 points on a small CIFAR setup, and same-seed GPU noise lands in the same statistical bucket. If your effect size dwarfs that, you can ignore this whole post.&lt;/p&gt;

&lt;p&gt;The cases where it bites are the small questions, which are most of debugging. "Did my refactor change the numerics?" — you can't diff against the pre-refactor run, because &lt;em&gt;nothing&lt;/em&gt; diffs clean, ever. "Is this 0.4-point improvement my new loss term or luck?" — under nondeterminism you can't even hold the luck constant while you toggle the loss term. A/B comparisons where the honest answer requires the same noise on both sides are exactly where "seeded but nondeterministic" quietly wastes days. I know because the one-point mystery that started this post turned out to be two runs of the &lt;em&gt;same code&lt;/em&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The fix, and its real price
&lt;/h2&gt;

&lt;p&gt;PyTorch ships the switch; it just isn't the seed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use_deterministic_algorithms&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;backends&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cudnn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;benchmark&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
&lt;span class="c1"&gt;# and, for CUDA &amp;gt;= 10.2, before the process starts:
# CUBLAS_WORKSPACE_CONFIG=:4096:8
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With those set, my two 200-step runs matched &lt;strong&gt;bit-for-bit&lt;/strong&gt;: max weight difference exactly 0, every loss value identical to the last digit. Two footnotes from the setup: the &lt;code&gt;CUBLAS_WORKSPACE_CONFIG&lt;/code&gt; environment variable is mandatory (you get a clear RuntimeError without it), and &lt;code&gt;use_deterministic_algorithms&lt;/code&gt; will refuse to run ops that simply have no deterministic GPU implementation — ResNet18 sailed through, but if your model contains one of the holdouts, the flag fails loudly and your options are CPU fallback or living without determinism for that op. Loud failure, for once, is the feature.&lt;/p&gt;

&lt;p&gt;The folklore price for all this is "2–3x slower," usually delivered as a reason not to bother. Measured, on the ResNet18 training step:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;precision&lt;/th&gt;
&lt;th&gt;default&lt;/th&gt;
&lt;th&gt;deterministic&lt;/th&gt;
&lt;th&gt;cost&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;fp32&lt;/td&gt;
&lt;td&gt;17.94 ms&lt;/td&gt;
&lt;td&gt;20.90 ms&lt;/td&gt;
&lt;td&gt;+16.5%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;bf16 autocast&lt;/td&gt;
&lt;td&gt;12.42 ms&lt;/td&gt;
&lt;td&gt;14.37 ms&lt;/td&gt;
&lt;td&gt;+15.6%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Sixteen percent. Some of that isn't even determinism proper — it includes giving up &lt;code&gt;cudnn.benchmark&lt;/code&gt;, which &lt;a href="https://b1ackjack12.github.io/posts/tf32-cudnn-benchmark-measured" rel="noopener noreferrer"&gt;I measured at ~3%&lt;/a&gt; on this model. The 2–3x stories presumably come from specific unlucky ops or older stacks, and maybe your model contains one; that's what the two-minute measurement is for. On mine, bit-exact reproducibility costs about as much as a modestly larger batch.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the switch sits now
&lt;/h2&gt;

&lt;p&gt;My default stays fast and nondeterministic — for training throughput, 16% is real money, and ordinary training doesn't need bit-exactness. But the flag has earned a permanent spot in three situations: any debugging session that compares two runs (which is to say, any debugging session), numerics-sensitive refactors where I want &lt;code&gt;git diff&lt;/code&gt;-grade certainty that outputs didn't change, and small-effect experiments where the noise floor would otherwise eat the result. The deciding question is one sentence: &lt;em&gt;am I about to compare two runs and interpret the difference?&lt;/em&gt; If yes, 16% is the cheapest insurance in this entire optimization series — and unlike the seeds ritual, it actually buys what it claims to.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://b1ackjack12.github.io/posts/pytorch-same-seed-different-model/" rel="noopener noreferrer"&gt;b1ackjack12.github.io&lt;/a&gt; — an AI tech blog where every claim comes with a measurement. More experiments like this one live there.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>pytorch</category>
      <category>machinelearning</category>
      <category>deeplearning</category>
      <category>reproducibility</category>
    </item>
    <item>
      <title>Model CPU offload on a 48 GB Mac: 18.5 GB footprint and 9.6 GB of swap</title>
      <dc:creator>Rodion Kazennov</dc:creator>
      <pubDate>Sun, 11 Oct 2026 05:44:25 +0000</pubDate>
      <link>https://dev.to/nefayran/model-cpu-offload-on-a-48-gb-mac-185-gb-footprint-and-96-gb-of-swap-25pe</link>
      <guid>https://dev.to/nefayran/model-cpu-offload-on-a-48-gb-mac-185-gb-footprint-and-96-gb-of-swap-25pe</guid>
      <description>&lt;p&gt;&lt;a href="https://allkeep.org/en/lab/qwen-image-one-stage-at-a-time" rel="noopener noreferrer"&gt;The second stageload note&lt;/a&gt; ran Qwen-Image-2.1 on a 48 GB Mac two ways. The memory guard stopped eager loading in the VAE decode, and staged loading finished. diffusers has a third way built in, &lt;code&gt;enable_model_cpu_offload&lt;/code&gt;, which keeps each model on the CPU and moves it to the GPU only while it runs. On 8 October I ran it once, with the same prompt, size, steps, seed and guard limits as the runs of 6 October.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fuh7d88lr3qt1s2msbny7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fuh7d88lr3qt1s2msbny7.png" alt="Process footprint and swap growth over time for Qwen-Image-2.1 on a 48 GB Mac: eager, model CPU offload and staged" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;By the process footprint, offload looked like staged: it peaked at 18.5 GB, staged at 19.0 GB. Swap tells them apart. Encoding took 8 s and added no swap. During the 87 s of denoising, swap grew by 3.5 GB and available memory fell to 27 %. The VAE decode took swap growth to 9.6 GB and available memory down to 22 %, and the guard stopped the run 105 s in, before it wrote an image. Staged added no swap in either of its two runs and finished in 94 and 124 s.&lt;/p&gt;

&lt;p&gt;The footprint cannot show this, because it does not count pages that are already in swap. On a Mac the CPU and the GPU share one pool of memory, so a model kept "on the CPU" sits in the same RAM as the model that is running. Judged by footprint alone, offload would sit next to staged. Judged by swap, it sits next to eager, which the guard stopped at the same point after 8 GB of new swap.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I could not show
&lt;/h2&gt;

&lt;p&gt;This is one run. A second would have pushed the machine into swap again for an outcome the guard had already decided. I did not measure which pages went to swap, and I did not try offload on a Mac with more memory.&lt;/p&gt;

&lt;p&gt;The script is &lt;a href="https://github.com/nefayran/stageload/blob/main/bench/qwen_image_offload.py" rel="noopener noreferrer"&gt;bench/qwen_image_offload.py&lt;/a&gt;, and the trace is in &lt;a href="https://github.com/nefayran/stageload/tree/main/bench/results/2026-10-08-qwen-image-offload" rel="noopener noreferrer"&gt;the repository&lt;/a&gt;. stageload installs with &lt;code&gt;pip install stageload&lt;/code&gt;.&lt;/p&gt;

</description>
      <category>pytorch</category>
      <category>python</category>
      <category>machinelearning</category>
      <category>macos</category>
    </item>
    <item>
      <title>Building a Transformer from Scratch: Part 1 — The Embedding Layer</title>
      <dc:creator>Dhruv</dc:creator>
      <pubDate>Sat, 10 Oct 2026 11:04:44 +0000</pubDate>
      <link>https://dev.to/heywinterbell/building-a-transformer-from-scratch-part-1-the-embedding-layer-68k</link>
      <guid>https://dev.to/heywinterbell/building-a-transformer-from-scratch-part-1-the-embedding-layer-68k</guid>
      <description>&lt;p&gt;In an autoregressive Transformer such as GPT, the work begins before the attention mechanism processes a single tensor. A model cannot operate on raw text, and integer token IDs carry no semantic structure on their own. The embedding layer bridges this gap by converting discrete token IDs into continuous, high-dimensional vectors that encode both the meaning of each token and its position in the sequence.&lt;/p&gt;

&lt;p&gt;This article explains how the embedding layer works and presents a clean PyTorch implementation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Problem Embeddings Solve&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A tokenizer splits raw text into segments and maps each one to an integer index. For example, the word "transformer" might map to &lt;code&gt;41551&lt;/code&gt; (an illustrative value).&lt;/p&gt;

&lt;p&gt;Passing these integers directly into a neural network is problematic, because numerical values imply an ordinal relationship that does not exist. Token &lt;code&gt;41552&lt;/code&gt; is not "greater than" token &lt;code&gt;41551&lt;/code&gt; in any meaningful sense.&lt;/p&gt;

&lt;p&gt;The solution uses two learned lookup tables:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Token embeddings (&lt;code&gt;W_e&lt;/code&gt;)&lt;/strong&gt; map each token ID to a continuous vector of dimension &lt;code&gt;d_model&lt;/code&gt;. Through backpropagation, tokens that appear in similar linguistic contexts converge toward similar regions of the vector space.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Positional embeddings (&lt;code&gt;W_p&lt;/code&gt;)&lt;/strong&gt; encode word order. Transformers process all tokens in parallel rather than sequentially, so without explicit position information, "dog bites man" and "man bites dog" would be indistinguishable to self-attention. Each position index &lt;code&gt;0, 1, ..., T-1&lt;/code&gt; is therefore mapped to a learned vector of dimension &lt;code&gt;d_model&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  PyTorch Implementation
&lt;/h2&gt;

&lt;p&gt;The module below combines token embeddings with learned positional embeddings, following the architecture used in GPT-2.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;torch.nn&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;nn&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TransformerEmbeddings&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Module&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;vocab_size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;d_model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;max_seq_len&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="nf"&gt;super&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="c1"&gt;# Token lookup matrix: shape (vocab_size, d_model)
&lt;/span&gt;        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;token_embeddings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;nn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Embedding&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;vocab_size&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;d_model&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;# Position lookup matrix: shape (max_seq_len, d_model)
&lt;/span&gt;        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;position_embeddings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;nn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Embedding&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max_seq_len&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;d_model&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;forward&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;input_ids&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Tensor&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Tensor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;# Expected input shape: (batch_size, seq_len)
&lt;/span&gt;        &lt;span class="n"&gt;batch_size&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;seq_len&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;input_ids&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shape&lt;/span&gt;

        &lt;span class="c1"&gt;# Guard clause: protect against context window overflow
&lt;/span&gt;        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;seq_len&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;position_embeddings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;num_embeddings&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Sequence length (&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;seq_len&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;) exceeds maximum context window &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
                &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;(&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;position_embeddings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;num_embeddings&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;# Generate positional indices [0, 1, ..., seq_len - 1]
&lt;/span&gt;        &lt;span class="c1"&gt;# Shape: (1, seq_len), placed on the same device as the input
&lt;/span&gt;        &lt;span class="n"&gt;positions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;arange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;seq_len&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;device&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;input_ids&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;device&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;unsqueeze&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;# Retrieve representations
&lt;/span&gt;        &lt;span class="n"&gt;tok_emb&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;token_embeddings&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input_ids&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;      &lt;span class="c1"&gt;# (batch_size, seq_len, d_model)
&lt;/span&gt;        &lt;span class="n"&gt;pos_emb&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;position_embeddings&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;positions&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;# (1, seq_len, d_model)
&lt;/span&gt;
        &lt;span class="c1"&gt;# Element-wise addition with broadcasting across the batch dimension
&lt;/span&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;tok_emb&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;pos_emb&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Key Implementation Details
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. How &lt;code&gt;nn.Embedding&lt;/code&gt; Works
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;nn.Embedding&lt;/code&gt; is a trainable weight matrix:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;W ∈ R^(vocab_size × d_model)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It is mathematically equivalent to multiplying a one-hot vector by this matrix (&lt;code&gt;x_one_hot @ W&lt;/code&gt;), but it is implemented as a direct row lookup. This avoids constructing large, sparse one-hot tensors and removes the associated memory and compute overhead.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Device Placement and Broadcasting
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Device matching.&lt;/strong&gt; Passing &lt;code&gt;device=input_ids.device&lt;/code&gt; ensures that the position indices are created on the same device (CPU, CUDA, or Apple Silicon MPS) as the input. Omitting it defaults to the CPU and raises a device mismatch error when training on a GPU.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Broadcasting.&lt;/strong&gt; The positional embedding tensor has shape &lt;code&gt;(1, seq_len, d_model)&lt;/code&gt;, while &lt;code&gt;tok_emb&lt;/code&gt; has shape &lt;code&gt;(batch_size, seq_len, d_model)&lt;/code&gt;. When the two are added, PyTorch broadcasts the position vectors across every sequence in the batch.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3. Why Addition Rather Than Concatenation
&lt;/h3&gt;

&lt;p&gt;Concatenating the two vectors would widen each token representation to &lt;code&gt;2 × d_model&lt;/code&gt;, increasing the parameter count and memory usage of every subsequent linear projection.&lt;/p&gt;

&lt;p&gt;Element-wise addition preserves the channel dimension at &lt;code&gt;d_model&lt;/code&gt;. Conceptually, the positional embedding acts as an offset applied to the token vector, allowing the model to distinguish the same token at different positions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Next Steps
&lt;/h2&gt;

&lt;p&gt;The combined embeddings leave this layer with shape &lt;code&gt;(batch_size, seq_len, d_model)&lt;/code&gt; and feed directly into the first Transformer block. The next article in this series covers the attention mechanism.&lt;/p&gt;

</description>
      <category>python</category>
      <category>pytorch</category>
      <category>machinelearning</category>
      <category>deeplearning</category>
    </item>
    <item>
      <title>model.train() vs model.eval() vs torch.no_grad() in PyTorch: What Each One Changes</title>
      <dc:creator>Wesam Khallaf — Author of PyTorch From Ground Up</dc:creator>
      <pubDate>Sat, 10 Oct 2026 07:43:54 +0000</pubDate>
      <link>https://dev.to/pytorchfromgroundup/modeltrain-vs-modeleval-vs-torchnograd-in-pytorch-what-each-one-changes-3op3</link>
      <guid>https://dev.to/pytorchfromgroundup/modeltrain-vs-modeleval-vs-torchnograd-in-pytorch-what-each-one-changes-3op3</guid>
      <description>&lt;p&gt;PyTorch has three calls that are used around evaluation: &lt;code&gt;model.train()&lt;/code&gt;, &lt;code&gt;model.eval()&lt;/code&gt; and &lt;code&gt;torch.no_grad()&lt;/code&gt;. They control two separate things. &lt;code&gt;train()&lt;/code&gt; and &lt;code&gt;eval()&lt;/code&gt; set a mode flag that changes how some layers compute their output. &lt;code&gt;torch.no_grad()&lt;/code&gt; controls whether autograd records the computation. This article explains each one, shows which layers are affected, and gives the standard train-and-evaluate loop.&lt;/p&gt;

&lt;h2&gt;
  
  
  The training flag
&lt;/h2&gt;

&lt;p&gt;Every &lt;code&gt;nn.Module&lt;/code&gt; has a boolean attribute &lt;code&gt;training&lt;/code&gt;. &lt;code&gt;model.train()&lt;/code&gt; sets it to &lt;code&gt;True&lt;/code&gt; on the model and on every sub-module. &lt;code&gt;model.eval()&lt;/code&gt; sets it to &lt;code&gt;False&lt;/code&gt;. A new model starts in training mode.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;torch.nn&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;nn&lt;/span&gt;

&lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;manual_seed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;nn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Sequential&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;nn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Linear&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;nn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;BatchNorm1d&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;nn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ReLU&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="n"&gt;nn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Dropout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;nn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Linear&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;training&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;eval&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;training&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;train&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;training&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;True
[False, False, False, False, False]
[True, True, True, True, True]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;model.eval()&lt;/code&gt; is the same call as &lt;code&gt;model.train(False)&lt;/code&gt;. Both return the model itself, so &lt;code&gt;model = model.eval()&lt;/code&gt; and &lt;code&gt;model.eval()&lt;/code&gt; do the same thing.&lt;/p&gt;

&lt;p&gt;Setting the flag does nothing by itself. It only matters for layers that read it in their &lt;code&gt;forward()&lt;/code&gt;. In the standard layer set, these are mainly the dropout layers and the normalization layers that keep running statistics (&lt;code&gt;BatchNorm1d&lt;/code&gt;, &lt;code&gt;BatchNorm2d&lt;/code&gt;, &lt;code&gt;BatchNorm3d&lt;/code&gt;). &lt;code&gt;nn.Linear&lt;/code&gt;, &lt;code&gt;nn.Conv2d&lt;/code&gt;, &lt;code&gt;nn.ReLU&lt;/code&gt;, &lt;code&gt;nn.LayerNorm&lt;/code&gt; and most other layers compute the same output in both modes.&lt;/p&gt;

&lt;p&gt;A model made only of &lt;code&gt;Linear&lt;/code&gt; and &lt;code&gt;ReLU&lt;/code&gt; layers gives identical outputs in both modes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;plain&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;nn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Sequential&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Linear&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;nn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ReLU&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;nn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Linear&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;randn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;plain&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;train&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;plain&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;plain&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;eval&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;  &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;plain&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;True
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Dropout in each mode
&lt;/h2&gt;

&lt;p&gt;In training mode, &lt;code&gt;nn.Dropout(p)&lt;/code&gt; sets each element of its input to zero with probability &lt;code&gt;p&lt;/code&gt;, and multiplies the remaining elements by &lt;code&gt;1 / (1 - p)&lt;/code&gt;. In evaluation mode it returns its input unchanged.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;drop&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;nn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Dropout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;ones&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ones&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;drop&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;train&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;drop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ones&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;drop&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;eval&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;  &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;drop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ones&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;tensor([0., 0., 2., 0., 0., 0., 2., 2., 0., 2.])
tensor([1., 1., 1., 1., 1., 1., 1., 1., 1., 1.])
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With &lt;code&gt;p = 0.5&lt;/code&gt;, the surviving elements are multiplied by 2. This keeps the expected value of each element the same in both modes. Over one million ones, the mean of the training-mode output is 1.0004, and the mean of the evaluation-mode output is exactly 1.0. Because of this scaling, the layers after dropout receive inputs of the same average size in training and in evaluation, so no extra correction is needed at test time.&lt;/p&gt;

&lt;p&gt;Since the dropped elements are chosen at random on every call, the same input gives a different output on every forward pass in training mode. For a small model with one &lt;code&gt;Dropout(0.5)&lt;/code&gt; layer, ten forward passes of one input give:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;train mode: 10 different outputs in 10 passes
eval mode:   1 different output  in 10 passes
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  BatchNorm in each mode
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;nn.BatchNorm1d&lt;/code&gt; normalizes each feature to mean 0 and variance 1, then applies a learned scale and shift. The difference between the modes is where the mean and variance come from.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Training mode:&lt;/strong&gt; the mean and variance are computed from the current batch. The layer also updates two stored buffers, &lt;code&gt;running_mean&lt;/code&gt; and &lt;code&gt;running_var&lt;/code&gt;, as a moving average of the batch statistics.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Evaluation mode:&lt;/strong&gt; the layer uses the stored &lt;code&gt;running_mean&lt;/code&gt; and &lt;code&gt;running_var&lt;/code&gt;, and does not change them.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The buffers start at mean 0 and variance 1. One forward pass in training mode moves them 10% of the way towards the batch statistics (the default &lt;code&gt;momentum=0.1&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;manual_seed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;bn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;nn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;BatchNorm1d&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;randn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;          &lt;span class="c1"&gt;# features with mean about 5, std about 2
&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mean&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;bn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;train&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;bn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;running_mean&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;running_var&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;tensor([5.0390, 4.4964, 5.5362])
tensor([0.5039, 0.4496, 0.5536])
tensor([1.2227, 1.2167, 1.3412])
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;0.5039&lt;/code&gt; is &lt;code&gt;0.9 * 0 + 0.1 * 5.0390&lt;/code&gt;. The running statistics are updated by the forward pass. No &lt;code&gt;backward()&lt;/code&gt; call or optimizer step is involved.&lt;/p&gt;

&lt;h3&gt;
  
  
  In training mode, one sample's output depends on the rest of the batch
&lt;/h3&gt;

&lt;p&gt;Because training mode normalizes with the batch's own statistics, the output for a sample changes when the other samples in the batch change. Below, the same sample &lt;code&gt;s&lt;/code&gt; is passed in two different batches, after the layer has seen 200 training batches:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;train mode, s in batch A:  [ 0.4099, -0.3283,  0.1971]
train mode, s in batch B:  [-1.6630, -1.8497, -1.7948]
eval mode,  s in batch A:  [-0.1599, -0.2482, -0.1628]
eval mode,  s in batch B:  [-0.1599, -0.2482, -0.1628]
eval mode,  s alone:       [-0.1599, -0.2482, -0.1628]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In evaluation mode the output for &lt;code&gt;s&lt;/code&gt; is the same in any batch and on its own, because the statistics are fixed.&lt;/p&gt;

&lt;h3&gt;
  
  
  A batch of one sample in training mode
&lt;/h3&gt;

&lt;p&gt;A batch of one sample has no variance to compute, so &lt;code&gt;BatchNorm1d&lt;/code&gt; refuses it in training mode:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;bn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;train&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="nf"&gt;bn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;tensor&lt;/span&gt;&lt;span class="p"&gt;([[&lt;/span&gt;&lt;span class="mf"&gt;5.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;5.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;5.0&lt;/span&gt;&lt;span class="p"&gt;]]))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ValueError: Expected more than 1 value per channel when training, got input size torch.Size([1, 3])
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This error usually appears when a single prediction is made on a model that is still in training mode, or when the last batch of an epoch has one sample. In the first case, the fix is &lt;code&gt;model.eval()&lt;/code&gt;. In the second, &lt;code&gt;DataLoader(..., drop_last=True)&lt;/code&gt; drops the incomplete final batch.&lt;/p&gt;

&lt;h2&gt;
  
  
  What happens when evaluation runs in training mode
&lt;/h2&gt;

&lt;p&gt;The effect of a missing &lt;code&gt;model.eval()&lt;/code&gt; can be measured directly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A model with Dropout.&lt;/strong&gt; This is a binary classifier with one &lt;code&gt;Dropout(0.3)&lt;/code&gt; layer, trained for 20 epochs on 480 samples and evaluated on 120:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;manual_seed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;randn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;600&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;[:,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;[:,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;long&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;nn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Sequential&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;nn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Linear&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;nn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ReLU&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;nn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Dropout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.3&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;nn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Linear&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After training, the same validation set was evaluated once in evaluation mode and five times in training mode:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;eval mode:   val loss 0.0462   accuracy 0.992
train mode:  val loss 0.0520   accuracy 0.967
train mode:  val loss 0.0772   accuracy 0.967
train mode:  val loss 0.0728   accuracy 0.967
train mode:  val loss 0.0831   accuracy 0.967
train mode:  val loss 0.0520   accuracy 0.983
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In training mode, the measured loss is higher and changes from run to run, because dropout removes a different 30% of the hidden units each time. The weights are the same in all six rows.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A model with BatchNorm.&lt;/strong&gt; This is a 3-class classifier with &lt;code&gt;BatchNorm1d&lt;/code&gt; and &lt;code&gt;Dropout(0.3)&lt;/code&gt;, trained for 30 epochs. Its validation accuracy was measured with different validation batch sizes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;validation batch size     train-mode accuracy    eval-mode accuracy
--------------------------------------------------------------------
600                              0.913                 0.932
64                               0.903                 0.932
8                                0.795                 0.932
2                                0.622                 0.932
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In evaluation mode the accuracy does not depend on the batch size. In training mode it falls as the batch gets smaller, because the mean and variance of 2 or 8 samples are poor estimates of the statistics the model was trained with.&lt;/p&gt;

&lt;p&gt;Validating in training mode also changes the model. After one pass over the validation set in training mode, &lt;code&gt;num_batches_tracked&lt;/code&gt; went from 1140 to 1150 and the largest change in &lt;code&gt;running_mean&lt;/code&gt; was 0.1975. The validation data has been mixed into the stored statistics, and these are the statistics the model will use later for real predictions.&lt;/p&gt;

&lt;h2&gt;
  
  
  What torch.no_grad() does
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;model.eval()&lt;/code&gt; changes how layers compute their output. It does not stop autograd. In evaluation mode, the forward pass still builds a computation graph:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;eval&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;out&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;model&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;requires_grad&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;grad_fn&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;no_grad&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;out&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;model&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;requires_grad&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;grad_fn&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;True &amp;lt;AddmmBackward0 object at 0x...&amp;gt;
False None
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To compute gradients later, autograd stores some intermediate results of the forward pass, such as the input to each linear layer and the output of each ReLU. These stored tensors stay in memory until &lt;code&gt;backward()&lt;/code&gt; runs or the output is deleted. Inside &lt;code&gt;torch.no_grad()&lt;/code&gt;, nothing is recorded and nothing is stored.&lt;/p&gt;

&lt;p&gt;For a network with layers &lt;code&gt;1024 -&amp;gt; 4096 -&amp;gt; 4096 -&amp;gt; 10&lt;/code&gt;, the activations stored for backward are:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;batch size     stored with graph     stored under no_grad
---------------------------------------------------------
256                  17.0 MiB              0.0 MiB
4096                272.0 MiB              0.0 MiB
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The stored memory grows with the batch size. On a GPU, this is the memory that a validation loop without &lt;code&gt;no_grad()&lt;/code&gt; uses for no purpose, and it limits how large the validation batch can be.&lt;/p&gt;

&lt;p&gt;A tensor produced under &lt;code&gt;no_grad()&lt;/code&gt; has no graph, so calling &lt;code&gt;backward()&lt;/code&gt; on it raises:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;RuntimeError: element 0 of tensors does not require grad and does not have a grad_fn
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Why both are needed during evaluation
&lt;/h2&gt;

&lt;p&gt;The two calls do not overlap. &lt;code&gt;no_grad()&lt;/code&gt; does not switch layers to evaluation mode. A model in training mode, run under &lt;code&gt;no_grad()&lt;/code&gt;, still applies dropout:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;train&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;no_grad&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;o1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;o2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;model&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nf"&gt;model&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;allclose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;o1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;o2&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;False
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;no_grad()&lt;/code&gt; also does not stop BatchNorm from updating its running statistics. Those updates happen in the forward pass and depend only on the training flag:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;bn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;train&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;no_grad&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;bn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;          &lt;span class="c1"&gt;# running_mean still changes
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And &lt;code&gt;eval()&lt;/code&gt; does not stop the graph from being built, as shown above. The two calls are separate because there are cases where only one is wanted. For example, some interpretability methods compute gradients with respect to the input of a model in evaluation mode, which needs &lt;code&gt;eval()&lt;/code&gt; without &lt;code&gt;no_grad()&lt;/code&gt;.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Call&lt;/th&gt;
&lt;th&gt;What it changes&lt;/th&gt;
&lt;th&gt;What it does not change&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;model.train()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;sets &lt;code&gt;training=True&lt;/code&gt;: dropout active, BatchNorm uses batch statistics and updates running statistics&lt;/td&gt;
&lt;td&gt;gradient recording&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;model.eval()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;sets &lt;code&gt;training=False&lt;/code&gt;: dropout off, BatchNorm uses stored running statistics&lt;/td&gt;
&lt;td&gt;gradient recording&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;torch.no_grad()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;no graph is built, no activations are stored&lt;/td&gt;
&lt;td&gt;layer behaviour, BatchNorm running statistics&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  torch.inference_mode()
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;torch.inference_mode()&lt;/code&gt; is a stricter version of &lt;code&gt;no_grad()&lt;/code&gt;. It also skips some internal bookkeeping (version counters and view tracking), so it can be slightly faster. The cost is that tensors created inside it can never be used in a computation that autograd records:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;eval&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;inference_mode&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;out&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;model&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;                       &lt;span class="c1"&gt;# shape (4, 3)
&lt;/span&gt;
&lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;randn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;requires_grad&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;out&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;backward&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;RuntimeError: Inference tensors cannot be saved for backward. Please do not use Tensors created in inference mode in computation tracked by autograd. ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use &lt;code&gt;inference_mode()&lt;/code&gt; for prediction code whose outputs are not used for training. Use &lt;code&gt;no_grad()&lt;/code&gt; in a validation loop or anywhere the results may be used in a later computation that needs gradients. Like &lt;code&gt;no_grad()&lt;/code&gt;, &lt;code&gt;inference_mode()&lt;/code&gt; does not change the training flag, so &lt;code&gt;model.eval()&lt;/code&gt; is still required.&lt;/p&gt;

&lt;h2&gt;
  
  
  The train-and-evaluate loop
&lt;/h2&gt;

&lt;p&gt;The training phase starts with &lt;code&gt;model.train()&lt;/code&gt;. The validation phase starts with &lt;code&gt;model.eval()&lt;/code&gt; and runs inside &lt;code&gt;torch.no_grad()&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;epoch&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;num_epochs&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;

    &lt;span class="c1"&gt;# training phase
&lt;/span&gt;    &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;train&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;train_loss&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;bx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;by&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;train_loader&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;pred&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;model&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;loss&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;loss_fn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pred&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;by&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;optimizer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;zero_grad&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;loss&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;backward&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;optimizer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;step&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;train_loss&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;loss&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;item&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;bx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;size&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# validation phase
&lt;/span&gt;    &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;eval&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;val_loss&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;no_grad&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;bx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;by&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;val_loader&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;pred&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;model&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;val_loss&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nf"&gt;loss_fn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pred&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;by&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;item&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;bx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;size&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;train_loss&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;train_loader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dataset&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;val_loss&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;val_loader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dataset&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Epoch &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;epoch&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;  Train: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;  Val: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;model.train()&lt;/code&gt; is at the top of every epoch, not once before the loop. The validation phase leaves the model in evaluation mode, so without this line every epoch after the first would train with dropout off and with BatchNorm frozen at its running statistics.&lt;/p&gt;

&lt;p&gt;On the Dropout example above, this loop gives:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Epoch  0  Train: 0.6110  Val: 0.4610
Epoch  5  Train: 0.1028  Val: 0.0940
Epoch 10  Train: 0.0489  Val: 0.0609
Epoch 15  Train: 0.0390  Val: 0.0574
Epoch 19  Train: 0.0367  Val: 0.0462
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The training loss is computed in training mode, with dropout active, while the validation loss is computed in evaluation mode. The training loss is also averaged over the whole epoch, while the weights are still improving, and the validation loss is measured at the end of the epoch. Both reasons explain why the validation loss can be lower than the training loss.&lt;/p&gt;

&lt;p&gt;For predictions after training, the pattern is the same as the validation phase:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;eval&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;no_grad&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;logits&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;model&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;new_x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;preds&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;logits&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;argmax&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dim&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;model.train()&lt;/code&gt; and &lt;code&gt;model.eval()&lt;/code&gt; set the &lt;code&gt;training&lt;/code&gt; flag on the model and all its sub-modules. The flag only affects layers that read it, mainly Dropout and BatchNorm.&lt;/li&gt;
&lt;li&gt;Dropout in training mode zeroes elements with probability &lt;code&gt;p&lt;/code&gt; and scales the rest by &lt;code&gt;1 / (1 - p)&lt;/code&gt;. In evaluation mode it does nothing.&lt;/li&gt;
&lt;li&gt;BatchNorm in training mode uses the current batch's statistics and updates &lt;code&gt;running_mean&lt;/code&gt; and &lt;code&gt;running_var&lt;/code&gt;. In evaluation mode it uses the stored values and does not change them.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;torch.no_grad()&lt;/code&gt; stops autograd from building a graph and storing activations. It does not change layer behaviour and does not stop BatchNorm's running statistics from updating.&lt;/li&gt;
&lt;li&gt;Evaluation needs both: &lt;code&gt;model.eval()&lt;/code&gt; and &lt;code&gt;torch.no_grad()&lt;/code&gt;. Call &lt;code&gt;model.train()&lt;/code&gt; at the start of every training epoch.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;torch.inference_mode()&lt;/code&gt; is a stricter &lt;code&gt;no_grad()&lt;/code&gt; for prediction code whose outputs will not be used with autograd.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;This is one chapter's worth of an idea from my book,&lt;/em&gt; &lt;strong&gt;PyTorch From Ground Up&lt;/strong&gt;, &lt;em&gt;which builds everything from tensors upward so nothing stays vague. If it helped: &lt;a href="https://github.com/pytorch-from-ground-up/book_code/blob/main/PyTorch-From-Ground-Up-8-Free-Chapters.pdf" rel="noopener noreferrer"&gt;8 chapters are free, no email required&lt;/a&gt;, there's a &lt;a href="https://payhip.com/b/7ukxh" rel="noopener noreferrer"&gt;free one-page tensor cheat-sheet here&lt;/a&gt;, every example runs in the &lt;a href="https://github.com/pytorch-from-ground-up/book_code" rel="noopener noreferrer"&gt;companion notebooks on GitHub&lt;/a&gt;, and the full book is on &lt;a href="https://leanpub.com/pytorchfromgroundup" rel="noopener noreferrer"&gt;Leanpub&lt;/a&gt; or in &lt;a href="https://www.amazon.com/dp/B0H8WMCV33" rel="noopener noreferrer"&gt;paperback and Kindle on Amazon&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  More in this series
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;How Training Actually Works&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/pytorchfromgroundup/pytorch-autograd-explained-what-backward-actually-does-4acg"&gt;PyTorch Autograd Explained: What &lt;code&gt;.backward()&lt;/code&gt; Actually Does&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/pytorchfromgroundup/backpropagation-by-hand-two-layers-a-pen-and-then-autograd-agrees-13i6"&gt;Backpropagation by Hand: Two Layers, a Pen, and Then Autograd Agrees&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/pytorchfromgroundup/what-optimizerstep-actually-does-one-step-of-sgd-momentum-and-adam-by-hand-44fe"&gt;What &lt;code&gt;optimizer.step()&lt;/code&gt; Actually Does: SGD, Momentum and Adam by Hand&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/pytorchfromgroundup/what-your-loss-function-actually-tells-the-model-mse-cross-entropy-and-the-softmax-bug-that-fan"&gt;What Your Loss Function Actually Tells the Model&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/pytorchfromgroundup/nnmodule-explained-the-same-model-built-with-raw-tensors-and-with-nnmodule-1ccl"&gt;nn.Module Explained: The Same Model Built with Raw Tensors and with nn.Module&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Shape mechanics&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/pytorchfromgroundup/reshape-vs-view-in-pytorch-whats-the-difference-and-when-view-breaks-4a9o"&gt;Reshape vs View in PyTorch&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/pytorchfromgroundup/pytorch-broadcasting-explained-the-3-rules-and-the-silent-bug-that-bites-everyone-3606"&gt;PyTorch Broadcasting Explained&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/pytorchfromgroundup/what-does-unsqueeze-do-in-pytorch-and-why-your-model-keeps-asking-for-it-181a"&gt;What Does &lt;code&gt;unsqueeze&lt;/code&gt; Do in PyTorch?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/pytorchfromgroundup/what-does-keepdim-do-in-pytorch-and-the-silent-bug-when-you-forget-it-17p1"&gt;What Does &lt;code&gt;keepdim&lt;/code&gt; Do in PyTorch?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/pytorchfromgroundup/pytorch-permute-vs-transpose-whats-the-difference-and-the-reshape-bug-that-scrambles-your-7ke"&gt;PyTorch &lt;code&gt;permute&lt;/code&gt; vs &lt;code&gt;transpose&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Next in this series: saving and loading a model with &lt;code&gt;state_dict()&lt;/code&gt; and checkpoints.&lt;/p&gt;

</description>
      <category>deeplearning</category>
      <category>pytorch</category>
      <category>machinelearning</category>
      <category>beginners</category>
    </item>
    <item>
      <title>How to make GPU training survive spot preemption (without babysitting it)</title>
      <dc:creator>Viswaretas Kotra</dc:creator>
      <pubDate>Fri, 09 Oct 2026 01:59:34 +0000</pubDate>
      <link>https://dev.to/viswaretas_kotra/how-to-make-gpu-training-survive-spot-preemption-without-babysitting-it-44k6</link>
      <guid>https://dev.to/viswaretas_kotra/how-to-make-gpu-training-survive-spot-preemption-without-babysitting-it-44k6</guid>
      <description>&lt;p&gt;&lt;strong&gt;Short answer:&lt;/strong&gt; save your model, optimizer and step counter to one directory, write those files atomically, load them on startup, and run on a platform that copies that directory before the machine disappears and restores it on the next machine. Do that and a preemption costs you minutes of work, not the whole run.&lt;/p&gt;

&lt;p&gt;Spot and interruptible GPUs are often a fraction of on-demand prices. The catch is that the provider can take the machine back. Here is the pattern we use at &lt;a href="https://www.nodus-compute.ai/" rel="noopener noreferrer"&gt;Nodus&lt;/a&gt;, and it works anywhere.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Put all recovery state in one directory
&lt;/h2&gt;

&lt;p&gt;Everything you need to continue goes in one place: weights, optimizer state, LR scheduler, RNG state, data loader position, current step. Results you want to download (final model, eval reports) go somewhere else. Mixing the two makes checkpoints big and slow.&lt;/p&gt;

&lt;p&gt;On Nodus that directory is &lt;code&gt;/nodus/state&lt;/code&gt; (also in &lt;code&gt;$NODUS_STATE_DIR&lt;/code&gt;), and outputs go to &lt;code&gt;/nodus/outputs&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Write atomically
&lt;/h2&gt;

&lt;p&gt;A checkpoint taken while you are halfway through writing a file restores a broken state. Write to a temp file, then rename it over the old one. Rename is atomic on POSIX filesystems.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pathlib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;

&lt;span class="n"&gt;STATE_DIR&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;NODUS_STATE_DIR&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/nodus/state&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;STATE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;STATE_DIR&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;progress.json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;step&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;opt&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;STATE_DIR&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mkdir&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;parents&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;exist_ok&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;tmp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;STATE_DIR&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ckpt.pt.tmp&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;model&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;state_dict&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;opt&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;opt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;state_dict&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;step&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;step&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="n"&gt;tmp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tmp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;STATE_DIR&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ckpt.pt&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  3. Resume on startup
&lt;/h2&gt;

&lt;p&gt;Checkpoints restore files, not process memory. Your program starts from the top, so it has to check for saved state:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="n"&gt;ckpt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;STATE_DIR&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ckpt.pt&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;ckpt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exists&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ckpt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load_state_dict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;model&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt; &lt;span class="n"&gt;opt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load_state_dict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;opt&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;step&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;resumed from step &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;step&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;total_steps&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="bp"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  4. Pick a checkpoint cadence
&lt;/h2&gt;

&lt;p&gt;Too often and you waste GPU time writing files. Too rarely and each preemption throws away a lot of work. A practical rule: aim for at least four checkpoints per expected run, and keep checkpointing under about 10% of runtime. Capacity that is rarely interrupted can be checkpointed less often.&lt;/p&gt;

&lt;p&gt;Nodus does this math for you with &lt;code&gt;interval: auto&lt;/code&gt;, using how often that capacity actually gets interrupted and how long your saves take.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Save when the machine is about to go away
&lt;/h2&gt;

&lt;p&gt;Most providers give a short reclaim notice. Use it. On Nodus your program can subscribe to checkpoint requests over a local socket and acknowledge when files are consistent:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;nodus&lt;/span&gt;  &lt;span class="c1"&gt;# pip install nodus-compute; no-ops outside Nodus
&lt;/span&gt;
&lt;span class="n"&gt;nodus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;checkpoint&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on_request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;step&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;opt&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When the capacity gives notice, Nodus sends an urgent request, takes the checkpoint after your ack, and prepares a replacement machine at the same time. The replacement only starts once the old one is provably gone, so two machines never write the same state.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Hugging Face Trainer users
&lt;/h2&gt;

&lt;p&gt;Trainer already saves and resumes. Point &lt;code&gt;output_dir&lt;/code&gt; at the state directory, set &lt;code&gt;save_steps&lt;/code&gt; and &lt;code&gt;save_total_limit=2&lt;/code&gt;, and call &lt;code&gt;trainer.train(resume_from_checkpoint=True)&lt;/code&gt; when &lt;code&gt;NODUS_RESTORED=1&lt;/code&gt;. Or set &lt;code&gt;integration: HFTrainer&lt;/code&gt; and Nodus registers the save-on-request callback for you.&lt;/p&gt;

&lt;h2&gt;
  
  
  Running it
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;nodus-compute
nodus login
nodus run &lt;span class="nt"&gt;--gpu&lt;/span&gt; H100 &lt;span class="nt"&gt;--interruptible&lt;/span&gt; &lt;span class="nt"&gt;--checkpoint&lt;/span&gt; /nodus/state &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;--&lt;/span&gt; python train.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;nodus describe job/&amp;lt;name&amp;gt;&lt;/code&gt; shows each attempt, why it ended (for example &lt;code&gt;Preempted&lt;/code&gt;) and the latest checkpoint. New accounts get a $30 starter grant, so you can test a preemption-safe run without a card.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Does checkpointing restore GPU memory?&lt;/strong&gt; No. It restores files. Your code reloads its own state.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What if a checkpoint is empty?&lt;/strong&gt; An empty state directory never replaces an earlier useful checkpoint, so a crash before the first save cannot erase progress.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How many times will it retry?&lt;/strong&gt; By default up to 8 recoveries (3 for distributed jobs). Two attempts in a row with no progress stops the run with &lt;code&gt;NoProgress&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Does this work for multi-node?&lt;/strong&gt; Yes, in beta. Each rank writes its shard with &lt;code&gt;torch.distributed.checkpoint&lt;/code&gt; and rank 0 commits it.&lt;/p&gt;

&lt;p&gt;Full guide: &lt;a href="https://www.nodus-compute.ai/docs/guides/checkpoints/" rel="noopener noreferrer"&gt;nodus-compute.ai/docs/guides/checkpoints&lt;/a&gt;&lt;/p&gt;

</description>
      <category>machinelearning</category>
      <category>pytorch</category>
      <category>gpu</category>
      <category>mlops</category>
    </item>
    <item>
      <title>Loading a PyTorch pipeline one stage at a time: Pixal3D on a 48 GB Mac</title>
      <dc:creator>Rodion Kazennov</dc:creator>
      <pubDate>Mon, 05 Oct 2026 13:00:36 +0000</pubDate>
      <link>https://dev.to/nefayran/loading-a-pytorch-pipeline-one-stage-at-a-time-pixal3d-on-a-48-gb-mac-55kg</link>
      <guid>https://dev.to/nefayran/loading-a-pytorch-pipeline-one-stage-at-a-time-pixal3d-on-a-48-gb-mac-55kg</guid>
      <description>&lt;p&gt;On 4 October a Pixal3D run on my Mac went into its texture stage, and within ten seconds the process grew from 27 to 44 GB. Available memory fell to 6 % and swap grew by 12 GB, so the guard I run heavy jobs under stopped it. The Mac has 48 GB. Before I had that guard, two Pixal3D runs, one of them next to other heavy work, ended with the Mac rebooting.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://ldyang694.github.io/projects/pixal3d/" rel="noopener noreferrer"&gt;Pixal3D&lt;/a&gt; turns one image into a textured 3D mesh, and &lt;a href="https://github.com/pawel-mazurkiewicz/Pixal3D-mac" rel="noopener noreferrer"&gt;Pixal3D-mac&lt;/a&gt; is its port to Apple Silicon. The &lt;code&gt;1024_cascade&lt;/code&gt; pipeline runs seven models in turn: a flow model and a decoder for the coarse structure, two flow models for the shape at 512 and 1024, a shape decoder, and a flow model and a decoder for the texture. Around them work four image feature extractors, a background remover and a camera estimator. Each stage needs one or two of the seven models, and the port keeps all of them in memory from the first second to the last.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the memory does not come back
&lt;/h2&gt;

&lt;p&gt;On Apple Silicon the CPU and the GPU share one pool of memory. Pixal3D's low-VRAM mode, which is on by default, keeps idle models on the CPU and copies the active one to the GPU. On a PC with a separate graphics card, that saves video memory. On a Mac the CPU copy and the GPU copy sit in the same RAM, so the mode keeps every model resident and adds a second copy of the active one. The four feature extractors also load the same DINOv3 backbone, four times.&lt;/p&gt;

&lt;h2&gt;
  
  
  Loading one stage at a time
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/nefayran/stageload" rel="noopener noreferrer"&gt;stageload&lt;/a&gt; is a small library I wrote for this. It swaps the pipeline's dictionary of models for one that loads a model the first time a stage asks for it, and that releases every model the next stage does not list. Releasing moves each tensor of the module to PyTorch's &lt;code&gt;meta&lt;/code&gt; device. That frees the storage even while other code still holds a reference to the module, and calling the module afterwards raises an error that names it and the stage that released it, instead of failing somewhere inside PyTorch.&lt;/p&gt;

&lt;p&gt;The port's own &lt;code&gt;run()&lt;/code&gt; executes unchanged. stageload wraps four of the pipeline's methods to learn where each stage begins: background removal, the first image conditioning, the conditioning of each shape and texture pass, and the final decoding. The four feature extractors are built so that they share one backbone.&lt;/p&gt;

&lt;p&gt;One part of this raises no error when it goes wrong. Pixal3D seeds once at the start of a run and then draws the noise of every stage from the CPU random generator. Building a model runs its random initialisation, which draws from that same generator. A model built lazily in the middle of the run would therefore change the noise of every stage after it, and with it the mesh. stageload saves the generators' state before each load and restores it afterwards: torch's CPU, MPS and CUDA generators, Python's &lt;code&gt;random&lt;/code&gt; and NumPy's global one. On a stand-in pipeline that seeds and draws noise the way the port does, a test checks that staged and eager runs produce the same latents bit for bit, and a control that turns the restore off sees them drift apart.&lt;/p&gt;

&lt;h2&gt;
  
  
  Not only Pixal3D
&lt;/h2&gt;

&lt;p&gt;Nothing in the core of stageload knows about Pixal3D. A pipeline needs three things from it: a function that builds each model on its own, a list of the models each stage uses, and a call where each stage begins. In code you own, that call is one line, &lt;code&gt;models.enter("decode")&lt;/code&gt;. In someone else's code, like this port, &lt;code&gt;on_call&lt;/code&gt; attaches it to a method that already runs at that point, so the pipeline itself stays as it is. A text-to-image pipeline has the same shape as Pixal3D: a text encoder, a denoiser and an image decoder run one after another. The memory meter and the guard work for any program on a Mac. Pixal3D is the only pipeline I have measured so far; the README shows the core on a small two-model pipeline, and that example runs as a test.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it changed
&lt;/h2&gt;

&lt;p&gt;On this M5 Pro with 48 GB, I ran the port's example image through &lt;code&gt;1024_cascade&lt;/code&gt; with seed 7 and 2048-pixel textures, two runs per mode, each in its own process under the guard. "Eager" is the port's own loading and "staged" is stageload. Eager runs do not get through the texture stage on this machine within the guard's limits, so the two eager runs stop where it begins. The texture number for eager comes from the run on 4 October that went into it.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;stage&lt;/th&gt;
&lt;th&gt;eager, peak footprint&lt;/th&gt;
&lt;th&gt;staged, peak footprint&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;setup&lt;/td&gt;
&lt;td&gt;20.4 GB&lt;/td&gt;
&lt;td&gt;2.9 GB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;camera&lt;/td&gt;
&lt;td&gt;23.2 GB&lt;/td&gt;
&lt;td&gt;6.7 GB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;structure&lt;/td&gt;
&lt;td&gt;22.2 GB&lt;/td&gt;
&lt;td&gt;10.9 GB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;shape_512&lt;/td&gt;
&lt;td&gt;26.6 GB&lt;/td&gt;
&lt;td&gt;11.4 to 11.5 GB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;shape_1024&lt;/td&gt;
&lt;td&gt;30.5 GB&lt;/td&gt;
&lt;td&gt;13.1 GB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;texture&lt;/td&gt;
&lt;td&gt;stopped by the guard at 44.2 GB&lt;/td&gt;
&lt;td&gt;30.1 GB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;decode&lt;/td&gt;
&lt;td&gt;not reached&lt;/td&gt;
&lt;td&gt;17.9 to 18.4 GB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;export&lt;/td&gt;
&lt;td&gt;not reached&lt;/td&gt;
&lt;td&gt;69.6 to 70.2 GB&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7gfcwe4zro09xodg5cna.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7gfcwe4zro09xodg5cna.png" alt="Peak footprint per stage of Pixal3D on a 48 GB Mac, with the port's own loading and with stageload" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In every stage both modes ran, staged held 11.3 to 17.5 GB less. The eight loads, the seven models plus the shape decoder a second time for decoding, read 13.2 GB in 24 s out of about ten minutes per run.&lt;/p&gt;

&lt;p&gt;Most of the texture stage's 30 GB is working memory: the texture model itself is 2.6 GB. When decoding began, stageload released it together with the feature extractors and emptied the MPS allocator's cache, and within two seconds the footprint was down to 4.5 GB in one run and 8.0 GB in the other.&lt;/p&gt;

&lt;p&gt;The highest staged number, about 70 GB, comes in the export, after every model weight has been released. It is the port's mesh processing (remeshing, UV unwrapping, baking), which stageload does not touch. The footprint is the number Activity Monitor shows, and it counts memory macOS has compressed, so it can be larger than the 48 GB of RAM.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I could not show
&lt;/h2&gt;

&lt;p&gt;The first is whether staged loading changes the result. Every run saved a hash and a copy of each sampler's output. The first latent, the coarse structure, is the same bit for bit in all four runs. From the 512 shape stage on, the port does not repeat itself on MPS even with the same seed: the two eager runs differ by up to 1.77 in the shape latent, and a staged run differs from an eager one by 1.24 to 1.86. So the comparison says any change from staged loading is no larger than the port's own variation, measured on that single pair of eager runs. The texture latent and the final mesh were not compared at all, because eager runs never reach them here.&lt;/p&gt;

&lt;p&gt;Building on the CPU did not help. stageload builds each model directly on the GPU, and that changes one tensor the checkpoint does not cover: the rotary frequencies of the sparse attention, computed when a model is built, come out up to 6e-8 apart. To see whether that explained the differences, I ran a staged pass that built every model on the CPU and moved it to the GPU, the way the port does. Its latents differed from the eager runs by 1.84 and 1.12, the same order as every other pair. Loading took 82 s instead of 24 s, and three stages peaked 2.6 to 3.8 GB higher while two came out lower. I went back to building on the GPU.&lt;/p&gt;

&lt;p&gt;My own summary had a bug that inflated one number. It took each stage's peak from the moment the stage was recorded as started, but releasing the previous stage's models takes up to 1.7 s. One memory sample in that gap put the decode peak of the CPU-built run at 29.5 GB; counted from the end of the releases, it is 16.7. A code review caught it. A stage's window now begins when the releases its start set off have finished.&lt;/p&gt;

&lt;h2&gt;
  
  
  The guard
&lt;/h2&gt;

&lt;p&gt;The runs went through a second part of stageload, &lt;code&gt;stageload guard&lt;/code&gt;, because other heavy jobs run on this Mac too. It starts one command only when enough memory is available and no process matching a list of heavy jobs has been seen for a while. While the command runs, it stops the command's whole process group if swap grows past a budget or available memory stays under 10 %. It needs no root, and the only processes it ever stops are the ones it started.&lt;/p&gt;

&lt;p&gt;The wait for quiet came from a failure. A chain of video jobs leaves short gaps between them, a run started in one of those gaps, and the next video job took swap past the budget within 20 s. In the bench every run started only after three minutes with no heavy job in sight.&lt;/p&gt;

&lt;h2&gt;
  
  
  On another image
&lt;/h2&gt;

&lt;p&gt;The same staged run on a character from a game I am making took 416 s and loaded the same 13.2 GB in 37 s. Its texture stage peaked at 30.1 GB, as on the example image. Here is the input and the mesh it produced, rendered in Blender:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fe93d8g09eoginig1n57m.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fe93d8g09eoginig1n57m.png" alt="The input image of a moss-covered character and the textured mesh Pixal3D made from it with stageload, rendered in Blender" width="800" height="610"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The code, the traces of every run and the scripts that drew these charts are in &lt;a href="https://github.com/nefayran/stageload" rel="noopener noreferrer"&gt;the repository&lt;/a&gt;. The Pixal3D adapter is tested against one commit of Pixal3D-mac.&lt;/p&gt;

</description>
      <category>pytorch</category>
      <category>python</category>
      <category>machinelearning</category>
      <category>macos</category>
    </item>
  </channel>
</rss>
