Gemini

Gemma 4 E4B vLLM으로 서빙 with MTP

cell-devlog 2026. 6. 10. 13:21
반응형

Gemma 4 E4B 로컬 서빙 세팅하면서 MTP까지 한 번에 붙여봤습니다. 


핵심 요약

→ vLLM 공식 이미지 중 vllm/vllm-openai:gemma4-unified 태그가 Gemma 4 전용 
→ Gemma 4 E4B는 MoE 아키텍처 — GPU 1장(tensor-parallel-size 1)으로 서빙 가능
→ --max-model-len 32768 — 풀 컨텍스트(128K)는 VRAM 부족, 32K가 실용적인 타협점
→ --gpu-memory-utilization 0.9 — 0.95 이상은 OOM 리스크, 0.9가 안정적
→ MTP(Multi-Token Prediction) 추측 디코딩으로 처리 속도 체감 20~30% 향상
→ MTP 드래프트 모델은 메인 모델과 동일 경로 사용 가능 — 별도 모델 불필요
→ num_speculative_tokens: 2 — 3 이상은 드래프트 정확도 떨어져 오히려 느려짐
→ --allowed-local-media-path — 멀티모달 입력(이미지) 쓸 때 필수, 없으면 로컬 파일 거부
→ --shm-size="20g" — vLLM 공유 메모리 부족 시 워커 크래시 나므로 넉넉하게
→ OpenAI 호환 API로 뜨기 때문에 기존 ChatGPT API 코드 그대로 연결 가능


실전 1 — 전체 도커 실행 명령 한 줄씩 해석

docker run -itd --name ${CONTAINER_NAME} \
    -p 8000:8000 \           # 호스트 8000 → 컨테이너 8000 포트 포워딩
    --ipc=host \              # 프로세스 간 공유 메모리, vLLM 워커 통신 필수
    --cpus="16" \             # CPU 코어 16개 제한
    --cpu-shares=512 \        # 다른 컨테이너와 CPU 경쟁 시 상대적 우선순위
    --memory="40g" \          # 메모리 하드 상한
    --memory-swap="40g" \     # swap 0 — 메모리 초과 시 OOM killer 즉시 개입
    --memory-reservation="20g" \  # 소프트 하한, 시스템 압박 시 여기까지 확보
    --shm-size="20g" \        # 공유 메모리 — vLLM 텐서 공유에 사용
    --oom-score-adj=300 \     # OOM 발생 시 이 컨테이너 먼저 죽임 (시스템 보호)
    --gpus '"device=1"' \     # GPU 1번 단독 할당
    -v /DATA1/son/models_file:/root/.cache/huggingface/models \  # 모델 마운트
    -v /DATA1/son:/DATA1/son \   # 멀티모달용 로컬 파일 접근
    vllm/vllm-openai:gemma4-unified \   # Gemma 4 전용 vLLM 이미지
        --model /root/.cache/huggingface/models/gemma-4-E4B-it \
        --tensor-parallel-size 1 \
        --max-model-len 32768 \
        --gpu-memory-utilization 0.9 \
        --host 0.0.0.0 \
        --port 8000 \
        --allowed-local-media-path /my/local_media \
        --speculative-config '{"method":"mtp","model":"/root/.cache/huggingface/models/gemma-4-E4B-it-assistant","num_speculative_tokens":2}'

개념 박스 — --ipc=host 왜 필요한가 vLLM은 내부적으로 여러 워커 프로세스 간 텐서를 공유 메모리로 주고받습니다. 기본 Docker IPC 격리 모드에서는 이 채널이 막혀 워커 초기화에서 hang이 걸립니다. --ipc=host로 호스트와 IPC 네임스페이스를 공유해야 정상 기동됩니다.


실전 2 — MTP 추측 디코딩 설정

MTP(Multi-Token Prediction)는 드래프트 모델이 다음 토큰 여러 개를 미리 예측하고, 메인 모델이 검증하는 방식으로 속도를 끌어올립니다.

{
  "method": "mtp",
  "model": "/root/.cache/huggingface/models/gemma-4-E4B-it-assistant",
  "num_speculative_tokens": 2
}

여기서 핵심은 model 경로입니다. 드래프트 모델로 메인 모델과 다른 경량 모델을 쓰는 게 일반적이지만, Gemma 4 MTP는 동일 계열 모델을 드래프트로 써도 동작합니다. -assistant suffix 모델을 드래프트로 쓰는 이유는 instruction 튜닝된 버전이 토큰 예측 패턴이 메인 모델과 더 유사하기 때문입니다.

num_speculative_tokens 값별 특성

1 → 속도 향상 미미, MTP 의미 없음
2 → 속도 20~30% 향상, 드래프트 정확도 유지 (권장)
3 → 드래프트 오검증 빈도 올라가 오히려 느려지는 케이스 발생
4+ → 대부분의 경우 손해

개념 박스 — MTP가 왜 빠른가 일반 자기회귀 디코딩은 토큰을 한 번에 하나씩 생성합니다. 매 스텝마다 전체 모델을 한 번 forward pass해야 해서 느립니다. MTP(Multi-Token Prediction)는 모델이 학습 시점부터 다음 토큰 N개를 동시에 예측하도록 훈련된 구조입니다. 추론 시에도 1 forward pass에서 토큰 여러 개를 한꺼번에 뽑아낼 수 있어서 처리량이 올라갑니다. 드래프트 모델이 따로 필요한 Speculative Decoding과 달리, MTP는 메인 모델 자체가 멀티 토큰을 뽑는 구조라서 별도 모델 없이도 동작합니다. num_speculative_tokens: 2는 한 번에 2개 토큰을 동시 생성한다는 의미입니다.


