<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Autograd on KbWen Blog</title>
    <link>https://www.kbwen.com/tags/autograd/</link>
    <description>Articles and notes on AI systems, machine learning, Python, data engineering, and software development.</description>
    <generator>Hugo</generator>
    <language>zh-tw</language>
    <image>
      <url>https://www.kbwen.com/images/og-default.png</url>
      <title>KbWen Blog</title>
      <link>https://www.kbwen.com/</link>
    </image>
    
    <lastBuildDate>Mon, 02 Nov 2026 22:31:33 +0800</lastBuildDate><atom:link href="https://www.kbwen.com/tags/autograd/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>model.eval() and torch.no_grad() in PyTorch</title>
      <link>https://www.kbwen.com/pytorch-model-eval-vs-no-grad/</link>
      <pubDate>Mon, 02 Nov 2026 22:31:33 +0800</pubDate><dc:creator>KbWen</dc:creator>
      <guid>https://www.kbwen.com/pytorch-model-eval-vs-no-grad/</guid>
      <description>We look at what model.eval() and torch.no_grad() each change when a model runs at test time.</description>
      <content:encoded><![CDATA[<p>If the forward pass in an evaluation loop already runs inside <code>with torch.no_grad():</code>, is <code>model.eval()</code> still needed? The two calls often sit next to each other in test-time code, and both have something to do with not training, so one of them can look like it covers the other.</p>
<p>A Dropout layer is a good place to check, since its output depends on the mode. The layer starts out in its default training mode:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">torch</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">torch</span> <span class="kn">import</span> <span class="n">nn</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">torch</span><span class="o">.</span><span class="n">manual_seed</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">drop</span> <span class="o">=</span> <span class="n">nn</span><span class="o">.</span><span class="n">Dropout</span><span class="p">(</span><span class="n">p</span><span class="o">=</span><span class="mf">0.5</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">x</span> <span class="o">=</span> <span class="n">torch</span><span class="o">.</span><span class="n">ones</span><span class="p">(</span><span class="mi">8</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">with</span> <span class="n">torch</span><span class="o">.</span><span class="n">no_grad</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="n">drop</span><span class="p">(</span><span class="n">x</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="n">drop</span><span class="p">(</span><span class="n">x</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">drop</span><span class="o">.</span><span class="n">eval</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="k">with</span> <span class="n">torch</span><span class="o">.</span><span class="n">no_grad</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="n">drop</span><span class="p">(</span><span class="n">x</span><span class="p">))</span>
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">tensor([0., 0., 2., 0., 0., 0., 2., 2.])
</span></span><span class="line"><span class="cl">tensor([0., 2., 2., 2., 2., 0., 2., 2.])
</span></span><span class="line"><span class="cl">tensor([1., 1., 1., 1., 1., 1., 1., 1.])
</span></span></code></pre></div><p>(Python 3.14.3 and PyTorch 2.12.0, on CPU. The seed keeps the zeros in the same places from run to run on this setup, though another PyTorch version or machine may put them elsewhere.)</p>
<p>The first two lines come from the same input, inside the same <code>no_grad</code> block, but they don&rsquo;t match. That is because Dropout picks which values to zero each time it is called: the <a href="https://docs.pytorch.org/docs/stable/generated/torch.nn.Dropout.html"><code>nn.Dropout</code> docs</a> say that during training it &ldquo;randomly zeroes some of the elements of the input tensor with probability&rdquo; p, and that &ldquo;the zeroed elements are chosen independently for each forward call&rdquo;. The values it keeps come out as 2 rather than 1, because during training the outputs are scaled by 1/(1-p), and with p at 0.5 that means doubling.</p>
<p>The third line, after <code>eval()</code>, is all ones, because &ldquo;during evaluation the module simply computes an identity function.&rdquo; The <code>forward</code> method of <code>nn.Dropout</code> in the <a href="https://github.com/pytorch/pytorch/blob/main/torch/nn/modules/dropout.py">PyTorch source</a> passes the mode along to <code>F.dropout</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="k">return</span> <span class="n">F</span><span class="o">.</span><span class="n">dropout</span><span class="p">(</span><span class="nb">input</span><span class="p">,</span> <span class="bp">self</span><span class="o">.</span><span class="n">p</span><span class="p">,</span> <span class="bp">self</span><span class="o">.</span><span class="n">training</span><span class="p">,</span> <span class="bp">self</span><span class="o">.</span><span class="n">inplace</span><span class="p">)</span>
</span></span></code></pre></div><p><code>self.training</code> is the flag that <code>eval()</code> sets to <code>False</code> and <code>train()</code> sets back to <code>True</code>, and BatchNorm is another common layer that reads it. None of those arguments is about gradients, so what does <code>no_grad</code> change? Here a Linear layer goes in front of the Dropout to give autograd a weight to track:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">torch</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">torch</span> <span class="kn">import</span> <span class="n">nn</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">torch</span><span class="o">.</span><span class="n">manual_seed</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">model</span> <span class="o">=</span> <span class="n">nn</span><span class="o">.</span><span class="n">Sequential</span><span class="p">(</span><span class="n">nn</span><span class="o">.</span><span class="n">Linear</span><span class="p">(</span><span class="mi">8</span><span class="p">,</span> <span class="mi">8</span><span class="p">),</span> <span class="n">nn</span><span class="o">.</span><span class="n">Dropout</span><span class="p">(</span><span class="n">p</span><span class="o">=</span><span class="mf">0.5</span><span class="p">))</span>
</span></span><span class="line"><span class="cl"><span class="n">model</span><span class="o">.</span><span class="n">eval</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="n">x</span> <span class="o">=</span> <span class="n">torch</span><span class="o">.</span><span class="n">ones</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="mi">8</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">out</span> <span class="o">=</span> <span class="n">model</span><span class="p">(</span><span class="n">x</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="n">out</span><span class="o">.</span><span class="n">requires_grad</span><span class="p">,</span> <span class="n">out</span><span class="o">.</span><span class="n">grad_fn</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">with</span> <span class="n">torch</span><span class="o">.</span><span class="n">no_grad</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">    <span class="n">out</span> <span class="o">=</span> <span class="n">model</span><span class="p">(</span><span class="n">x</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="n">out</span><span class="o">.</span><span class="n">requires_grad</span><span class="p">,</span> <span class="n">out</span><span class="o">.</span><span class="n">grad_fn</span><span class="p">)</span>
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">True &lt;AddmmBackward0 object at 0x000001877221FD30&gt;
</span></span><span class="line"><span class="cl">False None
</span></span></code></pre></div><p>In eval mode alone, the output still has <code>requires_grad</code> set and a <code>grad_fn</code> from the Linear layer, which means autograd recorded the forward pass so that <code>backward()</code> could be called on it later. Under <code>no_grad</code> the output has neither, even though the model is in the same eval mode. The <a href="https://docs.pytorch.org/docs/stable/notes/autograd.html">autograd notes</a> call <code>eval()</code> &ldquo;completely orthogonal to no-grad mode and inference mode&rdquo;. Because the recording stops, <a href="https://docs.pytorch.org/docs/stable/generated/torch.no_grad.html"><code>torch.no_grad</code></a> also &ldquo;will reduce memory consumption for computations that would otherwise have requires_grad=True.&rdquo;</p>
<p>If a model&rsquo;s test predictions come out different on every call, a missing <code>model.eval()</code> is a good first thing to check. Running out of GPU memory during evaluation, when nothing calls <code>backward()</code>, is a different problem: it is worth checking whether the forward passes are being recorded, and <code>torch.no_grad()</code> turns that recording off. An evaluation loop normally uses both. <code>eval()</code> also stays in effect until something changes it, so the training loop needs <code>model.train()</code> again before the next epoch.</p>
<p>PyTorch also has <code>torch.inference_mode()</code>, which the autograd notes call &ldquo;the extreme version of no-grad mode&rdquo;. They warn that tensors created in it &ldquo;will not be able to be used in computations to be recorded by autograd after exiting inference mode&rdquo;, so whether it can replace <code>no_grad</code> depends on what happens to those tensors after the block ends. The notes suggest trying it for model evaluation and going back to <code>no_grad</code> if the errors can&rsquo;t be avoided, which is probably the easiest way to find out for a given loop.</p>
<p>This post comes from running the two snippets above on CPU and reading the PyTorch docs and source, so take it as a small idea to check against your own loop. If you have run into similar cases, feel free to share them.</p>
]]></content:encoded>
    </item>
    
  </channel>
</rss>
