<?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: Borui Cai</title>
    <description>The latest articles on DEV Community by Borui Cai (@s2thend).</description>
    <link>https://dev.to/s2thend</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4062476%2F543fb78e-fdcd-4b2c-8ed5-ae580c856887.png</url>
      <title>DEV Community: Borui Cai</title>
      <link>https://dev.to/s2thend</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/s2thend"/>
    <language>en</language>
    <item>
      <title>How to Search Cursor Chat History Across Workspaces</title>
      <dc:creator>Borui Cai</dc:creator>
      <pubDate>Wed, 09 Sep 2026 02:08:55 +0000</pubDate>
      <link>https://dev.to/s2thend/how-to-search-cursor-chat-history-across-workspaces-md1</link>
      <guid>https://dev.to/s2thend/how-to-search-cursor-chat-history-across-workspaces-md1</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://extracurricular.ai/en/blog/search-cursor-chat-history/" rel="noopener noreferrer"&gt;Extracurricular AI&lt;/a&gt;.&lt;/em&gt;&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%2Fegj4ousvt0be0s8esowx.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%2Fegj4ousvt0be0s8esowx.png" alt="Editorial illustration: a lens examines conversation pages across project folders." width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You remember fixing the authentication bug. You cannot remember which project contained the conversation.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;cursor-history&lt;/code&gt; searches conversation text in supported local Cursor stores across discovered workspaces. It can read history created before you installed it, without a capture hook, embeddings, or an API key.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start with a phrase you remember
&lt;/h2&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; &lt;span class="nt"&gt;-g&lt;/span&gt; cursor-history
cursor-history list &lt;span class="nt"&gt;--all&lt;/span&gt;
cursor-history search &lt;span class="s2"&gt;"connection pool"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run this on the machine where the history is stored, using Node.js 20.x or 22.x–26.x. The CLI searches parsed message content using case-insensitive text matching. It does not semantically infer that “database saturation” means “connection pool.”&lt;/p&gt;

&lt;p&gt;Try a distinctive substring if a broad query returns too much:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;cursor-history search &lt;span class="s2"&gt;"ECONNRESET"&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; 30
cursor-history search &lt;span class="s2"&gt;"oauth callback"&lt;/span&gt;
cursor-history search &lt;span class="s2"&gt;"refreshToken"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Search returns up to 10 matching sessions by default. &lt;code&gt;-n 30&lt;/code&gt; raises the limit; &lt;code&gt;-n 0&lt;/code&gt; returns all matching sessions. &lt;code&gt;list --all&lt;/code&gt; removes the listing limit; it is not a switch that enables global search. Search covers discovered workspaces by default unless you select a workspace.&lt;/p&gt;

&lt;h2&gt;
  
  
  Open the session that actually matched
&lt;/h2&gt;

&lt;p&gt;For interactive use, copy the session index displayed in the result. If it is &lt;code&gt;12&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;cursor-history show 12
cursor-history &lt;span class="nb"&gt;export &lt;/span&gt;12
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The number refers to the underlying session list within the same data roots and workspace scope. It is not the search result's ordinal position. New sessions can also change list indices, so use exact UUIDs for saved commands.&lt;/p&gt;

&lt;p&gt;With &lt;code&gt;jq&lt;/code&gt; installed, retrieve those UUIDs directly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;cursor-history search &lt;span class="s2"&gt;"authentication"&lt;/span&gt; &lt;span class="nt"&gt;--json&lt;/span&gt;   | jq &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s1"&gt;'.results[].sessionId'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Copy a returned ID into these commands, replacing the placeholder:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;session_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'PASTE_RETURNED_SESSION_UUID'&lt;/span&gt;
cursor-history show &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$session_id&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
cursor-history &lt;span class="nb"&gt;export&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$session_id&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;--format&lt;/span&gt; json &lt;span class="nt"&gt;--output&lt;/span&gt; ./conversation.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The JSON export contains the available session representation. It may include source details and inferred timestamps; it is not proof of a complete original conversation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Narrow the workspace when you know it
&lt;/h2&gt;

&lt;p&gt;Keep the same scope on the follow-up read:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;cursor-history &lt;span class="nt"&gt;--workspace&lt;/span&gt; /absolute/path/to/project search &lt;span class="s2"&gt;"authentication"&lt;/span&gt;
cursor-history &lt;span class="nt"&gt;--workspace&lt;/span&gt; /absolute/path/to/project show 1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here &lt;code&gt;1&lt;/code&gt; means session 1 in that workspace's list. Do not reuse a global index in a workspace-scoped read. A workspace filter also limits which conversation sources may be opened; it is more than a display filter.&lt;/p&gt;

&lt;h2&gt;
  
  
  If you get no useful matches
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Check&lt;/th&gt;
&lt;th&gt;Why it matters&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Try a shorter phrase or exact error token&lt;/td&gt;
&lt;td&gt;Text search does not paraphrase the query&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Run &lt;code&gt;cursor-history list --all --json&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Inspect discovered sessions and any diagnostics&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Check which machine and user account owns the files&lt;/td&gt;
&lt;td&gt;Local search does not fetch SSH, cloud, or another user's history&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Check custom roots or WSL paths&lt;/td&gt;
&lt;td&gt;Composer data and &lt;code&gt;~/.cursor&lt;/code&gt; can live in different environments&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Review source status&lt;/td&gt;
&lt;td&gt;Missing, partial, or ambiguous sources can affect what is searchable&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;For an explicit environment, set both roots to the intended directories:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;CURSOR_DATA_PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'/absolute/path/to/Cursor/User/workspaceStorage'&lt;/span&gt; &lt;span class="nv"&gt;CURSOR_STORE_ROOT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'/absolute/path/to/.cursor'&lt;/span&gt; cursor-history search &lt;span class="s2"&gt;"authentication"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;a href="https://github.com/S2thend/cursor-history#where-cursor-stores-data" rel="noopener noreferrer"&gt;platform guide&lt;/a&gt; explains these selectors. An empty result means no match was returned from the readable selected sources; it does not prove that a chat was deleted.&lt;/p&gt;

&lt;h2&gt;
  
  
  When to use Cursor's own commands
&lt;/h2&gt;

&lt;p&gt;To continue an Agent CLI conversation, start with Cursor's &lt;a href="https://cursor.com/docs/cli/reference/parameters" rel="noopener noreferrer"&gt;native session commands&lt;/a&gt;. This workflow is useful when you need to locate text across local conversations and then inspect or export the result.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://github.com/S2thend/cursor-history-mcp#quick-start" rel="noopener noreferrer"&gt;MCP companion&lt;/a&gt; can let an assistant perform the search. As checked on September 8, 2026, MCP 0.3.1 uses reader 0.18.0. The packages release independently, so update the MCP package when upgrading the server.&lt;/p&gt;

&lt;p&gt;If the search finds something useful, &lt;a href="https://github.com/S2thend/cursor-history" rel="noopener noreferrer"&gt;keep cursor-history on GitHub&lt;/a&gt;. For moved projects, continue with &lt;a href="https://extracurricular.ai/en/blog/recover-moved-cursor-project-chats/" rel="noopener noreferrer"&gt;the recovery guide&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Implementation references: &lt;a href="https://github.com/S2thend/cursor-history/blob/3558ed86842d0beec8660abbb955ec9910720bc1/src/core/storage.ts" rel="noopener noreferrer"&gt;search traversal&lt;/a&gt;, &lt;a href="https://github.com/S2thend/cursor-history/blob/3558ed86842d0beec8660abbb955ec9910720bc1/src/core/parser.ts" rel="noopener noreferrer"&gt;text matcher&lt;/a&gt;, &lt;a href="https://github.com/S2thend/cursor-history/blob/3558ed86842d0beec8660abbb955ec9910720bc1/src/cli/formatters/json.ts" rel="noopener noreferrer"&gt;CLI JSON formatter&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>cursor</category>
      <category>ai</category>
      <category>cli</category>
      <category>productivity</category>
    </item>
    <item>
      <title>How to Rewind Codex Conversations and Files Together</title>
      <dc:creator>Borui Cai</dc:creator>
      <pubDate>Fri, 21 Aug 2026 12:07:53 +0000</pubDate>
      <link>https://dev.to/s2thend/how-to-rewind-codex-conversations-and-files-together-1bpm</link>
      <guid>https://dev.to/s2thend/how-to-rewind-codex-conversations-and-files-together-1bpm</guid>
      <description>&lt;p&gt;Codex can move a conversation back to an earlier prompt. The missing half is the workspace: the old conversation can return while the newer files remain on disk.&lt;/p&gt;

&lt;p&gt;This guide shows how to add synchronized conversation-and-file rewind to Codex CLI now. The first section is the practical setup. The rest is a Rust-level look at the snapshot design: how it covers Git-tracked files, files directly edited by the agent, and a bounded set of recent shell-made changes without taking an unbounded snapshot of the entire workspace.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Disclosure:&lt;/strong&gt; I built &lt;strong&gt;codex-rewind&lt;/strong&gt;, the project used in this article. It is an unofficial distribution of OpenAI Codex CLI and is not affiliated with or supported by OpenAI. I am describing the implementation and its limits, not claiming that it is the best agent for every workflow.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The two-minute setup
&lt;/h2&gt;

&lt;p&gt;Install codex-rewind from npm:&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; &lt;span class="nt"&gt;-g&lt;/span&gt; codex-rewind
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The executable is &lt;code&gt;codexr&lt;/code&gt;, so it can live beside the official &lt;code&gt;codex&lt;/code&gt; command rather than overwriting it. Start a &lt;strong&gt;new&lt;/strong&gt; tracked session with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;codexr &lt;span class="nt"&gt;--enable&lt;/span&gt; file_snapshots
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use Codex normally. When an experiment goes in the wrong direction, run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/rewind    # choose an earlier prompt; restore the conversation and its files
/redo      # undo that rewind and return to the pre-restore state
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important word is &lt;em&gt;together&lt;/em&gt;. Restoring only the files leaves the model with a conversation that describes edits which no longer exist. Restoring only the conversation leaves the model reasoning about an older world while newer files remain on disk. The selected turn is the shared coordinate for both histories.&lt;/p&gt;

&lt;p&gt;To enable tracking for future new sessions, add this to the existing Codex config:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="c"&gt;# ~/.codex/config.toml&lt;/span&gt;
&lt;span class="nn"&gt;[features]&lt;/span&gt;
&lt;span class="py"&gt;file_snapshots&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Tracking is session-scoped. A session starts with snapshot tracking for its whole life, or it has none. Turning the feature on after the mistake cannot create snapshots for earlier turns.&lt;/p&gt;

&lt;h3&gt;
  
  
  A small test outside Git
&lt;/h3&gt;

&lt;p&gt;You can test the behavior in an ordinary directory; no repository is required:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir &lt;/span&gt;rewind-demo
&lt;span class="nb"&gt;cd &lt;/span&gt;rewind-demo
&lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s1"&gt;'an uncommitted idea\n'&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; local-notes.md
codexr &lt;span class="nt"&gt;--enable&lt;/span&gt; file_snapshots
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ask the agent to delete &lt;code&gt;local-notes.md&lt;/code&gt;, then use &lt;code&gt;/rewind&lt;/code&gt; and select the prompt before the deletion. The file and the conversation should return to that point. &lt;code&gt;/redo&lt;/code&gt; should move both forward again.&lt;/p&gt;

