NVIDIA Dynamo スナップショット:Kubernetes 上の推論ワークロードにおける高速起動
NVIDIA は、Kubernetes 上で推論ワークロードを起動する際の「コールドスタート」遅延問題を解決し、GPU のアイドル時間を削減するための「Dynamo Snapshot」技術を発表した。
キーポイント
コールドスタートの課題とコスト
Kubernetes での推論レプリカのスケーリング時に、コンテナ起動やモデル重みの読み込みなどに数分を要し、その間 GPU がアイドル状態となり SLA 違反のリスクが高まる。
Dynamo Snapshot の仕組み
NVIDIA は、初期化済み状態のスナップショットを作成・保存することで、起動時の画像プルやエンジン初期化(重み読み込み、カーネルウォームアップなど)を省略する手法を開発した。
推論ワークロードへの適用
vLLM などの単一 GPU ワークロードを対象に、起動時間を劇的に短縮し、トラフィックの急増時でも即座に対応可能なインフラを実現する。
コールドスタートのボトルネック分析
シングルGPU推論ワーカーのコールドスタートでは、コンテナイメージのプルやエンジン初期化(重み読み込み、カーネルウォームアップなど)に数分を要し、これがパフォーマンスの主要な遅延要因となる。
NVIDIA Dynamo Snapshot の目的
この技術は、Kubernetes 環境における推論ワークロードのコールドスタート時間を大幅に短縮するために設計された高速起動ソリューションである。
NVIDIA Dynamo Snapshot の構成要素
AI インフラのチェックポイント/リストアには、CUDA ドライバーの機能を用いて GPU デバイス状態をシリアライズする「cuda-checkpoint」と、Linux カーネルの状態をディスクに保存するオープンソースツール「CRIU」の 2 つが組み合わされています。
チェックポイントとリストアの動作フロー
チェックポイント時はデバイス状態を CPU メモリへ、ホスト側プロセスツリーをストレージへそれぞれ保存し、リストア時には CRIU がプロセスツリーを復元した後に cuda-checkpoint が GPU 状態を新しいハードウェアに適用します。
重要な引用
In production inference deployments, demand fluctuates over time, requiring inference replicas to scale elastically. However, cold-starting inference workloads on Kubernetes can take several minutes.
During that time, GPUs are allocated but idle, generating no tokens and serving no requests.
This delay increases the risk of service level agreement (SLA) violations during traffic spikes.
Figure 1. Cold-Start Latency Breakdown for a Single-GPU Inference Worker
Stacked bar chart showing the time breakdown of a single-GPU cold start, with segments for container/image pull, engine initialization (weight loading, kernel warmup, graph compilation), and distributed runtime startup, totaling several minutes.
CRIU is fundamentally a freeze-and-thaw mechanism. When a process is restored, execution resumes at the exact instruction where it was checkpointed, completely unaware that checkpointing or restoration occurred.
影響分析・編集コメントを表示
影響分析
この記事は、大規模言語モデル(LLM)の推論コストとレスポンス性を最適化する上で極めて重要な進展を示しています。特に Kubernetes を基盤としたクラウドネイティブな AI インフラにおいて、起動遅延によるリソース浪費を解消し、SLA の安定性を担保する実用的な解決策を提供します。これにより、企業はより効率的かつ信頼性の高い推論サービス運用が可能になります。
編集コメント
推論サービスの運用コストとパフォーマンスを左右する「起動時間」の課題に対し、具体的な技術的解決策が提示された点は評価に値します。特にトラフィック急増時の SLA 維持という実務的な観点から、この技術は即座に導入検討されるべき重要なアップデートです。
コールドスタートの問題
本番環境での推論デプロイメントでは、需要は時間とともに変動するため、推論レプリカを弾力的にスケーリングする必要があります。しかし、Kubernetes 上で推論ワークロードをコールドスタートするには数分かかることがあります。その間、GPU は割り当てられていますがアイドル状態であり、トークンを生成せず、リクエストも処理できません。
この遅延は、システムが突然の需要増加を吸収するために十分に迅速にスケーリングできないため、トラフィックの急増時にサービスレベルアグリーメント(SLA)違反のリスクを高めます。
単一 GPU の vLLM (v0.20.0) ワークロードにおけるコールドスタートレイテンシの内訳は以下の通りです:
image*図 1. 単一 GPU 推論ワーカーのコールドスタートレイテンシ内訳*
起動時間を大幅に短縮するため、Kubernetes 上の AI 推論ワークロード向けのチェックポイント/リストアアプローチである NVIDIA Dynamo Snapshot を導入します。本稿では、単一 GPU ワークロードにおいて光速に近い起動時間を達成する初期プロトタイプ背後の設計上の選択と最適化について説明します。
これは、Dynamo における高速スタートアップに関するシリーズ記事の第 1 回です。
CRIU と cuda-checkpoint
実行中の推論ワーカーのチェックポイント可能な状態には、2 つのコンポーネントがあります:
- デバイス状態(GPU 側):CUDA コンテキスト、ストリーム、デバイスメモリ、仮想アドレスマッピングなど。これはホストからは見えません。この状態をシリアライズするために、各 CUDA コンテキストを持つプロセスの CPU メモリへデバイスの状態をダンプする CUDA ドライバーのチェックポイント機能(cuda-checkpoint コマンドラインツールでも公開されています)を使用します。
- ホスト状態(CPU 側):CPU メモリ、スレッド、ファイル記述子、名前空間など。Linux カーネルにはこの状態をシリアライズするために必要なすべての管理情報が含まれています。私たちはオープンソースツールである CRIU (Checkpoint/Restore in Userspace) を使用して Linux カーネルの管理情報を参照し、プロセスツリーの状態をディスクへシリアライズします。
これら 2 つのツールは、推論ワーカー全体の状態をチェックポイント/リストアできるようにきれいに連携しています。チェックポイント実行時:
- cuda-checkpoint がすべてのデバイス状態を CPU メモリにダンプします。
- CRIU がすべてのホスト側のプロセスツリー状態をストレージ内のフォルダーへダンプします。
リストア実行時(同じノードまたは異なるノード):
- CRIU が NFS/SMB などの分散ストレージからシリアライズされた状態に基づいてプロセスツリーをリストアし、チェックポイントされたアーティファクトを別のノードから取得できるようにします。
- cuda-checkpoint が CPU メモリにシリアライズされた内容から新しい GPU へ GPU 状態をリストアします。
CRIU は本質的に「凍結・再開」メカニズムです。プロセスがリストアされると、実行はチェックポイントされた時点の正確な命令から再開され、チェックポイントやリストアが行われたことを完全に認識しません。
このため、チェックポイント前のワークロードの静置化や、復元後の外部状態の再確立など、必要な調整は、オーケストレーターまたはワークロード固有フックを通じて外部で処理する必要があります。これらのメカニズムについては、以下のセクションで説明します。
Dynamo Snapshot: Kubernetes
Kubernetes では、ワークロードはポッド内のコンテナ内で実行されます。CRIU チェックポイントにはコンテナの書き込み可能ファイルシステム層への参照が含まれるため、プロセスツリーの状態とファイルシステムが一緒に移動するように、コンテナレベルでチェックポイントを行います。
Helm チャートを通じてインストール可能な特権 DaemonSet「snapshot-agent」を提供します。このエージェントは各ノード上で実行され、runc 自体への修正を必要とせずに、runc で管理されるコンテナのチェックポイントおよび復元処理を担当します。
チェックポイント時には、エージェントはワークロードの準備完了プローブを待機した後、ホスト側から cuda-checkpoint および CRIU を呼び出し、アーティファクトを共有ストレージに書き込みます。ワークロードがコンテナローカル(つまりオーバーレイファイルシステム)でファイルを作成または削除している場合があり、エージェントは CRIU ステージ後にこれらもチェックポイントします。
復元時には、エージェントは軽量なプレースホルダーポッドを開始し、オーバーレイファイルシステムを復元した後、CRIU/CUDA チェックポイントをそれぞれの名前空間に復元します。その後、復元されたワーカーが実行を引き継ぎます。
各エージェントはローカルノード上で独立して動作するため、チェックポイントと復元をクラスター全体で自然に並列化できます。私たちはこれを、runc の Kubernetes ネイティブなチェックポイント/復元サポート(これも CRIU に委譲しています)に依存する代わりに構築しました。DaemonSet アプローチは完全に移植可能であり、クラウドプロバイダーによるチェックポイント/復元機能ゲートのサポートに依存しません。
これにより、パフォーマンスチューニングのための CRIU に対する制御がより厳密になり、チェックポイントアーティファクトを OCI イメージに埋め込むのではなく、柔軟なストレージバックエンド上に保存できるようになります。
image*Figure 2. Kubernetes checkpoint and restore lifecycle with Dynamo Snapshot*
Dynamo Snapshot: The workload
Dynamo インフェレンスワーカーは、以下の 2 つのフェーズで起動します:
- エンジンの初期化:設定されたインフェレンスエンジンが起動されます。コミュニケーターが初期化され、重みが読み込まれ、カーネルがウォームアップされ、グラフがコンパイル/キャプチャされますなど。このフェーズの終了時には、ワーカーは完全にウォーム状態になります。リクエストに応えることは可能ですが、まだ自身のポッド外部からは検出できません。
- 分散ランタイムの起動:ワーカーは Dynamo コントロールプレーンに接続し、ディスカバリーバックエンドに登録することで、ルーターやグラフの残りの部分がワーカーを検出できるようにします。この時点から、ワーカーは「稼働中」となり、コントロールプレーンへのオープンな接続が確立され、クラスター内の他のコンポーネントもこのワーカーのポッドIDを認識しています。
もしワークロードがチェックポイント化されていることを知らされないまま、チェックポイント/リストアを素朴に実装した場合、チェックポイントジョブの準備状態プローブは、ディスカバリープレーンに登録された完全に初期化された分散ランタイムに対応することになります。つまり、CRIU によってキャプチャできないアクティブな TCP 接続が存在することになります。
これを解決する一般的なパターンは、クイエスク/レジュームフックです:ワークロードがクイエスク状態であることを保証し、リストア完了時に発火する外部シグナルでブロックします。これはチェックポイント/リストアにおいて強力な抽象化となります。なぜなら:
- ワークロードがチェックポイント化される前にリソースをクリーンアップできるため、最終的なチェックポイントサイズが最適化され(したがってリストア時間が短縮されます)。
- チェックポイント化できないリソースをレジューム後に再作成できるようにするためです。これは特にマルチ GPU およびマルチノードのチェックポイント(将来のリリースで予定されています)において重要です:RPC に使用される確立された状態のアウトバウンド TCP 接続は、ポッド IP がチェックポイントとリストアの間に変更されるため、チェックポイント化できません。また、RDMA 登録や NIC 状態も、レジューム後に再作成する必要があります。
Dynamo Snapshot では、これらのフックを「チェックポイント準備完了」シグナルファイルの存在を readiness probe として定義することで実装しています。ワーカーはエンジンが初期化された後、分散ランタイム起動前にこのファイルを作成します。
その時点で、ワーカーはスナップショットエージェントが外部でチェックポイントを取得している間、別の「復元完了」シグナルファイルを待つポーリングループに入ります。チェックポイントは、このポーリングループ内の任意の命令で行うことができます。
CRIU はチェックポイントが行われた正確な命令位置で実行を復元するため、ワーカーはポーリングループ内で直接再開し、シグナルファイルを検知して追加の同期処理を必要とせずに分散ランタイムの初期化を進めます。
最適化 #1: KV キャッシュのアンマップおよび解放
チェックポイントサイズを削減するための一つの最適化手法は、チェックポイント実行前に KV キャッシュメモリを解放することです。重み、CUDA グラフ、その他のバッファ/活性化が割り当てられている間の GPU メモリ使用量のピークを測定した後、推論エンジンは残りの GPU メモリを大規模な KV キャッシュバッファとして割り当てます。
ただし、チェックポイントはレプリカがリクエストを処理する前の静止状態で行われるため、この KV キャッシュバッファは全くチェックポイントする必要はありません。しかし、この CUDA グラフに埋め込まれているため、この KV キャッシュの仮想アドレスを安定して維持する必要があります。つまり、CUDA 仮想メモリ管理 API (cuMemCreate および cuMemMap) を介して KV キャッシュバッファを割り当てた後、仮想アドレスを安定させたまま基盤となる物理割り当てを解放するには、cuMemAddressFree を呼び出すのではなく、cuMemUnmap と cuMemRelease を呼び出すだけで十分です。幸いにも、この機能は vLLM (sleep() および wake_up() を経由) および SGLang (torch_memory_saver を経由) でネイティブに既に利用可能です。
KV キャッシュのアンマップと解放により、B200 上の Qwen3-0.6B の全アーティファクトサイズは約 190 GiB から約 6 GiB に削減されます。この効果は、KV キャッシュサイズが大きい場合(つまり、GPU サイズに対してモデル重みが相対的に小さい場合)に最も顕著です。
image*図 3. KV キャッシュのアンマップと解放によりチェックポイントサイズが削減される*
オプティマイゼーション #2: CRIU の高速化
現時点では、復元時間はまだ許容できるレベルにはほど遠いです。また、より大規模なモデルの場合、復元時間がコールドスタートよりも長くなり、チェックポイント/リストレートの目的そのものを無効にしてしまいます。
image*Figure 4. Restore time with upstream CRIU exceeds cold-start time*
主な理由は、CRIU と cuda-checkpoint がメモリを光速度(SOL: Speed-of-Light)でコピーできないからです。Linux プロセスには、匿名メモリ(プロセスのヒープやスタックなど)と共有メモリ(プロセス間で共有されるメモリ)の 2 種類のメモリが存在します。CRIU はこれらの両方のメモリタイプを復元する責任を負っており、大規模モデルにおいてはどちらも重要なボトルネックとなります。このセクションでは、プロセスメモリの復元を大幅に高速化するために私たちが CRIU に実装した最適化手法について概説します。
注: これらの CRIU 最適化は現時点では Dynamo Snapshot の一部として提供されておらず、アップストリームの CRIU にマージされた時点で利用可能になります。
注 #2: ベンチマーク対象のワークロードにおけるオーバーレイファイルシステムは非常に小さく(<100 MiB)、復元時間の計算において無視できるほどであるため、ここでは省略しています。
最適化 #2.1: Parallel memfd restore
vLLM の sleep()/wake_up() パスと SGLang の torch_memory_saver(quiesce/resume フック内で呼び出します)は、ウェイトタグ付きの GPU 割り当てをピン留めされた CPU シャドウバッファへ移動させます。これは、高帯域幅のホストからデバイスへ、およびデバイスからホストへのメモリコピー(H2D/D2H)における一般的な慣行です。CUDA はこれらの割り当てを共有匿名メモリでバックアップし、その後 NVIDIA ドライバによってピン留めされます。Linux カーネル内ではこれらは memfds として現れます。これは MAP_SHARED でマッピング可能な、RAM バックアップの匿名ファイルです。
gpt-oss-120b の場合、これらのバッファは合計 120 GiB を超え、多くの独立した 2 GiB 以下のバッファに分割されていました。Upstream CRIU はこれらのバッファを逐次的に復元します:シャムバックアップされたオブジェクトを 1 つ作成し、サイズを変更し、マッピングし、チェックポイントイメージからその内容をを読み込み、その後次のオブジェクトへ進むという手順です。
私たちは CRIU を変更し、まずすべての一意な shmem バックアップオブジェクトを列挙した上で、スレッドプールを開始して並列に復元するようにしました。各ワーカーは独立してバッファを割り当ててチェックポイントから読み込むため、復元処理が一度に 1 つのバッファを処理するのではなく、利用可能なストレージ帯域幅と CPU の並列性を活用できるようになりました。
オプティマイゼーション #2.2: 匿名メモリに対する Linux ネイティブ AIO
CRIU が共有リソース(ファイル、ソケット、共有メモリオブジェクト、memfds など)の復元を終えた後、各プロセスのプライベートメモリを埋める必要があります。具体的にはヒープページ、スタック、匿名マッピング、コピーオンライトのプライベートファイルマッピングです。これらのページは共有されず、1 つのプロセスに属し、チェックポイント前に持っていた正確な仮想アドレスに配置される必要があります。
アップストリームの CRIU では、この埋め込みパスは同期型の preadv ループです。レストアラーはリストから 1 つのジョブを取り出し、preadv に渡して待機します。カーネルはその単一の読み取りをストレージデバイスに発行し、デバイスは DMA でバイトを宛先の VMA ページに転送し、preadv が返ります。その後初めて、レストアラーは次のジョブに進みます。常に飛行中の読み取りは 1 つだけであり、リクエスト間のストレージデバイスのアイドル状態が残されます。単一のブロッキングストリームでは高速な NVMe バンド幅を飽和させることはできず、ネットワーク接続ストレージの場合、次の読み取りを開始する前に各読み取りで往復遅延が発生します。
image*Figure 5. Serial preadv Memory Restore (one read in flight at a time)*
私たちは preadv ループを Linux ネイティブの AIO に置き換えました。CRIU は事前に読み込みジョブのリストを構築します。各ジョブは、ファイルオフセット、バイト数、および宛先の VMA ページを指す iovec を記述する iocb です。復元プログラムは AIO コンテキストを作成し、これにより多数の異なる読み取りトランザクションを同時に保持できます。これにより、ストレージデバイスは内部チャネル全体でこれらの処理を並行して実行できます。復元プログラムは AIO コンテキストを作成し、io_submit を使って一連の iocb を提出し、最大 128 の読み込みを同時に飛行状態(in flight)として保持します。io_getevents を経由して完了が戻ってくると、新しい提出でウィンドウが埋められ、すべてのジョブが完了するまでこれが続きます。
image*Figure 6. Linux Native AIO Memory Restore (up to 128 reads in flight concurrently)*
Direct I/O and the page cache
ストレージバックエンドがサポートしている場合、匿名メモリおよび共有メモリの読み取りは O_DIRECT を使用します。復元は主にチェックポイントファイルから宛先メモリへの単一のストリーム処理であるため、カーネルページキャッシュに入力ページをキャッシュすることは通常無駄になります。ダイレクト I/O を使用しない場合、大規模な復元により、チェックポイントデータでページキャッシュが一時的に満たされると同時に、宛先の shmem ページが割り当てられ、メモリの圧力が上昇して他のワークロードにとって有用なデータがスワップアウトされる可能性があります。
さらに重要なのは、Linux ネイティブ AIO(非同期入出力)は O_DIRECT フラグ付きで開かれたファイルに対してのみ真に非同期動作する点です。O_DIRECT が利用できない、あるいは信頼性が低いファイルシステム、例えば一部の NFS 環境などでは、復元処理はバッファード I/O にフォールバックし、逐次読みAhead(先読み)が行われるため、カーネル側では予測可能なストリーミングアクセスパターンが維持されますが、AIO による性能向上効果は大幅に低下します。
Results
同じ環境において、CRIU の復元時間に劇的な改善が見られ、チェックポイントからの復元が推論ワーカーの完全起動(コールドスタート)よりもはるかに高速になりました:
モデルチェックポイントサイズCRIU (upstream)CRIU (AIO)CRIU (AIO + parallel memfd)上流版に対する速度向上比SOL
Qwen3-0.6B6.2 GiB6.8 秒2.9 秒2.4 秒2.8 倍0.95 秒
Qwen3-8B26 GiB24 秒11 秒4.7 秒5.1 倍1.8 秒
gpt-oss-120b129 GiB119 秒54 秒15 秒7.9 倍11 秒
*表 1. 上流版 CRIU と最適化された復元パスの復元時間比較。Linux ネイティブ AIO および並列 memfd 復元により、モデルサイズに関わらず復元レイテンシが大幅に削減され、光速(SOL)に近いパフォーマンスを実現しています。*
原文を表示
The cold-start problem
In production inference deployments, demand fluctuates over time, requiring inference replicas to scale elastically. However, cold-starting inference workloads on Kubernetes can take several minutes. During that time, GPUs are allocated but idle, generating no tokens and serving no requests.
This delay increases the risk of service level agreement (SLA) violations during traffic spikes, as the system cannot scale quickly enough to absorb sudden increases in demand.
For a single-GPU vLLM (v0.20.0) workload, the cold-start latency breaks down as follows:

