들어가며: GPU 추론의 콜드스타트, 왜 문제인가
프로덕션 환경에서 LLM 추론 워커는 트래픽 변동에 따라 탄력적으로 스케일링되어야 합니다. 하지만 쿠버네티스 위에서 GPU 워커를 새로 띄우는 데는 수 분이 걸립니다. 그 시간 동안 GPU는 할당되어 있지만 유휴 상태로 대기하며, SLA 위반 위험을 높입니다.
NVIDIA의 최신 기술 블로그(원문: NVIDIA Developer Blog)에 따르면, 단일 GPU vLLM 워크로드 기준 콜드스타트 지연 시간은 다음과 같이 분해됩니다:
| 단계 | 소요 시간 |
|---|---|
| 엔진 초기화 (가중치 로딩, CUDA 그래프 컴파일 등) | 수십 초 ~ 수 분 |
| 분산 런타임 시작 (컨트롤 플레인 연결, 디스커버리 등록) | 수 초 |
| 총합 | 수 분 |
이 문제를 해결하기 위해 NVIDIA는 Dynamo Snapshot이라는 체크포인트/리스토어 방식을 제안합니다. 이 글에서는 그 핵심 설계와 최적화 기법을 실무 관점에서 분석합니다.

핵심 아이디어: CRIU + cuda-checkpoint의 조합
Dynamo Snapshot은 두 가지 오픈소스 도구를 결합합니다:
- cuda-checkpoint: GPU 디바이스 상태(CUDA 컨텍스트, 디바이스 메모리, 가상 주소 매핑)를 CPU 메모리로 덤프
- CRIU (Checkpoint/Restore in Userspace): CPU 측 프로세스 트리 상태(메모리, 파일 디스크립터, 네임스페이스)를 디스크에 직렬화
체크포인트 시퀀스는 다음과 같습니다:
# 1. cuda-checkpoint가 GPU 상태를 CPU 메모리로 덤프
cuda-checkpoint --pid <PID> --dump-dir /tmp/gpu_state
# 2. CRIU가 호스트 상태를 디스크에 저장
criu dump --tree <PID> --images-dir /tmp/criu_images --shell-job
리스토어 시에는 반대로 CRIU가 먼저 프로세스 트리를 복원한 후, cuda-checkpoint가 GPU 상태를 새로운 GPU에 재할당합니다. CRIU는 freeze-and-thaw 메커니즘으로 동작하므로, 복원된 프로세스는 체크포인트 시점의 정확한 명령어부터 실행을 재개합니다.
쿠버네티스에서의 구현: snapshot-agent DaemonSet
NVIDIA는 snapshot-agent라는 특권 DaemonSet을 Helm 차트로 제공합니다. 각 노드에서 에이전트가 실행되며, runc 기반 컨테이너의 체크포인트와 리스토어를 처리합니다.
# Helm 차트로 설치 예시 (개념)
helm install dynamo-snapshot nvidia/dynamo-snapshot \
--set agent.image.tag=v0.1.0 \
--set storage.backend=nfs \
--set storage.path=/shared/checkpoints
에이전트는 워커의 readiness probe를 감지한 후 체크포인트를 수행하며, 리스토어 시에는 가벼운 placeholder 파드를 먼저 띄운 후 체크포인트 아티팩트를 복원합니다.
쿼시스/리줌(Quiesce/Resume) 훅
워커가 분산 런타임에 연결된 상태에서 체크포인트를 찍으면 TCP 연결이 포함되어 복원이 불가능합니다. 이를 해결하기 위해 Dynamo Snapshot은 신호 파일 기반 훅을 사용합니다.
# 워커 코드 내부 (의사 코드)
import os
import time
CHECKPOINT_READY = "/tmp/checkpoint_ready"
RESTORE_COMPLETE = "/tmp/restore_complete"
def quiesce_hook():
# 엔진 초기화 완료 후, 분산 런타임 시작 전에 신호 파일 생성
open(CHECKPOINT_READY, "w").close()
# 리스토어 완료를 기다리는 폴링 루프
while not os.path.exists(RESTORE_COMPLETE):
time.sleep(0.1)
# 리스토어 완료 후 분산 런타임 시작
start_distributed_runtime()
이 패턴은 체크포인트 크기를 최적화하고, 복원 후 재생성해야 하는 리소스(예: RDMA 연결)를 깔끔하게 처리할 수 있게 해줍니다.

