본문 바로가기
삵
AI

vLLM 재시작에서 모델 로딩 건너뛰기

냉냉장고를사다줘·2026년 11월 10일·조회 0

프로덕션에서 vLLM 설정 하나 바꾸려고 엔진을 내렸다 올릴 때마다 가중치 로딩을 멍하니 기다린 적이 있을 것이다. 70B급을 몇 장에 샤딩해 놓으면 디스크에서 safetensors를 다시 읽고 양자화 후처리까지 도는 데 수십 초에서 몇 분이 그냥 날아간다. vLLM 0.31에 들어온 vllm preload 데몬이 바로 이 구간을 겨냥한다.

요약하면 이렇다. GPU마다 가중치 캐시 데몬을 하나씩 띄워 양자화와 텐서 병렬(TP) 샤딩까지 끝낸 가중치를 GPU 메모리에 상주시킨다. 엔진은 --load-format ipc_cache로 붙어서 디스크를 다시 읽는 대신 CUDA IPC 핸들로 그 메모리를 매핑한다. 기본 모드인 zero_copy에서는 엔진이 데몬의 할당을 그대로 공유하므로, 재기동이 가중치 로딩을 메모리 매핑 한 번으로 줄이고 GPU 메모리도 한 벌만 쓴다.

1. 재시작마다 디스크를 다시 읽던 구간

vLLM이 모델을 올릴 때 하는 일은 파일을 GPU에 복사하는 것으로 끝나지 않는다. safetensors를 디스크에서 읽어 들이고, dtype을 맞추고, FP8이나 AWQ 같은 양자화를 쓰면 가중치 후처리를 돌리고, 그 결과를 TP 랭크별로 쪼개 각 GPU에 올린다. 이 과정이 재시작할 때마다 똑같이 반복된다. 설정 한 줄 고치려고 vllm serve를 재기동해도 상태가 남지 않으니 처음부터 다시 한다.

가중치 캐시 데몬은 이 결과물을 프로세스 밖에 살려 둔다. GPU 한 장당 데몬 프로세스가 하나 뜨고, 그 데몬이 자기 랭크 몫의 샤드를 디스크에서 로드해 양자화 후처리를 마친 뒤, 모든 파라미터와 버퍼를 CUDA IPC 핸들로 export한다. 핸들은 유닉스 도메인 소켓으로 엔진에 넘긴다. 엔진이 내려갔다 올라와도 데몬은 그대로 떠 있으니 가중치는 GPU에 남아 있고, 새 엔진은 그 메모리를 매핑만 하면 된다.

플랫폼은 CUDA와 ROCm만 지원한다. 그리고 데몬과 엔진은 같은 노드에서 같은 사용자로 떠 있어야 한다. IPC 핸들을 소켓으로 주고받는 구조라 이 제약이 붙는다.

2. 데몬을 띄우고 엔진을 ipc_cache로 붙이기

버전부터 맞춘다. 가중치 캐시 데몬은 0.31.0에 들어왔다.

$ vllm --version
vLLM 0.31.0

먼저 GPU마다 데몬을 올린다. vllm preload는 vllm serve와 같은 엔진 인자를 받으므로 --model과 --tensor-parallel-size를 서빙할 때와 똑같이 준다. TP=4면 데몬 프로세스가 4개 뜬다.

$ vllm preload \
    --model meta-llama/Llama-3.1-8B-Instruct \
    --tensor-parallel-size 4
INFO weight_cache: starting 4 daemon(s), one per GPU rank
INFO weight_cache: rank 0 loading shard from disk, running post-process
INFO weight_cache: rank 0 exported 291 params as CUDA IPC handles
INFO weight_cache: serving on unix socket (socket_dir=/run/user/1000/...)
INFO weight_cache: ready

여기서 한 번 걸리기 쉽다. vllm preload는 포그라운드로 떠서 소켓을 물고 계속 살아 있어야 한다. 이게 죽으면 zero_copy로 매핑해 둔 엔진도 같이 무너진다. 그러니 실전에서는 preload를 별도 systemd 유닛이나 tmux 세션으로 상주시키고, 엔진은 그 뒤에서 붙였다 뗐다 한다.