To significantly reduce startup time, we are introducing NVIDIA Dynamo Snapshot, our checkpoint/restore approach for AI inference workloads on Kubernetes. In this post, we describe the design choices and optimizations behind our early prototype, which achieves startup times close to the speed of light for single-GPU workloads.
This is the first post in a series on fast startup in Dynamo.
CRIU and cuda-checkpoint
A running inference worker’s checkpointable state has two components:
- Device state (GPU-side): CUDA contexts, streams, device memory, virtual address mappings, etc. This is not visible to the host. To serialize this state, we use the checkpointing capability of the CUDA driver (which is also exposed by the cuda-checkpoint command line tool) to dump the device state to CPU memory of the process owning each CUDA context.
- Host state (CPU-side): CPU memory, threads, file descriptors, namespaces, etc. The Linux kernel has all the bookkeeping necessary to be able to serialize this state. We use an open-source tool, CRIU (Checkpoint/Restore in Userspace) to walk the Linux kernel’s bookkeeping and serialize the process tree’s state to disk.
These two tools compose cleanly to allow checkpoint/restore of the full inference worker state. When checkpointing:
- cuda-checkpoint dumps all device state into CPU memory.
- CRIU dumps all host-side process tree state to a folder in storage.
When restoring (same or different node):
- CRIU restores the process tree according to the serialized state from distributed storage like NFS/SMB, allowing us to fetch the checkpointed artifact from a different node.
- cuda-checkpoint restores the GPU state from what is serialized in CPU memory onto the new GPUs.
CRIU is fundamentally a freeze-and-thaw mechanism. When a process is restored, execution resumes at the exact instruction where it was checkpointed, completely unaware that checkpointing or restoration occurred.
Because of this, any coordination required before checkpointing, such as quiescing the workload, or after restoration, such as re-establishing external state, must be handled externally through an orchestrator or workload-specific hooks. We describe these mechanisms in the following sections.
Dynamo Snapshot: Kubernetes
In Kubernetes, workloads run inside containers inside pods. Because CRIU checkpoints contain references to the container’s writable filesystem layer, we checkpoint at the container level so the process tree state and filesystem travel together.
We provide a privileged DaemonSet, snapshot-agent, installable through a Helm chart. An agent runs on every node and handles checkpoint and restore for runc-managed containers without requiring modifications to runc itself.
On checkpoint, the agent waits for the workload’s readiness probe, then invokes cuda-checkpoint and CRIU from the host side before writing the artifact to shared storage. The workload may have created/deleted files local to the container (i.e. the overlay filesystem), which the agent also checkpoints after the CRIU stage.
On restore, the agent launches a lightweight placeholder pod, restores the overlay filesystem, and restores the CRIU/CUDA checkpoint into its namespaces. The restored worker then takes over execution.
Each agent operates independently on its local node, allowing checkpoints and restores to parallelize naturally across the cluster. We built this instead of relying on Kubernetes native checkpoint/restore support in runc, which also delegates to CRIU. The DaemonSet approach is fully portable and does not depend on cloud-provider support for checkpoint/restore feature gates.
It also gives us tighter control over CRIU for performance tuning and allows checkpoint artifacts to live in flexible storage backends instead of being embedded into OCI images.