실전 3 — 컨테이너 기동 후 확인

# 컨테이너 로그 확인 (MTP 초기화 메시지 체크)
docker logs -f ${CONTAINER_NAME}

# 정상 기동 시 로그에 이런 라인이 떠야 함
# INFO: Started server process
# INFO: Waiting for application startup.
# INFO: Application startup complete.
# Speculative decoding (MTP) initialized with 2 draft tokens

# 헬스체크
curl http://localhost:8000/health

# 모델 목록 확인
curl http://localhost:8000/v1/models

실전 4 — API 연결 및 멀티모달 테스트

OpenAI 호환 API로 뜨기 때문에 기존 코드에서 base_url과 api_key만 바꾸면 됩니다.

from openai import OpenAI

client = OpenAI(
    base_url="http://localhost:8000/v1",
    api_key="dummy"  # vLLM은 api_key 검증 안 함, 아무 값이나
)

# 텍스트 추론
response = client.chat.completions.create(
    model="/root/.cache/huggingface/models/gemma-4-E4B-it",
    messages=[
        {"role": "user", "content": "파이썬으로 피보나치 수열 짜줘"}
    ],
    max_tokens=1024,
    temperature=0.7
)
print(response.choices[0].message.content)
# 멀티모달 — 로컬 이미지 파일 직접 전달
# --allowed-local-media-path 설정된 경로 내 파일만 가능
response = client.chat.completions.create(
    model="/root/.cache/huggingface/models/gemma-4-E4B-it",
    messages=[
        {
            "role": "user",
            "content": [
                {
                    "type": "image_url",
                    "image_url": {
                        "url": "file:///my/local_media/test_image.png"
                    }
                },
                {
                    "type": "text",
                    "text": "이 이미지에서 뭐가 보여?"
                }
            ]
        }
    ],
    max_tokens=512
)
print(response.choices[0].message.content)

개념 박스 — --allowed-local-media-path 왜 필요한가 vLLM은 보안 상 기본적으로 file:// 프로토콜로 로컬 파일 접근을 차단합니다. --allowed-local-media-path로 허용할 디렉토리를 명시해야 해당 경로 내 파일에 접근 가능합니다. Base64 인코딩해서 data URL로 넘기면 이 설정 없이도 동작하지만, 큰 이미지는 요청 크기가 폭발합니다.


실전 5 — 자주 나오는 오류와 해결

# 오류 1: CUDA OOM
# CUDA out of memory. Tried to allocate ...
# → gpu-memory-utilization 낮추거나 max-model-len 줄이기
--gpu-memory-utilization 0.85
--max-model-len 16384

# 오류 2: SharedMemory 부족으로 워커 크래시
# [Errno 28] No space left on device (shared memory)
# → --shm-size 늘리기
--shm-size="24g"

# 오류 3: MTP 드래프트 모델 경로 못 찾음
# FileNotFoundError: model path not found
# → 컨테이너 내부 마운트 경로로 지정했는지 확인
# 호스트 경로 아닌 /root/.cache/huggingface/models/... 기준

# 오류 4: 멀티모달 로컬 파일 접근 거부
# ValueError: Local file access not allowed
# → --allowed-local-media-path 경로가 실제 파일 경로를 포함하는지 확인
# /DATA1/son 하위 파일이면 --allowed-local-media-path /my/local_media

# 오류 5: gemma4-unified 이미지 없음
# Unable to find image 'vllm/vllm-openai:gemma4-unified'
# → docker pull vllm/vllm-openai:gemma4-unified 먼저 실행

실전 6 — 리소스 튜닝 포인트

현재 설정 기준으로 조정 가능한 파라미터입니다.

# VRAM 16GB 이하 환경
--max-model-len 8192          # 컨텍스트 줄이기
--gpu-memory-utilization 0.85

# VRAM 24GB 이상 환경
--max-model-len 65536         # 컨텍스트 늘리기 가능
--gpu-memory-utilization 0.92

# 처리량 우선 (배치 크기 늘리기)
--max-num-seqs 64             # 동시 처리 시퀀스 수 (기본 256)

# 지연 시간 우선 (MTP 토큰 수 조정)
# speculative-config의 num_speculative_tokens를 1로 낮추기

✅ 이 설정이 잘 맞는 경우

✅ GPU 1장으로 Gemma 4 E4B 서빙이 필요한 환경
✅ 멀티모달(이미지 입력) + 텍스트 혼용 파이프라인
✅ 기존 OpenAI API 코드를 그대로 로컬로 전환하고 싶을 때
✅ MTP로 처리량 개선이 필요한 배치성 추론

❌ 다른 설정을 고려해야 하는 경우

❌ VRAM 12GB 이하 — max-model-len 더 줄이거나 양자화 버전 필요
❌ GPU 2장 이상 쓸 수 있는 환경 — tensor-parallel-size 2로 풀 컨텍스트(128K) 가능
❌ 실시간 스트리밍 응답 — stream=True 별도 처리 필요


 

반응형