엔진은 --load-format ipc_cache만 추가하면 된다. 모델과 TP 크기는 데몬과 똑같이 맞춘다.

$ vllm serve meta-llama/Llama-3.1-8B-Instruct \
    --tensor-parallel-size 4 \
    --load-format ipc_cache
INFO Loading weights via ipc_cache (mode=zero_copy)
INFO Mapped 291 params from weight cache daemon
INFO Model weights ready (no disk read)
INFO Starting vLLM API server on http://0.0.0.0:8000

이제 엔진을 내렸다 다시 올려 보면 차이가 바로 드러난다. 디스크 읽기와 양자화 후처리 구간이 통째로 빠지고, 가중치 로딩이 메모리 매핑으로 바뀐다. 수십 초에서 몇 분 걸리던 로딩이 초 단위로 줄어든다. 정확한 값은 모델 크기와 GPU에 따라 다르니, 서빙 로그의 로딩 시간 줄을 직접 보면 된다.

$ journalctl -u vllm-serve -n 20 | grep -i "weights\|loading"

3. 가중치가 한 벌만 올라갔는지 확인

데몬과 엔진이 같은 메모리를 공유하는지는 nvidia-smi로 바로 보인다. TP=4로 Llama-3.1-8B(bf16 약 16GB)를 올렸다면, 데몬 4개가 각자 자기 랭크 몫인 약 4GB씩만 들고 있어야 한다. 전체 16GB를 4로 나눈 값이다.

$ nvidia-smi
+-----------------------------------------------------------------------------+
| Processes:                                                                   |
|  GPU   GI   CI        PID   Type   Process name              GPU Memory      |
|                                                               Usage          |
|=============================================================================|
|    0   N/A  N/A     18231      C   vllm preload (rank 0)       3914MiB       |
|    1   N/A  N/A     18232      C   vllm preload (rank 1)       3914MiB       |
|    2   N/A  N/A     18233      C   vllm preload (rank 2)       3914MiB       |
|    3   N/A  N/A     18234      C   vllm preload (rank 3)       3914MiB       |
+-----------------------------------------------------------------------------+

랭크당 약 4GB가 맞다. 수치는 양자화 방식과 버퍼에 따라 조금씩 달라진다. zero_copy에서는 엔진이 데몬의 할당을 그대로 매핑하므로 GPU마다 가중치가 한 벌만 올라간다. 그래서 엔진을 재기동해도 가중치 몫의 메모리가 추가로 늘지 않는다. 다만 공유된 가중치는 엔진의 gpu_memory_utilization 예산에 포함되니, KV 캐시 여유를 계산할 때 이 점을 빼먹지 말자.

데몬이 아무 가중치나 넘겨주지는 않는다. 엔진이 붙을 때 지문(fingerprint)을 맞춰 본다. 지문에는 체크포인트 내용 해시, 모델 아키텍처, TP와 DP의 크기 및 랭크, dtype, 양자화 방식과 설정, RoPE 파라미터, 모델 리비전, KV 캐시 dtype, 그리고 vLLM 버전까지 들어간다. 하나라도 어긋나면 데몬 가중치를 쓰지 않고 디스크 로딩으로 되돌아간다. 즉 vLLM을 업그레이드하거나 양자화 설정을 바꾸면 preload 데몬도 새로 띄워야 한다는 뜻이다.

헬스 체크가 필요하면 데몬을 띄울 때 --weight-cache-health-port를 준다. 기본값은 비활성이라 포트를 지정해야 부모 프로세스의 /health 엔드포인트가 열린다. 호스트는 --weight-cache-health-host로, 기본값은 0.0.0.0이다.

4. zero_copy와 copy, 그리고 데몬이 죽었을 때

모드는 --model-loader-extra-config로 바꾼다. 기본은 zero_copy, 대안은 copy다.

$ vllm serve meta-llama/Llama-3.1-8B-Instruct \
    --tensor-parallel-size 4 \
    --load-format ipc_cache \
    --model-loader-extra-config '{"mode": "copy", "fallback": false}'