Dynamo Snapshot: The workload
A Dynamo inference worker comes up in two phases:
- Engine initialization: The configured inference engine is started: communicators are initialized, weights are loaded, kernels are warmed up, graphs are compiled/captured, etc. By the end of this phase the worker is fully warm. It could serve a request, but is not yet discoverable to anything outside its own pod.
- Distributed runtime startup: The worker connects to the Dynamo control plane and registers itself with the discovery backend, so the router and the rest of the graph can find it. From this point on, the worker is “live” — there are open connections to the control plane, and other components in the cluster are aware of this worker’s pod identity.
If we were to implement checkpoint/restore naively, without the workload knowing it was being checkpointed, the readiness probe of the checkpoint job would correspond to a fully initialized distributed runtime that is registered to the discovery plane, which means there are active TCP connections that cannot be captured by CRIU.
The general pattern that solves this is quiesce/resume hooks: the workload ensures it is in a quiescent state and blocks on an external signal that fires when the restore is complete. This is a powerful abstraction for checkpoint/restore because:
- It lets the workload clean up its resources before being checkpointed, which optimizes the final checkpoint size (and thereby decreases restore time).
- It allows the workload to recreate resources that aren’t checkpointable post-resume. This is especially important for multi-GPU and multi-node checkpoints (planned for a future release): outbound TCP connections used for RPC cannot be checkpointed in an established state since the pod IP changes between checkpoint and restore, and RDMA registrations and NIC state also need to be recreated post-restore.
In Dynamo Snapshot, we implement these hooks by defining the readiness probe as the presence of a “ready for checkpoint” signal file. The worker writes this file after the engine initializes but before distributed runtime startup.
At that point, the worker enters a polling loop waiting for a separate “restore complete” signal file while the snapshot agent checkpoints it externally. The checkpoint can occur at any instruction within that polling loop.
Because CRIU restores execution at the exact instruction where checkpointing occurred, the worker resumes directly inside the polling loop, detects the signal file, and proceeds with distributed runtime initialization without requiring additional synchronization.
Optimization #1: KV cache unmap and release
One optimization to reduce the checkpoint size is to deallocate the KV cache memory before checkpointing. After measuring the peak GPU memory usage while weights, CUDA graphs, and other buffers/activations are allocated, inference engines allocate the remainder of the GPU memory as a large KV cache buffer.
However, since our checkpoint is taken in a quiescent state *before* the replica has served any requests, this KV cache buffer does not need to be checkpointed at all. But we need to keep the virtual address of this KV cache stable since it is baked into the CUDA graph. This means we allocate the KV cache buffer via the CUDA Virtual Memory Management API (cuMemCreate and cuMemMap); then deallocating the underlying physical allocation while keeping the virtual address stable is as simple as calling cuMemUnmap and cuMemRelease, but not cuMemAddressFree. Luckily, this functionality is already natively available in vLLM (via sleep() and wake_up()) and SGLang (via torch_memory_saver).
Unmap and release of the KV cache reduces the total artifact size of Qwen3-0.6B on a B200 from ~190 GiB to ~6 GiB. The wins are most pronounced for large KV cache sizes (i.e. smaller model weights relative to GPU size).