최적화 1: KV 캐시 언맵/릴리즈로 체크포인트 크기 97% 감소
체크포인트를 찍는 시점은 아직 요청을 서빙하지 않은 쿼시스 상태입니다. 따라서 KV 캐시 버퍼는 전혀 필요하지 않습니다. 하지만 CUDA 그래프에 가상 주소가 고정되어 있어야 하므로, NVIDIA는 cuMemUnmap + cuMemRelease를 사용해 물리 할당만 해제합니다.
// CUDA Virtual Memory Management API 사용 예시
CUmemGenericAllocationHandle handle;
CUdeviceptr ptr;
// 가상 주소 예약
cuMemAddressReserve(&ptr, kv_cache_size, 0, 0, 0);
// 물리 메모리 할당 및 매핑
cuMemCreate(&handle, kv_cache_size, &mem_pool_props, 0);
cuMemMap(ptr, kv_cache_size, 0, handle, 0);
// 체크포인트 직전: 물리 메모리만 해제 (가상 주소는 유지)
cuMemUnmap(ptr, kv_cache_size);
cuMemRelease(handle);
이 최적화로 Qwen3-0.6B 모델의 체크포인트 크기가 ~190 GiB에서 ~6 GiB로 97% 감소했습니다. vLLM의 sleep()/wake_up() 또는 SGLang의 torch_memory_saver를 통해 이미 지원되는 기능입니다.
최적화 2: CRIU 리스토어 속도 7.9배 향상
대규모 모델(gpt-oss-120b, 129 GiB)에서 CRIU 리스토어 시간이 콜드스타트보다 오래 걸리는 문제가 있었습니다. NVIDIA는 두 가지 핵심 최적화를 적용했습니다.
2.1 병렬 memfd 리스토어
vLLM/SGLang의 wake-up 과정에서 가중치가 CPU 핀 메모리(memfd)로 이동됩니다. 기존 CRIU는 이 버퍼들을 직렬로 복원했지만, NVIDIA는 스레드 풀을 도입해 병렬 처리로 전환했습니다.
2.2 Linux Native AIO로 익명 메모리 읽기
기존 CRIU는 preadv 동기 루프를 사용해 한 번에 하나의 읽기만 실행했습니다. NVIDIA는 이를 Linux Native AIO로 교체하여 최대 128개의 읽기를 동시에 발행, NVMe 대역폭을 완전히 활용했습니다.
// AIO 기반 읽기 예시 (의사 코드)
struct iocb iocbs[128];
struct io_event events[128];
aio_context_t ctx = 0;
// 읽기 작업 준비
for (int i = 0; i < num_jobs; i++) {
io_prep_pread(&iocbs[i], fd, buf, size, offset);
}
// 배치 제출 (128개 동시)
io_submit(ctx, num_jobs, iocbs);
// 완료 대기
io_getevents(ctx, num_jobs, num_jobs, events, NULL);
결과는 인상적이었습니다:
| 모델 | 체크포인트 크기 | CRIU (기존) | CRIU (AIO+병렬) | 속도 향상 | SOL 대비 |
|---|---|---|---|---|---|
| Qwen3-0.6B | 6.2 GiB | 6.8초 | 2.4초 | 2.8x | 0.95초 |
| Qwen3-8B | 26 GiB | 24초 | 4.7초 | 5.1x | 1.8초 |
| gpt-oss-120b | 129 GiB | 119초 | 15초 | 7.9x | 11초 |
최적화 3: GPU Memory Service (GMS) - 병렬 가중치 복원
가장 큰 병목은 가중치 데이터가 CRIU 이미지에 포함되어 직렬로 CPU 메모리를 거쳐 GPU로 이동하는 점이었습니다. GMS는 CUDA VMM API를 사용해 가중치를 별도의 아티팩트로 분리합니다.
| 모델 | 기존 CRIU 크기 | GMS 적용 후 CRIU 크기 | GMS 가중치 아티팩트 |
|---|---|---|---|
| Qwen3-0.6B | 6.2 GiB | 4.3 GiB | 1.2 GiB |
| Qwen3-8B | 26 GiB | 4.8 GiB | 15 GiB |
| gpt-oss-120b | 129 GiB | 6.7 GiB | 74 GiB |
GMS를 사용하면 프로세스 상태 복원과 가중치 복원을 병렬로 수행할 수 있으며, GPUDirect Storage(GDS)나 NVLink 같은 고속 경로를 활용할 수 있습니다. 8개의 로컬 NVMe SSD에 스트라이핑한 PoC에서는 gpt-oss-120b 기준 21배의 시작 시간 단축을 달성했습니다.

실무 적용 시 주의사항 및 한계
Dynamo Snapshot은 현재 실험 단계이며, 다음과 같은 제약이 있습니다:
- 단일 GPU만 지원: vLLM, SGLang 워크로드에 한정. 멀티 GPU/멀티 노드는 향후 업데이트 예정
- CUDA 드라이버 패치 필요: GMS 복원 경로는 아직 pending 상태
- TensorRT-LLM 미지원: 현재는 vLLM과 SGLang만 가능
- 스토리지 백엔드 의존성: NFS/SMB 같은 공유 스토리지가 필요하며, O_DIRECT가 지원되지 않는 환경에서는 AIO 이점이 제한적
한국 개발 생태계에서의 적용 맥락
국내 클라우드/NCP 환경에서는 GPU 인스턴스의 콜드스타트가 특히 민감한 이슈입니다. 네이버 클라우드, KT Cloud 등에서 GPU 노드를 오토스케일링할 때, 기존에는 미리 웜 풀을 유지하거나 스케일 업/다운을 제한적으로 사용해야 했습니다. Dynamo Snapshot이 안정화되면 GPU 비용 최적화와 SLA 준수를 동시에 달성할 수 있을 것으로 기대됩니다.
다음 단계 학습 방향
- CRIU 심화 학습: 리눅스 프로세스 체크포인트/리스토어의 내부 동작 이해
- CUDA Virtual Memory Management:
cuMemCreate,cuMemMap,cuMemUnmapAPI 숙지 - Kubernetes Device Plugin: GPU 자원 관리 및 커스텀 스케줄러 설계
- NVIDIA Dynamo 오픈소스 프로젝트: GitHub 저장소 모니터링
함께 보면 좋은 글
- React Server Components 치명적 보안 취약점(CVE-2025-55182) 지금 당장 확인해야 할 사항
- Vercel AI Gateway에서 Recraft 이미지 모델 사용하기 고품질 생성 AI 통합
이 글은 NVIDIA Developer Blog의 내용을 분석하고, 실무 관점에서 재구성한 인사이트입니다.