mallo

djev-run은 왜 DiffusionGemma-Jev를 Cloud Run에 올리는 일을 두 가지 방식으로 나눠 설명할까요?

요약 듣기

0:006:35

원문 보기github.com
요약 보기

이 저장소가 하려는 일은 비교적 분명해요. 작성자는 DiffusionGemma-Jev, 즉 `djev`를 Google Cloud Run에서 NVIDIA RTX PRO 6000 Blackwell GPU로 서빙하는 방법을 정리해 두었고, 바탕에는 `mmastrac/djev`가 있다고 밝혀요. 여기에 끝이 아니라 `/snake`, `/dino`, `/tetris` 같은 데모 앱도 함께 넣어 두었어요. 그러니까 이 저장소는 단순히 모델 파일을 보관하는 곳이 아니라, 특정 GPU 환경에서 모델을 띄우고 바로 만져 볼 수 있는 예제까지 포함한 배포 안내서에 가까워요. 여기서 낯선 용어를 원문 맥락대로만 풀어보면, Cloud Run은 모델을 실행해 두는 자리이고, GCS 버킷은 그 모델 가중치 파일을 올려 두는 저장소 역할을 해요.

배포를 두 단계로 나눈 이유도 자연스럽게 이어져요. 먼저 모델을 GCS에 올리고, 그다음 Cloud Run 서비스가 그 파일을 읽어 오도록 만들어요. 작성자는 GPU가 지원되는 리전도 예시로 제한해 두었고, 실제 명령에서도 버킷을 만든 뒤 Hugging Face에서 모델을 내려받아 GCS의 `dgemma` 경로로 복사하게 해요. 그다음에는 두 갈래예요. 하나는 미리 만들어 둔 `djev-run` 컨테이너를 쓰는 방식이고, 다른 하나는 `vllm-openai:nightly`라는 원본 컨테이너를 거의 그대로 쓰는 방식이에요. 이해를 위해 비유해 보면, 전자는 자주 쓰는 조리도구와 동선이 이미 세팅된 주방을 빌리는 쪽에 가깝고, 후자는 빈 주방에 들어가 필요한 순서를 직접 맞추는 쪽에 가까워요. 그래서 전자는 `/dev/shm`으로 옮기는 작업이나 `vllm serve` 옵션이 미리 구워져 있다고 설명하고, 후자는 명령행 인자와 환경변수를 사용자가 직접 적어 넣게 해요.

이 저장소에서 가장 중요한 부분은 왜 이렇게 많은 플래그를 붙이느냐예요. 작성자는 이것이 Cloud Run GPU 모범 사례를 따른다고 적어 두고, 특히 시작 지연과 가중치 적재 비용을 줄이는 쪽에 신경을 많이 써요. 설명을 따라가 보면 GCS를 볼륨으로 붙인 뒤, 버퍼드 리드를 켜고 모델 파일을 `/dev/shm`으로 복사해요. 작성자 설명으로는 이렇게 해야 18GB짜리 safetensors 조각들을 GCS에서 순차적으로 미리 읽어 RAM으로 올린 다음 vLLM을 시작할 수 있어요. 또 `--no-cpu-throttling`은 가중치 로딩과 스케줄링 동안 20 vCPU를 계속 쓰기 위한 것이고, `--no-gpu-zonal-redundancy`는 표준 리전 RTX PRO 6000 할당량에 필요하다고 적어요. 런타임 쪽에서도 이유를 하나씩 달아요. `VLLM_ENABLE_V1_MULTIPROCESSING=0`은 Python/CUDA 모듈을 두 번 가져오지 않기 위해서, `--enforce-eager`는 시작 시 `torch.compile`과 CUDA graph capture를 건너뛰기 위해서, `--language-model-only`는 사용하지 않는 SigLIP 비전 인코더 적재와 프로파일링을 생략하기 위해서예요. `--kv-cache-memory=2G`는 시작할 때 메모리 프로파일링용 forward pass를 건너뛰도록 고정 크기 KV 캐시를 미리 잡아 두는 설정이라고 설명하고, `--attention-backend=TRITON_ATTN`은 DiffusionGemma에 필요한 양방향 어텐션 백엔드라고 적어요. 마지막으로 `canvas_length`를 128로 둔 diffusion 설정은 이 모델이 128토큰 병렬 diffusion canvas를 쓰도록 맞추는 부분이에요. 즉, 이 저장소의 핵심 주장은 단순히 '배포된다'가 아니라, 모델 특성과 Cloud Run의 시작 비용을 함께 고려해 서빙 경로를 세밀하게 맞췄다는 데 있어요.

모델에 질의하는 방식도 배포 선택에 맞춰 두 갈래로 갈려요. 미리 만든 `djev-run` 컨테이너를 썼다면 `/v1/systemone` 하나로 비교적 높은 수준의 JSON을 보내면 돼요. 여기서는 `state`와 `questions`를 받는 미들웨어가 토큰 슬롯 수를 계산하고, 128토큰 캔버스를 만들고, 내부의 vLLM 생성 엔드포인트를 호출한 뒤, 로그확률을 사람이 바로 읽을 수 있는 범주와 점수로 정리해 준다고 설명해요. 예시에 나온 분류 요청도 바로 이런 형태예요. 반대로 원본 `vllm-openai:nightly` 컨테이너를 직접 쓰면, 먼저 `/tokenize`로 프롬프트를 토큰으로 바꾼 다음, 그 토큰을 `diffusion_seed_canvas`에 넣어 `/v1/chat/completions`를 다시 호출해야 해요. 여기에 `diffusion_pinned`, `diffusion_max_steps`, `diffusion_read_only` 같은 항목도 명시적으로 넣어요. 같은 분류 작업을 하더라도 전자는 '질문 의도'를 중심으로 보내고, 후자는 '모델이 이해하는 토큰 단위 구조'를 직접 맞춰 줘야 한다는 차이가 있는 셈이에요. 예시의 긴 숫자 배열은 그런 수동 작업이 얼마나 저수준인지 보여 주지만, 배열 안의 `1`들이 정확히 어떤 의미를 갖는지는 원문만으로는 풀어 설명하지 않아요.

조건과 한계도 같이 봐야 해요. 첫째, 이 문서는 아무 GPU나 아무 리전을 전제로 하지 않아요. RTX PRO 6000 지원 리전을 따로 적어 두고, 표준 리전 할당량에서는 특정 플래그가 필요하다고 못 박아요. 둘째, 예시 배포 설정은 인스턴스 수와 동시성, 포트, 스타트업 프로브 같은 운영값까지 꽤 구체적으로 고정해 두었는데, 이것이 모든 상황의 정답이라는 뜻까지는 원문이 말하지 않아요. 셋째, 일부 최적화는 모델과 하드웨어 전제에 묶여 있어요. 예를 들어 FlashInfer MoE 백엔드는 Blackwell SM120을 겨냥한다고 설명하고, `TRITON_ATTN`은 DiffusionGemma에 필요하다고 적어요. 또 `--language-model-only`는 비전 인코더가 쓰이지 않는 경우에만 의미가 있어 보여요. 마지막으로 작성자 스스로도 두 선택지를 구분해 놓았듯이, 편한 사용성을 원하면 미리 만든 컨테이너가 낫고, 순수한 표준 vLLM 경로를 원하면 원본 컨테이너가 맞지만, 그만큼 토큰화와 diffusion 입력을 직접 다뤄야 해요. 그러니 이 저장소는 '아무 모델 배포법'이라기보다, 특정 모델을 특정 GPU 환경에서 비교적 빠르게 띄우려는 목적에 맞춘 레시피로 읽는 편이 정확해요.