Optimization #2: Speeding up CRIU
At this point, restore times are still far from acceptable. And or larger models, the restore time actually exceeds that of a cold start, defeating the entire purpose of checkpoint/restore.

The primary reason is, CRIU and cuda-checkpoint do not copy memory at speed-of-light (SOL) speeds. In a Linux process, there are two types of memory: anonymous memory (the heap, stack, etc. of a process) and shared memory (shared between processes). CRIU is responsible for restoring both types of memory, and both become significant bottlenecks for large models. In this section, we outline the optimizations to CRIU that we developed to significantly speed up restoration of process memory.
Note: These CRIU optimizations are not yet shipped as part of Dynamo Snapshot, and will be available once they have been merged into upstream CRIU.
Note #2: The overlay filesystem for our benchmarked workloads was very small (<100 MiB) and is negligible in restore timing, so it is omitted.
Optimization #2.1: Parallel memfd restore
vLLM’s sleep()/wake_up() path and SGLang’s torch_memory_saver (which we call in the quiesce/resume hooks) move weight-tagged GPU allocations into pinned CPU shadow buffers. This is common practice for high-bandwidth host-to-device/device-to-host (H2D/D2H) memory copies. CUDA backs these allocations with shared anonymous memory, which is then pinned through the NVIDIA driver. Inside the Linux kernel, these appear as memfds: anonymous, RAM-backed files that can be mapped with MAP_SHARED.
For gpt-oss-120b, these buffers consumed more than 120 GiB, split across many independent 2 GiB-or-smaller buffers. Upstream CRIU restores those buffers serially: it creates one shmem-backed object, resizes it, maps it, reads its contents from the checkpoint image, and only then moves on to the next object.
We modified CRIU to first enumerate all unique shmem-backed objects, then launch a thread pool to restore them in parallel. Each worker allocates its buffer and reads from the checkpoint independently, allowing restore to use the available storage bandwidth and CPU parallelism instead of processing buffers one at a time.
Optimization #2.2: Linux native AIO for anonymous memory
After CRIU has restored the shared resources (files, sockets, shmem objects, memfds, etc.), it still has to fill in each process’s private memory: heap pages, stacks, anonymous mappings, and copy-on-write private file mappings. These pages are not shared; they belong to one process and need to land at the exact virtual addresses they had before checkpoint.
In upstream CRIU, that fill path is a synchronous preadv loop. The restorer pulls one job from the list, hands it to preadv, and waits. The kernel issues that single read to the storage device, the device DMAs the bytes into the destination VMA pages, and preadv returns. Only then does the restorer move on to the next job. There is exactly one read in flight at any moment, which leaves the storage device idle between requests. A single blocking stream cannot saturate fast NVMe bandwidth, and on network-attached storage each read also pays a round trip before the next one can start.