&lt;p&gt;That example is deliberately not a Git trick. A useful agent-level undo buffer has to work for an untracked note, a generated document, or a directory that was never a repository.&lt;/p&gt;

&lt;h2&gt;
  
  
  The actual failure mode: two histories drift apart
&lt;/h2&gt;

&lt;p&gt;There are two relevant states in an agent session:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The transcript that determines what the model believes has happened.&lt;/li&gt;
&lt;li&gt;The workspace that contains the effects of what actually happened.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Codex CLI users have asked for a unified restore in open issues &lt;a href="https://github.com/openai/codex/issues/9203" rel="noopener noreferrer"&gt;#9203&lt;/a&gt; and &lt;a href="https://github.com/openai/codex/issues/11626" rel="noopener noreferrer"&gt;#11626&lt;/a&gt;. The latter describes the gap precisely: conversation rewind exists, but the code changes after the selected point remain in the working tree.&lt;/p&gt;

&lt;p&gt;Git is still the right tool for durable, reviewed project history. It is not a complete substitute for a per-turn safety net:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A never-added file has no committed version to recover.&lt;/li&gt;
&lt;li&gt;Agent work increasingly includes notes, documents and data outside repositories.&lt;/li&gt;
&lt;li&gt;Committing or stashing every exploratory turn mixes disposable agent history with intentional project history.&lt;/li&gt;
&lt;li&gt;Shell commands and MCP tools can change files without producing a structured edit record that the model can later replay in reverse.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The target is therefore not “replace Git.” It is “make every restore point carry a coherent conversation state and a reversible workspace state.”&lt;/p&gt;

&lt;h2&gt;
  
  
  Boundary one: own the snapshot storage, not the repository
&lt;/h2&gt;

&lt;p&gt;codex-rewind stores content-addressed blobs, manifests and per-thread references under Codex's own home directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;~/.codex/file_snapshots/
├── blobs/
├── manifests/
└── refs/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The snapshot crate has no dependency on Codex's other crates and does not invoke Git or write to &lt;code&gt;.git&lt;/code&gt;. Restore writes workspace files from its own store. The Git-tracked partition may &lt;strong&gt;read the index&lt;/strong&gt; to obtain project-owned file paths, but snapshot objects, refs, commits and index mutations never enter the user's repository.&lt;/p&gt;

&lt;p&gt;That separation is about ownership and compatibility. Session history belongs to the agent; repository history belongs to the user and their tools.&lt;/p&gt;

&lt;p&gt;It also keeps the official Codex session format untouched. The existing rollout file gets no new fields or event types. A sidecar maps Codex turn IDs to snapshot manifest IDs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Codex rollout:       ... turn_id = "turn-42" ...
Snapshot sidecar:        "turn-42" -&amp;gt; "manifest-b3..."
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As a result, the same conversation can be opened by &lt;code&gt;codex&lt;/code&gt; or &lt;code&gt;codexr&lt;/code&gt; without conversion. That is format compatibility, not magical snapshot coverage: turns run in the official build do not create sidecar snapshots, so reopening that session in &lt;code&gt;codexr&lt;/code&gt; cannot retroactively make those turns file-rewindable.&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%2F0evhdopzctuazrgs5w49.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%2F0evhdopzctuazrgs5w49.png" alt="Codex and codexr use the unchanged session format while only codexr writes an independent snapshot sidecar." width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Boundary two: track a bounded union, not the whole tree
&lt;/h2&gt;

&lt;p&gt;Separating storage from &lt;code&gt;.git&lt;/code&gt; does not answer how many workspace files should be observed. That is a different problem.&lt;/p&gt;

&lt;p&gt;On the repository used to develop this subsystem, a full subtree walk saw 70,609 files and about 116 GB. Most of that was build output, logs and caches that would not normally enter Git. The bounded tracked union was about 6,096 files and 59 MB.&lt;/p&gt;

&lt;p&gt;Those numbers do &lt;strong&gt;not&lt;/strong&gt; argue that Git is bad. They demonstrate why capture scope cannot be “everything below the working directory.” A build can keep inflating that tree even when the actual project has not changed.&lt;/p&gt;