zero_copy는 엔진이 데몬의 GPU 메모리를 직접 공유한다. 메모리는 한 벌이지만 데몬이 엔진 수명 내내 살아 있어야 한다. 데몬이 내려가면 엔진이 매핑한 메모리도 사라진다.

copy는 엔진이 가중치를 자기 메모리로 복제하고, 복제가 끝나면 데몬이 그 캐시를 풀어 준다. 디스크를 다시 읽지 않는다는 이점은 같지만, 복제하는 순간 잠깐 가중치가 두 벌 존재하므로 메모리 여유가 필요하다. 데몬을 띄워 두고 싶지 않거나, 올린 뒤 데몬을 정리하고 싶을 때 쓴다.

fallback은 기본값이 true다. 데몬 소켓을 못 찾거나 지문이 어긋나면 조용히 디스크 로딩으로 되돌아간다. preload를 깜빡 안 띄워도 서빙은 뜨되 재시작이 느려질 뿐이다. 반대로 fallback: false로 두면 데몬 가중치를 못 쓸 때 기동이 실패한다. 캐시 적중을 반드시 보장해야 하는 환경에서는 이쪽이 낫다. 소켓 연결은 connect_timeout_s(기본 5.0초), 가중치 전송은 state_timeout_s(기본 300.0초)로 타임아웃을 건다.

한 가지 선은 분명히 긋고 가자. zero_copy가 살려 주는 것은 엔진 재시작이지 GPU 자체의 중단이 아니다. GPU를 리셋하거나 스팟 인스턴스가 회수되면 데몬의 GPU 메모리도 같이 날아간다. 그때는 preload 그룹 전체를 다시 띄워야 한다. 데몬을 강제 종료했다면 오래된 소켓 파일이 남을 수 있는데, 새 엔진을 붙이기 전에 preload 그룹을 통째로 재시작하면 깔끔하다.

정리하자면 설정 튜닝이나 코드 배포로 엔진만 자주 내렸다 올리는 환경이라면 preload 데몬을 상주시켜 두는 쪽이 재시작 시간을 가장 크게 줄여 준다. 다음으로 볼 만한 것은 --warmup-cache-stores와 데몬 간 전송(D2D) 옵션으로, 여러 노드에 걸쳐 같은 가중치를 미리 데워 두는 구성이다.

자주 묻는 질문

vllm preload는 어느 버전부터 쓸 수 있나

vLLM 0.31.0부터 들어왔다. vllm --version으로 확인하고, 0.31 미만이면 업그레이드해야 한다. 플랫폼은 CUDA와 ROCm만 지원한다.

데몬과 엔진을 다른 노드나 다른 사용자로 띄워도 되나

안 된다. CUDA IPC 핸들을 유닉스 도메인 소켓으로 주고받는 구조라 데몬과 엔진이 같은 노드에서 같은 사용자로 떠 있어야 한다.

vLLM을 업그레이드했는데 preload가 효과가 없다

지문 검증에 vLLM 버전이 포함된다. 버전이 바뀌면 지문이 어긋나 디스크 로딩으로 되돌아간다. dtype이나 양자화 설정을 바꿀 때도 마찬가지이니 preload 데몬을 새로 띄워야 한다.

zero_copy와 copy 중 무엇을 쓰나

엔진을 자주 재시작하며 데몬을 상주시킬 수 있으면 기본값 zero_copy가 메모리를 한 벌만 쓰고 가장 빠르다. 데몬을 띄워 두기 싫거나 로딩 후 캐시를 풀고 싶으면 copy를 쓰되, 복제 순간 가중치가 잠깐 두 벌 존재하므로 메모리 여유가 필요하다.

스팟 인스턴스가 회수되면 캐시도 살아남나

아니다. zero_copy가 보존하는 것은 엔진 재시작이지 GPU 중단이 아니다. GPU 리셋이나 스팟 회수로 GPU 메모리가 비워지면 데몬 가중치도 사라지므로 preload 그룹 전체를 다시 띄워야 한다.

관련 글

댓글 0

로그인 후 댓글을 남길 수 있습니다.

아직 댓글이 없습니다.