We replaced the preadv loop with Linux native AIO. CRIU builds a list of read jobs ahead of time. Each job is an iocb describing a file offset, a byte count, and an iovec pointing at the destination VMA pages. The restorer creates an AIO context, which holds many distinct read transactions simultaneously, allowing the storage device to run them concurrently across its internal channels. The restorer creates an AIO context, submits a batch of iocbs with io_submit, and keeps a window of up to 128 reads in flight. As completions come back via io_getevents, new submissions backfill the window until every job is done.

Direct I/O and the page cache
Where the storage backend supports it, both anonymous and shared memory reads use O_DIRECT. Restore is mostly a one-pass stream from checkpoint files into destination memory, so caching the input pages in the kernel page cache is usually wasteful. Without direct I/O, a large restore can temporarily fill the page cache with checkpoint data while also allocating the destination shmem pages, increasing memory pressure and evicting useful data for other workloads.
Even more importantly, Linux native AIO is only truly asynchronous on files opened with O_DIRECT. On filesystems where O_DIRECT is unavailable or unreliable, such as some NFS deployments, restore falls back to buffered I/O with sequential readahead so the kernel still sees a predictable streaming access pattern, but the gains from AIO are significantly reduced.
Results
On the same setup, we saw a massive improvement in CRIU restore time, and it is now significantly faster to restore from checkpoint than to cold start an inference worker:
関連記事
今日のまとめ
AI日報で今日の重要ニュースをまとめ読み