&lt;p&gt;The implementation unions three partitions:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Partition&lt;/th&gt;
&lt;th&gt;What it covers&lt;/th&gt;
&lt;th&gt;What bounds it&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Git-tracked&lt;/td&gt;
&lt;td&gt;Files the project already identifies as source&lt;/td&gt;
&lt;td&gt;The project index&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Agent-touched&lt;/td&gt;
&lt;td&gt;Files directly edited by the agent in this session&lt;/td&gt;
&lt;td&gt;The agent's recorded actions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Recently modified residue&lt;/td&gt;
&lt;td&gt;Shell/MCP changes outside the first two sets&lt;/td&gt;
&lt;td&gt;100 extra files, 16 MiB each, with churn directories skipped&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;In Rust, the ordering is load-bearing. Recent files are selected last so their small budget is not wasted on paths the first two partitions already cover:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;tracked_files&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;roots&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;PathBuf&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;already_known&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;impl&lt;/span&gt; &lt;span class="nb"&gt;IntoIterator&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Item&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PathBuf&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;include_hidden&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;BTreeSet&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;PathBuf&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;ignores&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Gitignore&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;roots&lt;/span&gt;&lt;span class="nf"&gt;.iter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.map&lt;/span&gt;&lt;span class="p"&gt;(|&lt;/span&gt;&lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nf"&gt;load_ignore&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="nf"&gt;.collect&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="c1"&gt;// Partition 1 in this function: files the agent already touched.&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;files&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;BTreeSet&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;PathBuf&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;already_known&lt;/span&gt;&lt;span class="nf"&gt;.into_iter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.collect&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="c1"&gt;// Partition 2: project-owned paths read from the Git index.&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ignore&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;roots&lt;/span&gt;&lt;span class="nf"&gt;.iter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.zip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;ignores&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;files&lt;/span&gt;&lt;span class="nf"&gt;.extend&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;git_tracked_files&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ignore&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Partition 3: bounded residue not already covered above.&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ignore&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;roots&lt;/span&gt;&lt;span class="nf"&gt;.iter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.zip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;ignores&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;files&lt;/span&gt;&lt;span class="nf"&gt;.extend&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;recent_files&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ignore&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;include_hidden&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;files&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;files&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The names “partition 1” and “partition 2” in that annotated excerpt describe program order; conceptually I usually list Git-tracked first because it is easier to explain. The result is a set union either way.&lt;/p&gt;

&lt;p&gt;Only the recency partition needs arbitrary hard limits:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;RECENT_MAX_FILE_BYTES&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;u64&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;RECENT_LIMIT&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;usize&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// After filtering ignored, covered, oversized and churn-directory entries:&lt;/span&gt;
&lt;span class="n"&gt;candidates&lt;/span&gt;&lt;span class="nf"&gt;.sort_by_key&lt;/span&gt;&lt;span class="p"&gt;(|(&lt;/span&gt;&lt;span class="n"&gt;modified&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;)|&lt;/span&gt; &lt;span class="nn"&gt;std&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;cmp&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;Reverse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;modified&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="n"&gt;candidates&lt;/span&gt;&lt;span class="nf"&gt;.truncate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;RECENT_LIMIT&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The other two partitions are bounded by meaning rather than a numeric cutoff. A Git-tracked file is part of the project even if it is large. An agent-touched file is relevant because the session deliberately changed it. Silently dropping the 101st agent edit would make the most important partition unreliable.&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%2F5jzms6abqsgx56gn30zl.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%2F5jzms6abqsgx56gn30zl.png" alt="The tracked set combines project-bound, session-action-bound and hard-guardrailed partitions." width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The 116 GB measurement is the observation that produced those engineering trade-offs: use all three partitions to broaden coverage, but never let ambient build churn decide the size of every checkpoint.&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%2F7gg8ponctyqvmfqz3h29.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%2F7gg8ponctyqvmfqz3h29.png" alt="A full-tree walk of 70,609 files and 116,134 MB compared with a bounded union of about 6,096 files and 59 MB." width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  A separate ignore file is a feature, not duplicated configuration
&lt;/h2&gt;

&lt;p&gt;Rewind and version control answer different policy questions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;.gitignore&lt;/code&gt;: should this path enter shared repository history?&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;.codexsnapignore&lt;/code&gt;: may the rewind system capture and later touch this path?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Those sets should be allowed to overlap, but neither should imply the other. For example, &lt;code&gt;local-notes.md&lt;/code&gt; may be intentionally excluded from Git while still being valuable enough to rewind. A database, credential file or generated cache may be excluded from snapshots regardless of the Git policy.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# .codexsnapignore
.env
data/*.sqlite
tmp/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The matcher uses familiar Gitignore syntax but loads only the rewind-specific file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;SNAPSHOT_IGNORE_FILENAME&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;".codexsnapignore"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;load_ignore&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Gitignore&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;GitignoreBuilder&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="nf"&gt;.add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="nf"&gt;.join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SNAPSHOT_IGNORE_FILENAME&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="nf"&gt;.build&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.unwrap_or_else&lt;/span&gt;&lt;span class="p"&gt;(|&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nn"&gt;Gitignore&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;empty&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;is_ignored&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ignore&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;Gitignore&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;ignore&lt;/span&gt;&lt;span class="nf"&gt;.matched_path_or_any_parents&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.is_ignore&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The rule is symmetric: an ignored path is never captured, restored &lt;strong&gt;or deleted&lt;/strong&gt; by a restore. Applying it only during capture would create a dangerous asymmetry where an excluded file could still be removed later.&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%2F2hf95iw7g1asaapzm60j.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%2F2hf95iw7g1asaapzm60j.png" alt="Version-control ignore and rewind-safety ignore are independent policies; a local document can stay out of Git and remain rewindable." width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Safe restore requires evidence, then an escape hatch
&lt;/h2&gt;

&lt;p&gt;A bounded snapshot cannot honestly claim that every absent path should be deleted. Absence may mean “did not exist,” or it may mean “outside the tracked set.” Confusing the two is how a restore becomes a deletion bug.&lt;/p&gt;

&lt;p&gt;The restore planner therefore deletes only from witnessed history: the subsystem must have observed enough of a path's lifecycle to know that the target checkpoint says it was absent. It does not infer deletion from a partial directory listing.&lt;/p&gt;

&lt;p&gt;Before applying any restore, it also checkpoints the current state. &lt;code&gt;/redo&lt;/code&gt; restores that safety manifest. This changes the cost of a mistaken choice from lost work to an extra round trip.&lt;/p&gt;

&lt;p&gt;There are still real limitations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A file no checkpoint ever saw cannot be restored.&lt;/li&gt;
&lt;li&gt;A file outside the Git index, changed only by a shell command and pushed out of the 100-file recent window may be missed.&lt;/li&gt;
&lt;li&gt;Hidden files are skipped by default unless directly edited; &lt;code&gt;.git&lt;/code&gt; remains excluded from capture.&lt;/li&gt;
&lt;li&gt;Rewind cannot undo remote side effects such as a pushed branch, sent request or database operation.&lt;/li&gt;
&lt;li&gt;Two concurrent sessions can overwrite each other's workspace changes; this is not a merge system.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Stating those gaps is more useful than calling any snapshot system “complete.”&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the core and app-server boundary matters
&lt;/h2&gt;

&lt;p&gt;A feature implemented only in a terminal UI solves one surface and creates the next compatibility problem. Codex also has Desktop and IDE clients, so the reusable unit must live below any one interface.&lt;/p&gt;

&lt;p&gt;Capture lives in the Rust core where turn boundaries and tool execution are visible. Restore is exposed through the app-server protocol. The protocol field is intentionally small:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="cd"&gt;/// Also restore tracked workspace files to their state at the fork point.&lt;/span&gt;
&lt;span class="cd"&gt;/// A safety checkpoint is recorded first, so the restore is reversible.&lt;/span&gt;
&lt;span class="nd"&gt;#[experimental(&lt;/span&gt;&lt;span class="s"&gt;"thread/fork.restoreFiles"&lt;/span&gt;&lt;span class="nd"&gt;)]&lt;/span&gt;
&lt;span class="nd"&gt;#[serde(default,&lt;/span&gt; &lt;span class="nd"&gt;skip_serializing_if&lt;/span&gt; &lt;span class="nd"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"std::ops::Not::not"&lt;/span&gt;&lt;span class="nd"&gt;)]&lt;/span&gt;
&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;restore_files&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;codex-rewind exposes the CLI/TUI user flow today. Desktop and IDE clients do &lt;strong&gt;not&lt;/strong&gt; ship this implementation, but the core store and protocol path are structured so another Codex surface can reuse the same capture and restore semantics instead of inventing another checkpoint format.&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%2Fn9xkdrsszumbvgx8i1fn.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%2Fn9xkdrsszumbvgx8i1fn.png" alt="Capture lives in core and restore is exposed through app-server; CLI/TUI is the current flow while other clients can reuse the substrate." width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This cross-project, cross-surface concern is easy to miss in a local UI patch. It is also why “it works in my terminal” is not enough evidence that a rewind architecture fits Codex as a whole.&lt;/p&gt;

&lt;h2&gt;
  
  
  How the mechanism compares with Claude Code and OpenCode
&lt;/h2&gt;

&lt;p&gt;This is a comparison of boundaries, not a product leaderboard.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://code.claude.com/docs/en/checkpointing" rel="noopener noreferrer"&gt;Claude Code's checkpoint documentation&lt;/a&gt; says it tracks changes made through its file-editing tools, but not files modified by Bash commands. That is a clean and predictable boundary. codex-rewind keeps an equivalent agent-touched set, then adds Git-tracked paths and the bounded recency residue to catch more shell and MCP changes. The benefit is broader write-path coverage; the cost is more scope machinery and an explicit bounded-coverage gap.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://dev.opencode.ai/docs/config#snapshot" rel="noopener noreferrer"&gt;OpenCode's snapshot documentation&lt;/a&gt; says it uses an internal Git repository and warns that large repositories or many submodules can cause slow indexing and significant disk use. codex-rewind instead uses its own content-addressed store and a bounded tracked set. The benefit is no Git repository dependency and less coupling to ambient tree size; the cost is a custom store, custom garbage collection and no Git-native interoperability.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;System&lt;/th&gt;
&lt;th&gt;Tracking boundary&lt;/th&gt;
&lt;th&gt;Storage approach&lt;/th&gt;
&lt;th&gt;Main trade-off relevant here&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Claude Code&lt;/td&gt;
&lt;td&gt;File-editing tools&lt;/td&gt;
&lt;td&gt;Session checkpointing separate from Git&lt;/td&gt;
&lt;td&gt;Bash-made changes are not tracked&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OpenCode&lt;/td&gt;
&lt;td&gt;Agent operations / project snapshot&lt;/td&gt;
&lt;td&gt;Internal Git repository&lt;/td&gt;
&lt;td&gt;Docs warn about indexing and disk use on large repos/submodules&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;codex-rewind&lt;/td&gt;
&lt;td&gt;Git-tracked + agent-touched + bounded recent residue&lt;/td&gt;
&lt;td&gt;Independent content-addressed sidecar&lt;/td&gt;
&lt;td&gt;Broader but still deliberately incomplete coverage&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%2Fknxcw064vpyp9su7lf9n.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%2Fknxcw064vpyp9su7lf9n.png" alt="Claude Code, OpenCode and codex-rewind use different tracking and storage boundaries." width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The specific reason to try codex-rewind is therefore narrow: you use Codex, need conversation-and-file rewind now, want coverage beyond direct edit tools, and do not want the undo history written into the project's Git state.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quick answers to the compatibility questions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Does codex-rewind replace the official &lt;code&gt;codex&lt;/code&gt; command?
&lt;/h3&gt;

&lt;p&gt;No. It installs a separate &lt;code&gt;codexr&lt;/code&gt; executable, so both can exist on the same machine. It is an unofficial distribution built from an upstream Codex baseline, not an extension loaded into the official binary.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does rewind require a Git repository?
&lt;/h3&gt;

&lt;p&gt;No. In a repository, the subsystem reads the index as one source of project file paths. In a non-repository directory that partition is empty, while agent-touched and recently modified files still provide coverage. It never needs commits or stashes and does not write Git state.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is the conversation format merely “compatible,” or actually unchanged?
&lt;/h3&gt;

&lt;p&gt;It is unchanged. The implementation adds no field or event to Codex's conversation rollout. File history lives in a separate sidecar keyed by existing turn IDs. The practical caveat is coverage, not serialization: the official binary can read the conversation but does not produce snapshot entries for the turns it runs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does the same feature work across projects and Codex surfaces?
&lt;/h3&gt;

&lt;p&gt;The capture policy is based on session workspace roots, not one repository layout, so the same subsystem handles repositories and ordinary directories across projects. The user-facing implementation here is CLI/TUI. Its Rust core and app-server restore API are reusable by Desktop and IDE clients, but those official surfaces have not adopted this subsystem.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is it a complete backup system?
&lt;/h3&gt;

&lt;p&gt;No. It is a bounded, session-level undo buffer. Keep using Git and normal backups for durable history. The three partitions increase useful coverage without promising a full-tree snapshot, and the limits section above defines the remaining gaps.&lt;/p&gt;

&lt;h2&gt;
  
  
  Compatibility and the upstream path
&lt;/h2&gt;

&lt;p&gt;The package follows upstream Codex releases and installs alongside the official CLI. It deliberately shares the normal &lt;code&gt;~/.codex&lt;/code&gt; directory so login, configuration and conversation history can carry across. If you alternate executables inside one conversation, remember that only &lt;code&gt;codexr&lt;/code&gt; writes file snapshots; finish a tracked conversation in the executable that started it, or use a separate &lt;code&gt;CODEX_HOME&lt;/code&gt; if you prefer total isolation.&lt;/p&gt;

&lt;p&gt;As of August 21, 2026, the official Codex &lt;a href="https://github.com/openai/codex/blob/main/docs/contributing.md" rel="noopener noreferrer"&gt;contribution guide&lt;/a&gt; says that external code contributions and pull requests are not accepted. It asks for issues, root-cause analysis and design discussion instead. This project is therefore a usable bridge and a concrete design experiment, not a promise that an upstream PR will be merged.&lt;/p&gt;

&lt;p&gt;If you want to inspect or try it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/extracurricular-ai/codex-rewind" rel="noopener noreferrer"&gt;Source and installation instructions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/extracurricular-ai/codex-rewind/blob/main/docs/rfc-file-snapshot-rewind.md" rel="noopener noreferrer"&gt;Full file-snapshot rewind RFC&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The fastest path remains:&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; &lt;span class="nt"&gt;-g&lt;/span&gt; codex-rewind
codexr &lt;span class="nt"&gt;--enable&lt;/span&gt; file_snapshots
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you test it, the feedback I care about most is not “does undo sound useful?” It is where the tracking boundary surprises you: which real agent-made file change escaped the three partitions, or which captured path you believe should have stayed outside the rewind system.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;AI-assistance disclosure: I used an AI coding assistant to help structure and edit this article. I reviewed the technical claims and code excerpts against the project source and the linked documentation before publication.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Speculative Decoding, Illustrated: Why Generating 7 Tokens Can Cost the Same as 1</title>
      <dc:creator>Borui Cai</dc:creator>
      <pubDate>Wed, 05 Aug 2026 02:27:56 +0000</pubDate>
      <link>https://dev.to/s2thend/speculative-decoding-illustrated-why-generating-7-tokens-can-cost-the-same-as-1-k85</link>
      <guid>https://dev.to/s2thend/speculative-decoding-illustrated-why-generating-7-tokens-can-cost-the-same-as-1-k85</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a plain-language, illustrated explainer. You don't need an inference background to read it: every idea arrives as an everyday metaphor first (a small model that guesses, a big model that grades; a warehouse, a truck, and a workshop) and as math second — and every formula that does appear gets a plain-English translation right next to it. In a few places I deliberately oversimplify to keep the intuition front and center; footnoted rigor is what the papers in the reference list are for. If you've bounced off the original papers, this is the on-ramp.&lt;/em&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;🔑 The one sentence that unlocks everything&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Why is LLM inference slow, and why does speculative decoding make it fast? It comes down to one sentence: &lt;strong&gt;making the matrices bigger does add more multiplications for the GPU — but no matter how big the matrix is, one matrix multiplication loads the weights exactly once. More compute does not mean more loading.&lt;/strong&gt; And in LLM inference, &lt;em&gt;loading&lt;/em&gt; (moving weights from GPU memory into the compute units) is where nearly all the time goes, while &lt;em&gt;compute&lt;/em&gt; is close to free. So processing a few extra tokens costs almost no extra wall-clock time. This is not an algorithmic trick — it is a &lt;strong&gt;physical fact of GPU hardware&lt;/strong&gt;. Every bit of speedup speculative decoding delivers is this one hardware dividend being cashed in.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;A concrete example: the sentence "the cat sat on the red mat" &lt;em&gt;is&lt;/em&gt; a matrix.&lt;/strong&gt; Each token is first turned into a row of numbers (its embedding, say &lt;em&gt;d&lt;/em&gt; dimensions). Stack the 7 rows and you get a 7 × d matrix X:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;        ┌                                ┐
  the   │  0.12  -0.83   0.05   0.47  …  │
  cat   │ -0.31   0.22   0.68  -0.10  …  │
  sat   │  0.55   0.09  -0.42   0.33  …  │
  on    │ -0.07   0.61   0.18  -0.25  …  │
  the   │  0.12  -0.83   0.05   0.47  …  │
  red   │  0.29  -0.14   0.52   0.40  …  │
  mat   │  0.44   0.37  -0.20   0.08  …  │
        └                                ┘
            = matrix X  (7 rows × d columns)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;Rows = tokens (7 here).&lt;/strong&gt; A longer sentence means more rows and more multiplications — but multiplying this entire block X by the weight matrix W loads W from memory &lt;strong&gt;exactly once&lt;/strong&gt;. That is what "more compute, not more loading" looks like in the flesh. (Easter egg: the two "the" rows are identical — same token, same embedding row, before attention gets involved.)&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%2F7plecp5urumk7b0u1l18.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%2F7plecp5urumk7b0u1l18.png" alt=" " width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Context&lt;/strong&gt;: This is the technical deep-dive in a two-part series. Part 1 argued that speculative decoding is one of the most underrated skill niches for LLM inference roles in 2025–2026, and introduced the core idea ("a small model guesses, a big model grades"). This part covers &lt;strong&gt;the full math (including the rejection-sampling proof)&lt;/strong&gt;, the &lt;strong&gt;main research directions&lt;/strong&gt; of the past two years, and a &lt;strong&gt;list of real interview questions&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How to read this&lt;/strong&gt;: Section 1 is the foundation. Section 2 is the math (formulas, but every one gets a plain-English explanation). Section 3 maps the research landscape (use it as a study roadmap). Section 4 is a framing insight that &lt;strong&gt;stands out in interviews&lt;/strong&gt;. Section 5 is the learning path and interview question bank.&lt;/p&gt;
&lt;/blockquote&gt;


&lt;h2&gt;
  
  
  1. Why LLM Inference Is Slow: Two Orthogonal Bottlenecks
&lt;/h2&gt;

&lt;p&gt;To understand why speculative decoding works, &lt;strong&gt;you first have to see clearly why inference is slow at all&lt;/strong&gt;. This section doesn't touch the algorithm yet, but it is the foundation for everything after — and explaining it well in an interview instantly separates you from candidates who merely memorized the algorithm.&lt;/p&gt;
&lt;h3&gt;
  
  
  1.1 Bottleneck A: The Serial Dependency of Autoregressive Generation
&lt;/h3&gt;

&lt;p&gt;Mainstream LLMs are causal Transformers [15] that generate &lt;strong&gt;autoregressively&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;

&lt;/p&gt;
&lt;div class="katex-element"&gt;
  &lt;span class="katex-display"&gt;&lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;&lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;P&lt;/span&gt;&lt;span class="mopen"&gt;(&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord mathnormal"&gt;x&lt;/span&gt;&lt;span class="msupsub"&gt;&lt;span class="vlist-t vlist-t2"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="sizing reset-size6 size3 mtight"&gt;&lt;span class="mord mtight"&gt;1&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-s"&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="mpunct"&gt;,&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord mathnormal"&gt;x&lt;/span&gt;&lt;span class="msupsub"&gt;&lt;span class="vlist-t vlist-t2"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="sizing reset-size6 size3 mtight"&gt;&lt;span class="mord mtight"&gt;2&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-s"&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="mpunct"&gt;,&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="minner"&gt;…&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mpunct"&gt;,&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord mathnormal"&gt;x&lt;/span&gt;&lt;span class="msupsub"&gt;&lt;span class="vlist-t vlist-t2"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="sizing reset-size6 size3 mtight"&gt;&lt;span class="mord mathnormal mtight"&gt;n&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-s"&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="mclose"&gt;)&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mrel"&gt;=&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mop op-limits"&gt;&lt;span class="vlist-t vlist-t2"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="sizing reset-size6 size3 mtight"&gt;&lt;span class="mord mtight"&gt;&lt;span class="mord mathnormal mtight"&gt;i&lt;/span&gt;&lt;span class="mrel mtight"&gt;=&lt;/span&gt;&lt;span class="mord mtight"&gt;1&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span&gt;&lt;span class="mop op-symbol large-op"&gt;∏&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="sizing reset-size6 size3 mtight"&gt;&lt;span class="mord mtight"&gt;&lt;span class="mord mathnormal mtight"&gt;n&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-s"&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;P&lt;/span&gt;&lt;span class="mopen"&gt;(&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord mathnormal"&gt;x&lt;/span&gt;&lt;span class="msupsub"&gt;&lt;span class="vlist-t vlist-t2"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="sizing reset-size6 size3 mtight"&gt;&lt;span class="mord mathnormal mtight"&gt;i&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-s"&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mrel"&gt;∣&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord mathnormal"&gt;x&lt;/span&gt;&lt;span class="msupsub"&gt;&lt;span class="vlist-t vlist-t2"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="sizing reset-size6 size3 mtight"&gt;&lt;span class="mord mtight"&gt;&lt;span class="mrel mtight"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mord mathnormal mtight"&gt;i&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-s"&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="mclose"&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/div&gt;



&lt;p&gt;In plain English: &lt;strong&gt;every token needs all previous tokens as input&lt;/strong&gt;. Generating 100 tokens means 100 serial model calls.&lt;/p&gt;

&lt;p&gt;This serial nature lives at the algorithm level and &lt;strong&gt;cannot be bypassed without changing the modeling paradigm&lt;/strong&gt; — which is exactly where more radical approaches like Lookahead Decoding and diffusion language models come from.&lt;/p&gt;

&lt;h3&gt;
  
  
  1.2 Bottleneck B: Memory-Bandwidth Bound (the important one)
&lt;/h3&gt;

&lt;p&gt;This one is &lt;strong&gt;counterintuitive and a favorite interview topic&lt;/strong&gt;: &lt;strong&gt;the bottleneck of LLM inference is not GPU compute (FLOPs) — it is memory bandwidth&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;GPU memory is &lt;strong&gt;hierarchical&lt;/strong&gt;:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tier&lt;/th&gt;
&lt;th&gt;Capacity&lt;/th&gt;
&lt;th&gt;Bandwidth&lt;/th&gt;
&lt;th&gt;Role&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;HBM (device memory)&lt;/td&gt;
&lt;td&gt;~80 GB&lt;/td&gt;
&lt;td&gt;~3 TB/s&lt;/td&gt;
&lt;td&gt;Where model weights live&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;L2 cache&lt;/td&gt;
&lt;td&gt;~50 MB&lt;/td&gt;
&lt;td&gt;~10 TB/s&lt;/td&gt;
&lt;td&gt;Staging area&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SRAM (on-chip)&lt;/td&gt;
&lt;td&gt;~100 KB&lt;/td&gt;
&lt;td&gt;~30 TB/s&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Where matrix multiplies actually happen&lt;/strong&gt;&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%2Fnqo50xsayudqw6lzmrgn.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%2Fnqo50xsayudqw6lzmrgn.png" alt=" " width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;GPU compute cores can only work out of SRAM. On every forward pass, &lt;strong&gt;all model weights must stream from HBM into SRAM to participate in the computation&lt;/strong&gt; — this is dictated by GPU physics; no software layer can skip it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Put a number on it&lt;/strong&gt;: a 70B model in FP16 is roughly &lt;strong&gt;140 GB&lt;/strong&gt; of weights. At an H100's ~3 TB/s of HBM bandwidth, &lt;strong&gt;the floor for a single forward pass is about 47 ms&lt;/strong&gt; — the physical lower bound on per-token latency.&lt;/p&gt;

&lt;p&gt;But the more important fact is this: &lt;strong&gt;that 140 GB transfer cost barely changes with how many tokens you process&lt;/strong&gt;. One token: load everything once. Seven tokens: still load everything once. Processing a few extra positions adds almost nothing to the time of that forward pass.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why is "a few extra positions" nearly free?&lt;/strong&gt; The core operation of a forward pass is the matrix multiply &lt;strong&gt;Y = X · W&lt;/strong&gt;: input X has shape &lt;code&gt;[N, d]&lt;/code&gt; (N positions, d dimensions each), weights W have shape &lt;code&gt;[d, d']&lt;/code&gt;. Once the GPU has moved W from HBM into SRAM, it multiplies &lt;strong&gt;that same copy of W&lt;/strong&gt; against all N rows of X.&lt;/p&gt;

&lt;p&gt;To be precise: going from 1 row to 7 rows &lt;strong&gt;does increase the GPU's FLOPs&lt;/strong&gt; — six extra rows of multiply-accumulate is not zero work. The key is that &lt;strong&gt;one matrix multiplication loads W exactly once, and the loading volume does not depend on the number of rows&lt;/strong&gt;. Since LLM inference is memory-bound — compute is wildly abundant and bandwidth is the constraint — the extra compute &lt;strong&gt;hides inside the weight-loading time&lt;/strong&gt;: the compute cores were sitting idle waiting for W to arrive anyway, so multiplying a few more rows costs almost no extra wall-clock time. &lt;strong&gt;"Extra compute is free" doesn't mean compute literally costs nothing — it is a direct consequence of the GPU's compute ≫ bandwidth characteristic.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  1.3 Putting the Two Bottlenecks Together: The Core Pain
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Bottleneck A (algorithm)&lt;/strong&gt;: N serial forward passes are mandatory.&lt;br&gt;
&lt;strong&gt;Bottleneck B (hardware)&lt;/strong&gt;: each forward pass pays a fixed loading cost but produces only 1 token.&lt;/p&gt;

&lt;p&gt;Together, they are the essence of slow inference: &lt;strong&gt;paying "move 140 GB of weights" per pass and getting exactly 1 token back is a terrible exchange rate&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The elegance of speculative decoding is that it uses B to partially defeat A&lt;/strong&gt; — since extra tokens are free, find a way for one forward pass to &lt;em&gt;verify&lt;/em&gt; many tokens. Hold on to this and every paper in the field snaps into place.&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%2F5n9oo64qok4o254ah0of.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%2F5n9oo64qok4o254ah0of.png" alt=" " width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  1.4 The Key Clarification: What Does One Forward Pass Actually Produce?
&lt;/h3&gt;

&lt;p&gt;Time to puncture a &lt;strong&gt;very common misconception&lt;/strong&gt; — getting this right is the key to everything that follows.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Most people think one Transformer forward pass computes 1 token. It doesn't.&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;💡 &lt;strong&gt;The key mechanism&lt;/strong&gt;: one forward pass = one weight load + one matrix multiplication, and that single matrix multiplication &lt;strong&gt;computes every input position at once&lt;/strong&gt;. Loading and computing are &lt;strong&gt;welded together&lt;/strong&gt; — there is no such operation as "load once, then compute N separate times." "Computing N times" does not physically exist. What exists is "1 forward pass," "2 forward passes," … and each forward pass is an inseparable bundle of &lt;em&gt;load once + compute all current positions&lt;/em&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Transformers [15] use &lt;strong&gt;causal self-attention&lt;/strong&gt;: given an input sequence of length N, &lt;strong&gt;one forward pass outputs a "next-token distribution" at all N positions simultaneously&lt;/strong&gt;. Every input position i gets a distribution P(x_{i+1} | x≤ᵢ). This is precisely why Transformers train so much faster than RNNs — all positions compute in parallel.&lt;/p&gt;

&lt;p&gt;So why does ordinary decoding &lt;em&gt;look&lt;/em&gt; like "1 token per pass"? Because:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Position 0 predicts "what comes after input token 1" → that's input token 2, &lt;strong&gt;which we already have&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Position 1 predicts "what comes after input token 2" → that's input token 3, &lt;strong&gt;which we already have&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;…&lt;/li&gt;
&lt;li&gt;Position N−1 predicts "what comes after the last input token" → &lt;strong&gt;the only genuinely new information&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So &lt;strong&gt;the predictions at the first N−1 positions are entirely wasted&lt;/strong&gt; — they predict input we've already seen. Ordinary decoding keeps only the last position's output, hence "one new token per pass" from the outside.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Speculative decoding exploits exactly this waste&lt;/strong&gt;: let a small model pre-fill those "future positions" with guesses, and suddenly all N distributions from the big model's single forward pass become meaningful —&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the first γ distributions verify the corresponding draft tokens ("do I, the big model, agree with the small model's guess?")&lt;/li&gt;
&lt;li&gt;the last distribution yields a free bonus token (if all γ drafts were accepted)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Put ordinary and speculative decoding side by side and it becomes crystal clear&lt;/strong&gt; (both producing 7 tokens):&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Decoding&lt;/th&gt;
&lt;th&gt;Forward passes&lt;/th&gt;
&lt;th&gt;Weight loads&lt;/th&gt;
&lt;th&gt;What those matrix multiplies compute&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Ordinary&lt;/td&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;7 (≈ 7 × 47 ms)&lt;/td&gt;
&lt;td&gt;Pass k computes k positions but keeps only the last one; the rest is wasted&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Speculative&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;1&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;1 (≈ 47 ms)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;One pass computes 7 positions, &lt;strong&gt;all of them&lt;/strong&gt; used to verify drafts&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Here is the counterintuitive part: &lt;strong&gt;ordinary decoding's 7th forward pass also computes 7 positions&lt;/strong&gt; — it's just that the first 6 predict "input we already know" and get thrown away. So the &lt;em&gt;total matrix-multiply work&lt;/em&gt; of the two approaches is roughly the same. The real difference isn't how much you compute — it's &lt;strong&gt;how many times you load&lt;/strong&gt;: ordinary decoding loads the weights 7 times; speculative decoding compresses that to 1. And since loading dominates latency (~47 ms per load), &lt;strong&gt;compressing 7 loads into 1 is compressing the time itself&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Within that single verification pass, the costs are also wildly asymmetric:&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;Who&lt;/th&gt;
&lt;th&gt;What&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;Verification forward&lt;/td&gt;
&lt;td&gt;Big model (GPU)&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;One&lt;/strong&gt; forward pass, one weight load, distributions at all positions in parallel&lt;/td&gt;
&lt;td&gt;The main cost (~47 ms)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Accept/reject decisions&lt;/td&gt;
&lt;td&gt;Algorithm layer (CPU or a tiny GPU kernel)&lt;/td&gt;
&lt;td&gt;Table lookups over a batch of distributions + corrected rejection sampling&lt;/td&gt;
&lt;td&gt;Practically free&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;⚠️ A common wrong formulation is "multiple verifications inside one forward pass" or "multiple forward passes." Both are imprecise. &lt;strong&gt;The forward pass is 1&lt;/strong&gt;, the load is 1, and verification is &lt;strong&gt;many distributions from a single pass being consumed in parallel by the outer algorithm&lt;/strong&gt;. It is a free dividend of the Transformer's causal structure.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Internalize this mechanism and the proof, the research landscape, and the interview questions in the next sections all become intuitive.&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%2F8et1qb3c7iwuwtfzsqa7.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%2F8et1qb3c7iwuwtfzsqa7.png" alt=" " width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  2. The Core Algorithm
&lt;/h2&gt;
&lt;h3&gt;
  
  
  2.1 The Procedure
&lt;/h3&gt;

&lt;p&gt;Let:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Target model&lt;/strong&gt; M_p with distribution p(· | context) (big, slow — say 70B)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Draft model&lt;/strong&gt; M_q with distribution q(· | context), far smaller than M_p (small, fast — say 1B)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each round of speculative decoding has 4 steps [1, 2]:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Draft&lt;/strong&gt;: M_q autoregressively generates γ candidate tokens x₁, …, x_γ (typically γ = 4–7)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Parallel verification&lt;/strong&gt;: feed all γ tokens plus the prefix to M_p in &lt;strong&gt;one forward pass&lt;/strong&gt;, obtaining the conditional distributions p(· | x&amp;lt;ᵢ) at every position&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Corrected rejection sampling&lt;/strong&gt;: decide each xᵢ left to right —

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;accept&lt;/strong&gt; with probability &lt;strong&gt;min(1, p(xᵢ)/q(xᵢ))&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;otherwise &lt;strong&gt;reject&lt;/strong&gt;, resample a replacement from the corrected distribution &lt;strong&gt;p′(x) ∝ (p(x) − q(x))₊&lt;/strong&gt;, and &lt;strong&gt;discard all subsequent drafts&lt;/strong&gt; (where (·)₊ = max(0, ·))&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bonus token&lt;/strong&gt;: if all γ are accepted, sample one extra token from p(· | x≤_γ) — that distribution was computed in the pass anyway, so it's free&lt;/li&gt;
&lt;/ol&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%2F3vu5n3z5t8do7f8hnntv.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%2F3vu5n3z5t8do7f8hnntv.png" alt=" " width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  2.2 The Key Question: Why Is the Output &lt;em&gt;Exactly&lt;/em&gt; Equivalent to Sampling from M_p?
&lt;/h3&gt;

&lt;p&gt;This is the &lt;strong&gt;most magical and most important&lt;/strong&gt; property of speculative decoding: &lt;strong&gt;it is not "approximately lossless" — it is mathematically exact&lt;/strong&gt;. Deeply counterintuitive, yet the proof is short. &lt;strong&gt;Deriving it live in an interview instantly separates "read the paper" from "understands it."&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Goal&lt;/strong&gt;: show the probability of outputting any token x is exactly p(x).&lt;/p&gt;

&lt;p&gt;There are two paths to outputting x:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Path 1 (x is accepted):&lt;/strong&gt;&lt;/p&gt;


&lt;div class="katex-element"&gt;
  &lt;span class="katex-display"&gt;&lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;&lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mop"&gt;Pr&lt;/span&gt;&lt;span class="mopen"&gt;(&lt;/span&gt;&lt;span class="mord text"&gt;&lt;span class="mord"&gt;output&lt;/span&gt;&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mrel"&gt;=&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;x&lt;/span&gt;&lt;span class="mpunct"&gt;,&lt;/span&gt;&lt;span class="mspace"&gt;&amp;nbsp;&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mord text"&gt;&lt;span class="mord"&gt;accept&lt;/span&gt;&lt;/span&gt;&lt;span class="mclose"&gt;)&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mrel"&gt;=&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;q&lt;/span&gt;&lt;span class="mopen"&gt;(&lt;/span&gt;&lt;span class="mord mathnormal"&gt;x&lt;/span&gt;&lt;span class="mclose"&gt;)&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mbin"&gt;⋅&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mop"&gt;min&lt;/span&gt;&lt;span class="mclose"&gt;!&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="minner"&gt;&lt;span class="mopen delimcenter"&gt;&lt;span class="delimsizing size3"&gt;(&lt;/span&gt;&lt;/span&gt;&lt;span class="mord"&gt;1&lt;/span&gt;&lt;span class="mpunct"&gt;,&lt;/span&gt;&lt;span class="mspace"&gt;&amp;nbsp;&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mopen nulldelimiter"&gt;&lt;/span&gt;&lt;span class="mfrac"&gt;&lt;span class="vlist-t vlist-t2"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord mathnormal"&gt;q&lt;/span&gt;&lt;span class="mopen"&gt;(&lt;/span&gt;&lt;span class="mord mathnormal"&gt;x&lt;/span&gt;&lt;span class="mclose"&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="frac-line"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord mathnormal"&gt;p&lt;/span&gt;&lt;span class="mopen"&gt;(&lt;/span&gt;&lt;span class="mord mathnormal"&gt;x&lt;/span&gt;&lt;span class="mclose"&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-s"&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="mclose nulldelimiter"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="mclose delimcenter"&gt;&lt;span class="delimsizing size3"&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mrel"&gt;=&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mop"&gt;min&lt;/span&gt;&lt;span class="mopen"&gt;(&lt;/span&gt;&lt;span class="mord mathnormal"&gt;p&lt;/span&gt;&lt;span class="mopen"&gt;(&lt;/span&gt;&lt;span class="mord mathnormal"&gt;x&lt;/span&gt;&lt;span class="mclose"&gt;)&lt;/span&gt;&lt;span class="mpunct"&gt;,&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;q&lt;/span&gt;&lt;span class="mopen"&gt;(&lt;/span&gt;&lt;span class="mord mathnormal"&gt;x&lt;/span&gt;&lt;span class="mclose"&gt;))&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;Path 2 (rejected, then x is resampled from the corrected distribution):&lt;/strong&gt;&lt;/p&gt;


&lt;div class="katex-element"&gt;
  &lt;span class="katex-display"&gt;&lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;&lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mop"&gt;Pr&lt;/span&gt;&lt;span class="mopen"&gt;(&lt;/span&gt;&lt;span class="mord text"&gt;&lt;span class="mord"&gt;output&lt;/span&gt;&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mrel"&gt;=&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;x&lt;/span&gt;&lt;span class="mpunct"&gt;,&lt;/span&gt;&lt;span class="mspace"&gt;&amp;nbsp;&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mord text"&gt;&lt;span class="mord"&gt;reject&lt;/span&gt;&lt;/span&gt;&lt;span class="mclose"&gt;)&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mrel"&gt;=&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mop"&gt;Pr&lt;/span&gt;&lt;span class="mopen"&gt;(&lt;/span&gt;&lt;span class="mord text"&gt;&lt;span class="mord"&gt;reject&lt;/span&gt;&lt;/span&gt;&lt;span class="mclose"&gt;)&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mbin"&gt;⋅&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mopen nulldelimiter"&gt;&lt;/span&gt;&lt;span class="mfrac"&gt;&lt;span class="vlist-t vlist-t2"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mop"&gt;&lt;span class="mop op-symbol small-op"&gt;∑&lt;/span&gt;&lt;span class="msupsub"&gt;&lt;span class="vlist-t vlist-t2"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="sizing reset-size6 size3 mtight"&gt;&lt;span class="mord mathnormal mtight"&gt;y&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-s"&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="mopen"&gt;(&lt;/span&gt;&lt;span class="mord mathnormal"&gt;p&lt;/span&gt;&lt;span class="mopen"&gt;(&lt;/span&gt;&lt;span class="mord mathnormal"&gt;y&lt;/span&gt;&lt;span class="mclose"&gt;)&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mbin"&gt;−&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;q&lt;/span&gt;&lt;span class="mopen"&gt;(&lt;/span&gt;&lt;span class="mord mathnormal"&gt;y&lt;/span&gt;&lt;span class="mclose"&gt;))&lt;/span&gt;&lt;span class="mord"&gt;+&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="frac-line"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mopen"&gt;(&lt;/span&gt;&lt;span class="mord mathnormal"&gt;p&lt;/span&gt;&lt;span class="mopen"&gt;(&lt;/span&gt;&lt;span class="mord mathnormal"&gt;x&lt;/span&gt;&lt;span class="mclose"&gt;)&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mbin"&gt;−&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;q&lt;/span&gt;&lt;span class="mopen"&gt;(&lt;/span&gt;&lt;span class="mord mathnormal"&gt;x&lt;/span&gt;&lt;span class="mclose"&gt;))&lt;/span&gt;&lt;span class="mord"&gt;+&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-s"&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="mclose nulldelimiter"&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/div&gt;


&lt;p&gt;Here we need one &lt;strong&gt;key identity&lt;/strong&gt;:&lt;/p&gt;


&lt;div class="katex-element"&gt;
  &lt;span class="katex-display"&gt;&lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;&lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mop op-limits"&gt;&lt;span class="vlist-t vlist-t2"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="sizing reset-size6 size3 mtight"&gt;&lt;span class="mord mathnormal mtight"&gt;y&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span&gt;&lt;span class="mop op-symbol large-op"&gt;∑&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-s"&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="mopen"&gt;(&lt;/span&gt;&lt;span class="mord mathnormal"&gt;p&lt;/span&gt;&lt;span class="mopen"&gt;(&lt;/span&gt;&lt;span class="mord mathnormal"&gt;y&lt;/span&gt;&lt;span class="mclose"&gt;)&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mbin"&gt;−&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;q&lt;/span&gt;&lt;span class="mopen"&gt;(&lt;/span&gt;&lt;span class="mord mathnormal"&gt;y&lt;/span&gt;&lt;span class="mclose"&gt;)&lt;/span&gt;&lt;span class="mclose"&gt;&lt;span class="mclose"&gt;)&lt;/span&gt;&lt;span class="msupsub"&gt;&lt;span class="vlist-t vlist-t2"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="sizing reset-size6 size3 mtight"&gt;&lt;span class="mbin mtight"&gt;+&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-s"&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mrel"&gt;=&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mop op-limits"&gt;&lt;span class="vlist-t vlist-t2"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="sizing reset-size6 size3 mtight"&gt;&lt;span class="mord mathnormal mtight"&gt;y&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span&gt;&lt;span class="mop op-symbol large-op"&gt;∑&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-s"&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="delimsizing size1"&gt;[&lt;/span&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;p&lt;/span&gt;&lt;span class="mopen"&gt;(&lt;/span&gt;&lt;span class="mord mathnormal"&gt;y&lt;/span&gt;&lt;span class="mclose"&gt;)&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mbin"&gt;−&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mop"&gt;min&lt;/span&gt;&lt;span class="mopen"&gt;(&lt;/span&gt;&lt;span class="mord mathnormal"&gt;p&lt;/span&gt;&lt;span class="mopen"&gt;(&lt;/span&gt;&lt;span class="mord mathnormal"&gt;y&lt;/span&gt;&lt;span class="mclose"&gt;)&lt;/span&gt;&lt;span class="mpunct"&gt;,&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;q&lt;/span&gt;&lt;span class="mopen"&gt;(&lt;/span&gt;&lt;span class="mord mathnormal"&gt;y&lt;/span&gt;&lt;span class="mclose"&gt;))&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="delimsizing size1"&gt;]&lt;/span&gt;&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mrel"&gt;=&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;1&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mbin"&gt;−&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mop op-limits"&gt;&lt;span class="vlist-t vlist-t2"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="sizing reset-size6 size3 mtight"&gt;&lt;span class="mord mathnormal mtight"&gt;y&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span&gt;&lt;span class="mop op-symbol large-op"&gt;∑&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-s"&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mop"&gt;min&lt;/span&gt;&lt;span class="mopen"&gt;(&lt;/span&gt;&lt;span class="mord mathnormal"&gt;p&lt;/span&gt;&lt;span class="mopen"&gt;(&lt;/span&gt;&lt;span class="mord mathnormal"&gt;y&lt;/span&gt;&lt;span class="mclose"&gt;)&lt;/span&gt;&lt;span class="mpunct"&gt;,&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;q&lt;/span&gt;&lt;span class="mopen"&gt;(&lt;/span&gt;&lt;span class="mord mathnormal"&gt;y&lt;/span&gt;&lt;span class="mclose"&gt;))&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mrel"&gt;=&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mop"&gt;Pr&lt;/span&gt;&lt;span class="mopen"&gt;(&lt;/span&gt;&lt;span class="mord text"&gt;&lt;span class="mord"&gt;reject&lt;/span&gt;&lt;/span&gt;&lt;span class="mclose"&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/div&gt;


&lt;p&gt;That is: &lt;strong&gt;the normalizing constant of the corrected distribution equals the rejection probability exactly&lt;/strong&gt;. The two cancel:&lt;/p&gt;


&lt;div class="katex-element"&gt;
  &lt;span class="katex-display"&gt;&lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;&lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mop"&gt;Pr&lt;/span&gt;&lt;span class="mopen"&gt;(&lt;/span&gt;&lt;span class="mord text"&gt;&lt;span class="mord"&gt;output&lt;/span&gt;&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mrel"&gt;=&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;x&lt;/span&gt;&lt;span class="mpunct"&gt;,&lt;/span&gt;&lt;span class="mspace"&gt;&amp;nbsp;&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mord text"&gt;&lt;span class="mord"&gt;reject&lt;/span&gt;&lt;/span&gt;&lt;span class="mclose"&gt;)&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mrel"&gt;=&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mopen"&gt;(&lt;/span&gt;&lt;span class="mord mathnormal"&gt;p&lt;/span&gt;&lt;span class="mopen"&gt;(&lt;/span&gt;&lt;span class="mord mathnormal"&gt;x&lt;/span&gt;&lt;span class="mclose"&gt;)&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mbin"&gt;−&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;q&lt;/span&gt;&lt;span class="mopen"&gt;(&lt;/span&gt;&lt;span class="mord mathnormal"&gt;x&lt;/span&gt;&lt;span class="mclose"&gt;)&lt;/span&gt;&lt;span class="mclose"&gt;&lt;span class="mclose"&gt;)&lt;/span&gt;&lt;span class="msupsub"&gt;&lt;span class="vlist-t vlist-t2"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="sizing reset-size6 size3 mtight"&gt;&lt;span class="mbin mtight"&gt;+&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-s"&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/div&gt;


&lt;p&gt;Combine both paths:&lt;/p&gt;


&lt;div class="katex-element"&gt;
  &lt;span class="katex-display"&gt;&lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;&lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mop"&gt;Pr&lt;/span&gt;&lt;span class="mopen"&gt;(&lt;/span&gt;&lt;span class="mord text"&gt;&lt;span class="mord"&gt;output&lt;/span&gt;&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mrel"&gt;=&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;x&lt;/span&gt;&lt;span class="mclose"&gt;)&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mrel"&gt;=&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mop"&gt;min&lt;/span&gt;&lt;span class="mopen"&gt;(&lt;/span&gt;&lt;span class="mord mathnormal"&gt;p&lt;/span&gt;&lt;span class="mopen"&gt;(&lt;/span&gt;&lt;span class="mord mathnormal"&gt;x&lt;/span&gt;&lt;span class="mclose"&gt;)&lt;/span&gt;&lt;span class="mpunct"&gt;,&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;q&lt;/span&gt;&lt;span class="mopen"&gt;(&lt;/span&gt;&lt;span class="mord mathnormal"&gt;x&lt;/span&gt;&lt;span class="mclose"&gt;))&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mbin"&gt;+&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mopen"&gt;(&lt;/span&gt;&lt;span class="mord mathnormal"&gt;p&lt;/span&gt;&lt;span class="mopen"&gt;(&lt;/span&gt;&lt;span class="mord mathnormal"&gt;x&lt;/span&gt;&lt;span class="mclose"&gt;)&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mbin"&gt;−&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;q&lt;/span&gt;&lt;span class="mopen"&gt;(&lt;/span&gt;&lt;span class="mord mathnormal"&gt;x&lt;/span&gt;&lt;span class="mclose"&gt;)&lt;/span&gt;&lt;span class="mclose"&gt;&lt;span class="mclose"&gt;)&lt;/span&gt;&lt;span class="msupsub"&gt;&lt;span class="vlist-t vlist-t2"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="sizing reset-size6 size3 mtight"&gt;&lt;span class="mbin mtight"&gt;+&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-s"&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/div&gt;


&lt;p&gt;Check both cases:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If p(x) ≥ q(x): min = q(x), (p−q)₊ = p(x) − q(x), sum = p(x) ✓&lt;/li&gt;
&lt;li&gt;If p(x) &amp;lt; q(x): min = p(x), (p−q)₊ = 0, sum = p(x) ✓&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;The output follows p exactly.&lt;/strong&gt; ∎&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why this proof is beautiful&lt;/strong&gt;: the normalizing constant Σ_y (p(y)−q(y))₊ would normally require summing over the entire vocabulary (tens of thousands of tokens). Through this identity, &lt;strong&gt;it cancels against the rejection probability automatically&lt;/strong&gt; — the algorithm never has to compute it explicitly, yet the result is exactly correct. That is the elegance of corrected rejection sampling.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;💡 &lt;strong&gt;Interview tip&lt;/strong&gt;: learn these five lines cold and derive them live. Instantly upgrades you from "has read about it" to "understands it."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  2.3 Efficiency: Acceptance Rate Sets the Speedup Ceiling
&lt;/h3&gt;

&lt;p&gt;Define the &lt;strong&gt;acceptance rate&lt;/strong&gt;:&lt;/p&gt;


&lt;div class="katex-element"&gt;
  &lt;span class="katex-display"&gt;&lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;&lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;α&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mrel"&gt;=&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord mathbb"&gt;E&lt;/span&gt;&lt;span class="msupsub"&gt;&lt;span class="vlist-t vlist-t2"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="sizing reset-size6 size3 mtight"&gt;&lt;span class="mord mtight"&gt;&lt;span class="mord mathnormal mtight"&gt;x&lt;/span&gt;&lt;span class="mrel mtight"&gt;∼&lt;/span&gt;&lt;span class="mord mathnormal mtight"&gt;q&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-s"&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="delimsizing size1"&gt;[&lt;/span&gt;&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mop"&gt;min&lt;/span&gt;&lt;span class="mopen"&gt;(&lt;/span&gt;&lt;span class="mord"&gt;1&lt;/span&gt;&lt;span class="mpunct"&gt;,&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;p&lt;/span&gt;&lt;span class="mopen"&gt;(&lt;/span&gt;&lt;span class="mord mathnormal"&gt;x&lt;/span&gt;&lt;span class="mclose"&gt;)&lt;/span&gt;&lt;span class="mord"&gt;/&lt;/span&gt;&lt;span class="mord mathnormal"&gt;q&lt;/span&gt;&lt;span class="mopen"&gt;(&lt;/span&gt;&lt;span class="mord mathnormal"&gt;x&lt;/span&gt;&lt;span class="mclose"&gt;))&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="delimsizing size1"&gt;]&lt;/span&gt;&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mrel"&gt;=&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;1&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mbin"&gt;−&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord mathrm"&gt;TV&lt;/span&gt;&lt;/span&gt;&lt;span class="mopen"&gt;(&lt;/span&gt;&lt;span class="mord mathnormal"&gt;p&lt;/span&gt;&lt;span class="mpunct"&gt;,&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;q&lt;/span&gt;&lt;span class="mclose"&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/div&gt;


&lt;p&gt;where TV is total variation distance. Intuitively, α measures the &lt;strong&gt;overlap&lt;/strong&gt; between the target and draft distributions — the more alike they are, the higher the acceptance, the bigger the speedup.&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%2F3j51a6rpeg6nnvhncc96.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%2F3j51a6rpeg6nnvhncc96.png" alt=" " width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Expected tokens per round [1]:&lt;/p&gt;


&lt;div class="katex-element"&gt;
  ParseError: KaTeX parse error: Expected 'EOF', got '#' at position 13: 
\mathbb{E}[#̲\text{tokens}] …
&lt;/div&gt;


&lt;p&gt;With draft-to-target cost ratio c (typically c ∈ [0.02, 0.1]), the per-round speedup is:&lt;/p&gt;


&lt;div class="katex-element"&gt;
  &lt;span class="katex-display"&gt;&lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;&lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;S&lt;/span&gt;&lt;span class="mopen"&gt;(&lt;/span&gt;&lt;span class="mord mathnormal"&gt;γ&lt;/span&gt;&lt;span class="mclose"&gt;)&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mrel"&gt;=&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mopen nulldelimiter"&gt;&lt;/span&gt;&lt;span class="mfrac"&gt;&lt;span class="vlist-t vlist-t2"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mopen"&gt;(&lt;/span&gt;&lt;span class="mord"&gt;1&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mbin"&gt;−&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;α&lt;/span&gt;&lt;span class="mclose"&gt;)&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mopen"&gt;(&lt;/span&gt;&lt;span class="mord"&gt;1&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mbin"&gt;+&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;γ&lt;/span&gt;&lt;span class="mord mathnormal"&gt;c&lt;/span&gt;&lt;span class="mclose"&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="frac-line"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord"&gt;1&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mbin"&gt;−&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord mathnormal"&gt;α&lt;/span&gt;&lt;span class="msupsub"&gt;&lt;span class="vlist-t"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="sizing reset-size6 size3 mtight"&gt;&lt;span class="mord mtight"&gt;&lt;span class="mord mathnormal mtight"&gt;γ&lt;/span&gt;&lt;span class="mbin mtight"&gt;+&lt;/span&gt;&lt;span class="mord mtight"&gt;1&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-s"&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="mclose nulldelimiter"&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;Bottom line&lt;/strong&gt;: as c → 0 with optimal γ, the speedup approaches &lt;strong&gt;1 / (1 − α)&lt;/strong&gt;. vLLM reports up to &lt;strong&gt;2.8×&lt;/strong&gt; in production [3]; EAGLE-family methods reach &lt;strong&gt;3–4×&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. The Research Landscape (5 Subfields)
&lt;/h2&gt;

&lt;p&gt;Once vanilla speculative decoding was established, the past two years produced several active branches. &lt;strong&gt;These are exactly the tech stacks that inference teams hire for.&lt;/strong&gt;&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%2F6euuq2krp64olu74knpt.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%2F6euuq2krp64olu74knpt.png" alt=" " width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  3.1 Self-Speculation: Medusa &amp;amp; the EAGLE Family
&lt;/h3&gt;

&lt;p&gt;Vanilla requires deploying a separate small model — &lt;strong&gt;real ops cost and memory overhead&lt;/strong&gt;. &lt;strong&gt;Self-speculation&lt;/strong&gt; lets the target model draft for itself:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Medusa&lt;/strong&gt; [4]: attach K &lt;strong&gt;independent prediction heads&lt;/strong&gt; after the target model's last layer, predicting tokens 1 through K ahead. Shares the backbone features; cheap to train, simple to deploy&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;EAGLE&lt;/strong&gt; [5]: the core insight is that token-level drafting uncertainty mostly originates at the &lt;strong&gt;feature level&lt;/strong&gt;, so speculate in &lt;strong&gt;hidden-state space&lt;/strong&gt; (predict the next hidden state, then decode it to a token). Acceptance rates significantly beat token-level methods&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;EAGLE-2 / EAGLE-3&lt;/strong&gt; [6]: introduce the &lt;strong&gt;dynamic draft tree&lt;/strong&gt; — current SOTA on academic benchmarks, integrated into vLLM [7]&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;EAGLE-family speedups are typically &lt;strong&gt;3–4×&lt;/strong&gt;, currently the mainstream production choice.&lt;/p&gt;

&lt;h3&gt;
  
  
  3.2 Token-Tree Verification: SpecInfer
&lt;/h3&gt;

&lt;p&gt;Vanilla verifies one linear draft per round; a rejection at position 1 discards everything after it — &lt;strong&gt;wasteful&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;SpecInfer&lt;/strong&gt; [8] has the draft model generate &lt;strong&gt;multiple candidate branches&lt;/strong&gt; organized as a tree, and the target model verifies the whole tree in one pass via custom &lt;strong&gt;tree attention&lt;/strong&gt;, substantially raising expected accepted tokens. Later work (EAGLE-2 and others) adds &lt;strong&gt;dynamic depth adjustment&lt;/strong&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;💡 &lt;strong&gt;Note&lt;/strong&gt;: SpecInfer was published at &lt;strong&gt;ASPLOS — a systems conference, not an ML one&lt;/strong&gt;. That fact alone reflects the true nature of this field (see Section 4).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  3.3 Multi-Token Prediction (MTP)
&lt;/h3&gt;

&lt;p&gt;MTP [9, 10] takes a different route but is often discussed alongside spec decoding.&lt;/p&gt;

&lt;p&gt;The idea is to &lt;strong&gt;change the training objective&lt;/strong&gt; so the model predicts k future tokens at every position:&lt;/p&gt;


&lt;div class="katex-element"&gt;
  &lt;span class="katex-display"&gt;&lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;&lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord mathcal"&gt;L&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord text"&gt;&lt;span class="mord"&gt;MTP&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mrel"&gt;=&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;−&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mop op-symbol large-op"&gt;∑&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord"&gt;&lt;span class="mord mathnormal"&gt;i&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mrel"&gt;=&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mord"&gt;1&lt;/span&gt;&lt;/span&gt;&lt;span class="msupsub"&gt;&lt;span class="vlist-t"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="sizing reset-size6 size3 mtight"&gt;&lt;span class="mord mtight"&gt;&lt;span class="mord mathnormal mtight"&gt;N&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mop op-limits"&gt;&lt;span class="vlist-t vlist-t2"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="sizing reset-size6 size3 mtight"&gt;&lt;span class="mord mtight"&gt;&lt;span class="mord mathnormal mtight"&gt;j&lt;/span&gt;&lt;span class="mrel mtight"&gt;=&lt;/span&gt;&lt;span class="mord mtight"&gt;1&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span&gt;&lt;span class="mop op-symbol large-op"&gt;∑&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="sizing reset-size6 size3 mtight"&gt;&lt;span class="mord mtight"&gt;&lt;span class="mord mathnormal mtight"&gt;k&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-s"&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mop"&gt;lo&lt;span&gt;g&lt;/span&gt;&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord mathnormal"&gt;P&lt;/span&gt;&lt;span class="msupsub"&gt;&lt;span class="vlist-t vlist-t2"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="sizing reset-size6 size3 mtight"&gt;&lt;span class="mord mathnormal mtight"&gt;j&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-s"&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="mopen"&gt;(&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord mathnormal"&gt;x&lt;/span&gt;&lt;span class="msupsub"&gt;&lt;span class="vlist-t vlist-t2"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="sizing reset-size6 size3 mtight"&gt;&lt;span class="mord mtight"&gt;&lt;span class="mord mathnormal mtight"&gt;i&lt;/span&gt;&lt;span class="mbin mtight"&gt;+&lt;/span&gt;&lt;span class="mord mathnormal mtight"&gt;j&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-s"&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mrel"&gt;∣&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord mathnormal"&gt;x&lt;/span&gt;&lt;span class="msupsub"&gt;&lt;span class="vlist-t vlist-t2"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="sizing reset-size6 size3 mtight"&gt;&lt;span class="mord mtight"&gt;&lt;span class="mrel mtight"&gt;≤&lt;/span&gt;&lt;span class="mord mathnormal mtight"&gt;i&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-s"&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="mclose"&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/div&gt;


&lt;p&gt;MTP delivers &lt;strong&gt;two kinds of value&lt;/strong&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;High-quality drafts for spec decoding (a form of self-speculation)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Improved representation quality in the base model&lt;/strong&gt; (empirically shown in Meta's paper) — a side benefit no other acceleration method offers&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;DeepSeek-V3 adopted MTP at scale&lt;/strong&gt; [10]; it is one of the most watched directions in industry.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;💡 &lt;strong&gt;Advanced insight&lt;/strong&gt;: MTP and spec decoding attack &lt;strong&gt;different bottlenecks&lt;/strong&gt; — spec decoding borrows the hardware dividend (Bottleneck B) to relieve the algorithmic one (A); MTP attacks A directly. If future hardware (compute-in-memory, etc.) erases B, spec decoding's dividend shrinks dramatically — but &lt;strong&gt;MTP still works&lt;/strong&gt;. Articulating this distinction is a senior-level signal in interviews.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  3.4 Lookahead Decoding
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Lookahead Decoding&lt;/strong&gt; [11] needs &lt;strong&gt;no draft model at all&lt;/strong&gt;: view autoregressive decoding as solving a nonlinear system, update multiple positions in parallel with &lt;strong&gt;Jacobi iteration&lt;/strong&gt;, and converge to output identical to standard autoregressive decoding.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pro&lt;/strong&gt;: zero auxiliary models, zero training.&lt;br&gt;
&lt;strong&gt;Con&lt;/strong&gt;: speedups usually below spec decoding (1.5–2×).&lt;/p&gt;

&lt;p&gt;A good fit for resource-constrained deployments or teams that can't maintain an extra draft model.&lt;/p&gt;

&lt;h3&gt;
  
  
  3.5 Online Learning &amp;amp; Production Deployment
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Online Speculative Decoding&lt;/strong&gt; [12] observes that a draft model's acceptance rate &lt;strong&gt;drifts&lt;/strong&gt; across query distributions — a draft trained on general text can lose half its acceptance rate on code-heavy production traffic. The fix: &lt;strong&gt;continuously fine-tune the draft model&lt;/strong&gt; on live traffic during serving.&lt;/p&gt;

&lt;p&gt;On the engineering side, &lt;strong&gt;vLLM Speculators&lt;/strong&gt; [7] productizes spec-decoding training and deployment, supports mainstream variants like EAGLE-3, and is used in large production systems including Amazon Rufus and LinkedIn AI [13].&lt;/p&gt;




&lt;h2&gt;
  
  
  4. The Framing That Matters: This Is Software Engineering, Not Algorithmic Novelty
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Understanding this makes you sound like someone who truly gets the field, not someone who just read the papers.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Step back and look at the whole area:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The model itself never changes.&lt;/strong&gt; Vanilla spec decoding uses the target model exactly as-is — zero parameter updates, zero architecture changes&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Asymptotic complexity never changes.&lt;/strong&gt; Generating N tokens is still O(N) serial steps worst-case; only the &lt;strong&gt;constant factor drops severalfold&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The core math is classical corrected rejection sampling&lt;/strong&gt; — textbook sampling theory, not a new invention&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The physical source of the speedup is a GPU-architecture dividend&lt;/strong&gt; — the enormous gap between HBM bandwidth and SRAM speed opened a memory-bound window&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Which implies:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;It is tied to a hardware generation.&lt;/strong&gt; If compute-in-memory (PIM/CIM) hardware erases the memory wall, the dividend shrinks dramatically&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It is closer to a systems contribution.&lt;/strong&gt; SpecInfer went to ASPLOS; vLLM went to SOSP [14] — the venues tell the story&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Its algorithmic purity is limited&lt;/strong&gt; — no new learning paradigm, no new representational capacity, no new objective&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;None of this diminishes its value&lt;/strong&gt; — quite the opposite. It showcases what &lt;strong&gt;engineering insight&lt;/strong&gt; does for LLM deployment: spotting &lt;em&gt;wasted hardware capability&lt;/em&gt; and cashing it in with a clever scheme.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If you're job-hunting, this is great news.&lt;/strong&gt; You don't need top-venue publications or deep ML theory. You need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Solid systems knowledge (GPU architecture, memory hierarchy, concurrency)&lt;/li&gt;
&lt;li&gt;The ability to read real code (navigate vLLM internals)&lt;/li&gt;
&lt;li&gt;Hands-on projects with numbers (measured speedups, acceptance-rate distributions)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;This is precisely the window of opportunity for strong engineers without elite-school or research pedigrees.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  5. Learning Path &amp;amp; Interview Question Bank
&lt;/h2&gt;

&lt;h3&gt;
  
  
  5.1 Must-Read Papers (in order)
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;#&lt;/th&gt;
&lt;th&gt;Paper&lt;/th&gt;
&lt;th&gt;Focus&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Leviathan et al. (2023) [1]&lt;/td&gt;
&lt;td&gt;Algorithm 1 + appendix proof&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Chen et al. (2023) [2]&lt;/td&gt;
&lt;td&gt;Read against #1; two independent concurrent inventions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Cai et al. (2024) Medusa [4]&lt;/td&gt;
&lt;td&gt;Method section&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;Li et al. (2024) EAGLE [5]&lt;/td&gt;
&lt;td&gt;The motivation for feature-level speculation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;Fu et al. (2024) Lookahead [11]&lt;/td&gt;
&lt;td&gt;The Jacobi-iteration intuition&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Advanced&lt;/strong&gt;: MTP [9], the DeepSeek-V3 report [10], SpecInfer [8], EAGLE-3 [6].&lt;/p&gt;

&lt;h3&gt;
  
  
  5.2 Hands-On Projects (where the real differentiation happens)
&lt;/h3&gt;

&lt;p&gt;Do at least one:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Run a spec-decoding demo with HuggingFace &lt;code&gt;transformers&lt;/code&gt;&lt;/strong&gt;: &lt;code&gt;model.generate(..., assistant_model=...)&lt;/code&gt; — a few lines of code&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Measure the acceptance rate α&lt;/strong&gt;: swap draft models of different sizes (1B vs 3B) and watch α move&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Read the vLLM spec-decoding source&lt;/strong&gt;: &lt;a href="https://github.com/vllm-project/vllm" rel="noopener noreferrer"&gt;github.com/vllm-project/vllm&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Plot the acceptance-position distribution&lt;/strong&gt;: which tokens get rejected? (Named entities and numbers get rejected often; "the/a/of" sail through)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;One sentence in an interview — "&lt;strong&gt;I benchmarked the X + Y model pair, got a Z× speedup at α = …, and found that  gets rejected most&lt;/strong&gt;" — and you've pulled ahead of the pack.&lt;/p&gt;

&lt;h3&gt;
  
  
  5.3 Interview Question Bank
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Warm-up (asked in every interview):&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Explain speculative decoding in one sentence&lt;/li&gt;
&lt;li&gt;Where does the speedup come from?&lt;/li&gt;
&lt;li&gt;Why is the output exactly lossless? &lt;strong&gt;Derive the proof live&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Define the acceptance rate α and what drives it&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Mid-level (where most candidates get filtered):&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;If Transformers are parallel, why can an LLM only output 1 token per pass? (Key: each position predicts P(· | prefixᵢ))&lt;/li&gt;
&lt;li&gt;How is the KV cache handled in spec decoding? How do rejected drafts get "rolled back" from the cache?&lt;/li&gt;
&lt;li&gt;How do you choose the draft length γ? Why isn't bigger always better? Give the E[#tokens] formula and discuss&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;When does spec decoding fail to accelerate?&lt;/strong&gt; (Large batches, low α, a draft model that's too heavy relative to the target — extremely common question)&lt;/li&gt;
&lt;li&gt;What are Medusa's and EAGLE's core innovations, and what problems do they solve?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Senior (what separates top candidates):&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The &lt;strong&gt;fundamental difference&lt;/strong&gt; between spec decoding and MTP? When to use which?&lt;/li&gt;
&lt;li&gt;If the workload becomes compute-bound (large batch, PIM hardware), does spec decoding still matter?&lt;/li&gt;
&lt;li&gt;What does token-tree verification buy over single-branch verification?&lt;/li&gt;
&lt;li&gt;What's the tension between speculative decoding and continuous batching?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Open-ended design:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Design a production spec-decoding system for a 70B model at 1000 QPS: draft selection, dynamic γ, memory budget&lt;/li&gt;
&lt;li&gt;Acceptance rate is stuck at 50% — diagnose (tokenizer mismatch / domain drift / temperature / model-family mismatch)&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  5.4 Tricks for Interns and New Grads
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Use the three key terms unprompted&lt;/strong&gt;: memory-bound, acceptance rate α, corrected rejection sampling. Interviewers hear them and know you're real&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Derive, don't recite&lt;/strong&gt;: producing min(p,q) + (p−q)₊ = p live raises your level instantly&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Volunteer the limitations&lt;/strong&gt;: "In high-batch serving the gains shrink, because large batches are no longer memory-bound" — interviewers love hearing this&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Nail spec decoding vs MTP&lt;/strong&gt;: "one borrows the memory-bound dividend to attack Bottleneck A; the other attacks A directly" — top-candidate signal&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Closing
&lt;/h2&gt;

&lt;p&gt;Speculative decoding is not ML mysticism. &lt;strong&gt;It is a clear, learnable engineering optimization.&lt;/strong&gt; The whole field fits in one sentence:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Paying "move 140 GB of weights" for 1 token is a terrible deal; let a small model guess several tokens, have the big model grade them all in one load, and use corrected rejection sampling to guarantee the output is exactly lossless.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Hold that thread and every variant — Medusa, EAGLE, SpecInfer, MTP, Lookahead — is just a different branch of the same tree.&lt;/p&gt;

&lt;p&gt;I hope this pair of articles helps anyone preparing for LLM inference roles. &lt;strong&gt;This field genuinely isn't that hard — the key is building projects with measurable numbers.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;And if you don't have an elite-school or research pedigree: this is &lt;strong&gt;a fully viable path&lt;/strong&gt;. Companies desperately need engineers who understand inference optimization, and the barrier to entry here is far lower than "training large models." Work through these two articles carefully, build one or two small projects with real measurements, and you're already ahead of most candidates.&lt;/p&gt;




&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Academic Papers
&lt;/h3&gt;

&lt;p&gt;[1] Leviathan, Y., Kalman, M., &amp;amp; Matias, Y. (2023). Fast Inference from Transformers via Speculative Decoding. &lt;em&gt;Proceedings of the 40th International Conference on Machine Learning (ICML 2023)&lt;/em&gt;. arXiv:2211.17192.&lt;/p&gt;

&lt;p&gt;[2] Chen, C., Borgeaud, S., Irving, G., Lespiau, J.-B., Sifre, L., &amp;amp; Jumper, J. (2023). Accelerating Large Language Model Decoding with Speculative Sampling. &lt;em&gt;arXiv preprint&lt;/em&gt; arXiv:2302.01318.&lt;/p&gt;

&lt;p&gt;[4] Cai, T., Li, Y., Geng, Z., Peng, H., Lee, J. D., Chen, D., &amp;amp; Dao, T. (2024). Medusa: Simple LLM Inference Acceleration Framework with Multiple Decoding Heads. &lt;em&gt;ICML 2024&lt;/em&gt;. arXiv:2401.10774.&lt;/p&gt;

&lt;p&gt;[5] Li, Y., Wei, F., Zhang, C., &amp;amp; Zhang, H. (2024). EAGLE: Speculative Sampling Requires Rethinking Feature Uncertainty. &lt;em&gt;ICML 2024&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;[6] Li, Y., Wei, F., Zhang, C., &amp;amp; Zhang, H. (2024). EAGLE-2: Faster Inference of Language Models with Dynamic Draft Trees. &lt;em&gt;arXiv preprint&lt;/em&gt; arXiv:2406.16858.&lt;/p&gt;

&lt;p&gt;[8] Miao, X., Oliaro, G., Zhang, Z., Cheng, X., Wang, Z., Wong, R. Y. Y., et al. (2024). SpecInfer: Accelerating Large Language Model Serving with Tree-based Speculative Inference and Verification. &lt;em&gt;ASPLOS 2024&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;[9] Gloeckle, F., Idrissi, B. Y., Rozière, B., Lopez-Paz, D., &amp;amp; Synnaeve, G. (2024). Better &amp;amp; Faster Large Language Models via Multi-token Prediction. &lt;em&gt;ICML 2024&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;[10] DeepSeek-AI. (2024). DeepSeek-V3 Technical Report. &lt;em&gt;arXiv preprint&lt;/em&gt; arXiv:2412.19437.&lt;/p&gt;

&lt;p&gt;[11] Fu, Y., Bailis, P., Stoica, I., &amp;amp; Zhang, H. (2024). Break the Sequential Dependency of LLM Inference Using Lookahead Decoding. &lt;em&gt;ICML 2024&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;[12] Liu, X., Hu, L., Bailis, P., Stoica, I., Deng, Z., Cheung, A., &amp;amp; Zhang, H. (2024). Online Speculative Decoding. &lt;em&gt;ICML 2024&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;[14] Kwon, W., Li, Z., Zhuang, S., Sheng, Y., Zheng, L., Yu, C. H., et al. (2023). Efficient Memory Management for Large Language Model Serving with PagedAttention. &lt;em&gt;SOSP 2023&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;[15] Vaswani, A., Shazeer, N., Parmar, N., Uszkoreit, J., Jones, L., Gomez, A. N., Kaiser, Ł., &amp;amp; Polosukhin, I. (2017). Attention Is All You Need. &lt;em&gt;NeurIPS 2017&lt;/em&gt;. arXiv:1706.03762.&lt;/p&gt;

&lt;h3&gt;
  
  
  Industry Blogs
&lt;/h3&gt;

&lt;p&gt;[3] vLLM Team. (2024-10-17). &lt;em&gt;How Speculative Decoding Boosts vLLM Performance by up to 2.8x&lt;/em&gt;. &lt;a href="https://blog.vllm.ai/2024/10/17/spec-decode.html" rel="noopener noreferrer"&gt;https://blog.vllm.ai/2024/10/17/spec-decode.html&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;[7] vLLM Team. (2025-12-13). &lt;em&gt;Diving into speculative decoding training support for vLLM with Speculators v0.3.0&lt;/em&gt;. &lt;a href="https://blog.vllm.ai/2025/12/13/speculators-v030.html" rel="noopener noreferrer"&gt;https://blog.vllm.ai/2025/12/13/speculators-v030.html&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;[13] vLLM Team. (2025-01-10). &lt;em&gt;vLLM 2024 Retrospective and 2025 Vision&lt;/em&gt;. &lt;a href="https://blog.vllm.ai/2025/01/10/vllm-2024-wrapped-2025-vision.html" rel="noopener noreferrer"&gt;https://blog.vllm.ai/2025/01/10/vllm-2024-wrapped-2025-vision.html&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;If this helped, the best way to support the series is to share it with someone prepping for inference-engineering interviews. Questions and corrections welcome.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>machinelearning</category>
      <category>ai</category>
      <category>llm</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
