DL/LLM

[LLM] 로컬 vLLM Gemma 4 서빙 팁 (feat. Docker 기반 세팅 가이드)

moonzoo 2026. 4. 8. 01:20

Gemma 4가 성능이 좋다고 입소문 타면서 개인적으로 문의해주시는 분들이 많으시더라고요. 그래서 간단하게 vLLM과 도커로 Gemma4 서빙하는 방법 알려드리고자 왔습니다.


1. 왜 vLLM v0.19.0이어야 하는가

1-1. Gemma 4 아키텍처 정식 지원은 v0.19.0이 최초

Gemma 4는 기존 Gemma 시리즈와 아키텍처가 크게 달라졌습니다. MoE(128개 fine-grained experts, top-8 routing), Dual Attention(슬라이딩 윈도우 로컬 + 글로벌 어텐션 교차 배치, head dimension 256/512 이종 구성), 네이티브 멀티모달(텍스트/이미지/오디오) 등이 새로 도입되었습니다. vLLM은 이러한 Gemma4를 지원하기 위해 발빠르게 움직여서 vllm/vllm-openai:gemma4를 배포했습니다.

 

그런데 vLLM 공식 Usage Guide에서 권장하는 vllm/vllm-openai:gemma4로 docker pull을 하면 0.18.2rc1.dev73버전의 vllm이 설치되는데, 이게 좀 불안정합니다. 출력에 유니코드 포함, Tool calling 이슈 등 문제가 있더라고요. vLLM 깃허브 이슈만에서 #38826 #38847 요런거 보실 수 있습니다.

 

그래서 이러한 문제들을 반영하여 v0.19.0를 Release했습니다. 근데 문제는 transformers>=5.5.0이 필수인데 vllm-openai:v0.19.0에는 transformers>=5.5.0이 설치가 안되어있어서 Dockerfile 작성해서 다시 build하셔야 합니다. 

 

Gemma 4 아키텍처 자체가 transformers 4.x에는 존재하지 않으며, v0.19.0에서 Transformers v5와의 호환성 문제가 다수 수정되었습니다 (#37681, #38127, #38090, #38247, #38410). v0.18.x 이하에서는 모델 config 자체를 인식하지 못해 로딩이 불가능합니다.


1-2. 네이티브 Tool Calling과 Reasoning Parser

Gemma 4는 <|tool_call>, <tool_call|> 등 전용 special token을 사용하는 독자적인 Tool Call 프로토콜을 갖추고 있으며, 문자열 구분자로 <|"|>을 사용합니다. v0.19.0에서 이 프로토콜을 파싱하는 gemma4 Tool Call Parser와 gemma4 Reasoning Parser가 추가되었습니다.


2. Docker 이미지 선택: :gemma4 vs :v0.19.0

2-1. 두 태그의 차이

 

태그 내부 vLLM 버전 transformers 특징
vllm/vllm-openai:gemma4 0.18.2rc1.dev73 5.5.0 포함 v0.19.0 릴리스 전 Gemma 4 Day-0 프리빌드
vllm/vllm-openai:v0.19.0 0.19.0 정식 별도 설치 필요 448개 커밋, 197명 기여자의 모든 수정 포함

:gemma4 태그는 transformers 5.5.0이 이미 포함되어 있어 설치 없이 바로 쓸 수 있다는 장점이 있지만, 내부적으로 v0.18.2 개발 브랜치 스냅샷입니다.

2-2. :gemma4 이미지에서 확인된 버그들

:gemma4 이미지(vLLM 0.18.2rc1.dev73)를 기반으로 실제 서빙 시 다음과 같은 버그들이 보고되었습니다. 이 버그들은 v0.19.0 이후 main 브랜치에서 수정 PR이 머지되었거나 진행 중입니다. (사실 정확히는 모르는데 저도 당일 아침에 바로 :gemma4 이미지로 테스트 해봤는데, 유니코드 출력, tool calling, 출력 형식을 따르지 않는 등 여러 문제가 있어서 커스텀을 진행하여 사용하고 있었습니다.) 

 

Tool Calling 관련 버그:

이슈 #38946에서는 스트리밍 모드에서 Tool Call 시 <|"|> 문자열 구분자를 파서가 제대로 처리하지 못해 invalid JSON이 생성되는 문제가 보고되었습니다. 예를 들어 <|"|>index.html<|"|>이 스트리밍 청크로 쪼개지면서 파서가 중간 상태를 잘못 해석하여 깨진 JSON이 출력되었습니다. 이 버그는 PR #38992로 수정되어 main에 머지되었습니다.

 

이슈 #38910에서는 Tool Call 인자에 HTML 컨텐츠가 포함될 경우 <html>이 <<html>로, <meta>가 <<meta>로 중복되는 현상이 보고되었습니다. 스트리밍 파서의 _buffer_delta_text() 반환값이 current_text 재구성에 잘못 사용되면서 발생한 문제이며, PR #38909로 수정이 제출되었습니다.

 

이슈 #39089에서는 vllm/vllm-openai:gemma4 이미지에서 boolean 값 true가 "trutrue"로 변환되어 JSON 파싱이 불가능해지는 문제가 보고되었습니다. 스트리밍/비스트리밍 모두에서 동일하게 발생하며, _parse_gemma4_value()가 부분적으로 스트리밍된 boolean 토큰을 bare string으로 잘못 인식한 것이 원인입니다.

 

이슈 #39069에서는 오프라인 추론 경로의 gemma4_utils._parse_tool_arguments()가 <|"|> 구분자를 일반 "로 치환한 뒤 fallback regex [^"]*를 사용하여, 문자열 내부에 따옴표가 포함되면 값이 잘리는(truncation) 문제가 확인되었습니다. <div class="main">hello</div>가 <div class=로 잘리는 식입니다.

 

Reasoning Parser 버그:

이슈 #38855에서는 Gemma4 Reasoning Parser가 reasoning_content 필드를 채우지 못하고 모든 thinking 내용이 content에 합쳐져 나오는 문제가 보고되었습니다. 모델은 <|channel>thought\n...<channel|> 토큰을 정상 생성하지만, vLLM의 텍스트 디코딩이 skip_special_tokens=True로 이 토큰들을 제거한 뒤 파서에 전달하기 때문에 파서가 channel 마커를 인식할 수 없었습니다. Qwen3ReasoningParser와 달리 start_token_id/end_token_id 기반 토큰 레벨 매칭이 구현되지 않은 것이 근본 원인입니다.

 

요약 테이블 (NVIDIA 포럼 DGX Spark 벤치마크 스레드 기준):


 

이슈 증상 상태
#38946 스트리밍 Tool Call 시 invalid JSON 생성 PR #38992 머지됨
#38855 reasoning_content 항상 null 수정 모드 공개, 패치 적용 가능
#38910 스트리밍 HTML 태그 중복 (<<html>) PR #38909 제출됨
#39089 boolean true가 "trutrue"로 변환 원인 분석 완료, 파서 수정 필요
#39069 오프라인 추론 시 따옴표 포함 문자열 잘림 PR 제출 진행 중

 


2-3. 다른 추론 엔진에서도 유사한 문제가 보고됨

Gemma 4의 독자적인 Tool Call 포맷(<|"|> 구분자, unquoted key 등)은 vLLM 외 다른 추론 엔진에서도 파싱 문제를 일으켰습니다.

 

Ollama에서는 이슈 #15241에서 gemma4.go 파서가 tool name을 깨뜨리는 문제가 보고되었습니다. gemma4.go:287에서 invalid character 'p' after object key:value pair 에러가 발생하며 tool call 파싱이 실패합니다. Ollama PR #15255에서 내부 따옴표가 포함된 인자를 올바르게 이스케이프하는 수정이 제출되었습니다.

 

llama.cpp에서도 유사한 파서 버그가 있어 PR ggml-org/llama.cpp#21418로 Gemma 4 전용 파서가 수정되었으며, --jinja 플래그를 사용해야 tool calling이 정상 동작합니다. (다른 방법도 있거나 이제는 지원할 수도 있습니다.)

 

mlx-lm에서는 이슈 ml-explore/mlx-lm#1096에서 Gemma 4의 네이티브 tool call 토큰이 파싱되지 않아 OpenAI 호환 tool_calls 필드가 비어 있는 문제가 보고되었습니다.

 

이는 Gemma 4의 tool call 포맷이 기존 모델들과 상당히 다르기 때문에 발생하는 공통 현상입니다. vLLM v0.19.0은 이 포맷에 대한 전용 파서를 포함한 첫 번째 정식 릴리스이며, 이후 main 브랜치에서 후속 버그 수정이 계속 머지되고 있습니다.


3. 권장 구성: v0.19.0 + transformers 설치

위 상황을 종합하면, 현시점 권장 구성은 다음과 같습니다.

:gemma4 태그(0.18.2 dev)는 transformers가 내장되어 있지만 위에 나열한 파서 버그들이 포함되어 있습니다. :v0.19.0 태그는 정식 릴리스로서 일부 수정이 반영되어 있지만, transformers 5.5.0은 별도 설치가 필요합니다. 가장 안전한 방법은 v0.19.0 이미지 위에 transformers를 설치한 커스텀 이미지를 사용하되, 가능하면 main 브랜치의 최신 nightly를 사용하는 것입니다.

3-1. Dockerfile 작성

FROM vllm/vllm-openai:v0.19.0
RUN pip install --no-cache-dir "transformers>=5.5.0"

3-2. 이미지 빌드

docker build -t vllm-openai:v0.19.0-gemma4 .

4. Docker Compose 설정

H100 DGX 환경에서 Gemma 4 31B를 GPU 2장(TP=2)으로 서빙하는 예시입니다.

Copyoffline-llm:
    profiles: ["model-serving"]
    image: vllm-openai:v0.19.0-gemma4
    container_name: container_name
    ports:
      - "8888:8888"
    volumes:
      - model/gemma-4-31B-it:/workspace
    environment:
      NVIDIA_VISIBLE_DEVICES: "3,4"
      CUDA_VISIBLE_DEVICES: "3,4"
    entrypoint:
      - bash
      - -c
      - >
        python3 -m vllm.entrypoints.openai.api_server
        --model /workspace/gemma-4-31B-it
        --served-model-name google/gemma-4-31B-it
        --host 0.0.0.0
        --port 8888
        --dtype bfloat16
        --max-model-len 130172
        --tensor-parallel-size 2
        --max_num_batched_tokens 32768
        --max_num_seqs 16
        --trust-remote-code
        --enable-prefix-caching
        --gpu-memory-utilization 0.95
        --generation-config vllm
        --enable-auto-tool-choice
        --tool-call-parser gemma4
        --reasoning-parser gemma4
        --default-chat-template-kwargs '{"enable_thinking": false}'
    restart: unless-stopped
    ipc: host
    deploy:
      resources:
        reservations:
          devices:
            - driver: nvidia
              count: all
              capabilities: [gpu]

주요 옵션 설명

--dtype bfloat16은 H100에서 BF16 연산을 활용합니다. 

--tensor-parallel-size 2는 31B 모델을 GPU 2장에 분산합니다. 

--gpu-memory-utilization 0.95는 KV Cache 할당 비율을 높여 동시 처리량을 확보합니다.

--enable-prefix-caching은 반복되는 시스템 프롬프트의 prefix를 캐싱하여 연산량을 줄입니다.

--generation-config vllm은 HuggingFace 모델의 내장 generation config 대신 vLLM 기본 샘플링 파라미터를 사용합니다. (이거는 상황에 따라 권장하는게 다릅니다.)

--enable-auto-tool-choice --tool-call-parser gemma4는 Gemma 4의 네이티브 Tool Calling을 활성화합니다. 

--reasoning-parser gemma4는 Thinking 모드의 사고 과정을 reasoning_content 필드로 분리합니다. 

--default-chat-template-kwargs '{"enable_thinking": false}'는 기본적으로 Thinking 모드를 비활성화합니다.

Thinking 모드를 켜면 추론 토큰이 추가로 생성되어 속도가 느려지고, 프론트엔드/에이전트 프레임워크에서 thinking 토큰이 JSON 파서를 깨뜨리는 사례도 보고되었으므로, 필요한 요청에서만 켜는 것이 안정적입니다.

 

아직 Thinking은 뭐랄까 불안정하더라고요. 좀 더 설정하면 될 거같긴한데... 시간이 없어서 ㅎㅎ.. 추론끄고 해도 잘됩니다.


5. Thinking 모드 제어

기본 비활성화 상태에서, 특정 요청에만 Thinking을 켜려면 요청 body에 다음을 포함합니다.

{
  "chat_template_kwargs": {"enable_thinking": true}
}

다만 이슈 #38855에서 보고된 것처럼, 현재 reasoning_content가 null로 반환되고 thinking 내용이 content에 합쳐져 나오는 문제가 있습니다. 이 버그는 main 브랜치에서 수정이 진행 중이므로, Thinking 모드를 적극 활용하려면 nightly 이미지 사용을 고려해야 합니다.

 

7. 참고 자료

 

항목 링크
vLLM v0.19.0 릴리스 노트 github.com/vllm-project/vllm/releases/tag/v0.19.0
vLLM 공식 Gemma 4 가이드 docs.vllm.ai/projects/recipes/en/latest/Google/Gemma4.html
Google Gemma 4 Function Calling 가이드 ai.google.dev/gemma/docs/capabilities/text/function-calling-gemma4
Gemma 4 Tool Call Parser API docs.vllm.ai/en/latest/api/vllm/tool_parsers/gemma4_tool_parser
Ollama Gemma 4 Tool Call 이슈 github.com/ollama/ollama/issues/15241

마무리

Gemma 4를 vLLM에서 서빙하려면 v0.19.0이 최소 요구 버전입니다. :gemma4 프리빌드 이미지는 transformers가 내장되어 편리하지만, 0.18.2 개발 빌드 기반이라 tool call 파서의 JSON 깨짐, reasoning_content null, boolean 값 변환 오류 등의 버그가 포함되어 있습니다.

 

v0.19.0 정식 이미지에 transformers 5.5.0을 추가 설치한 커스텀 이미지를 사용하는 것이 현재로서는 가장 안정적인 구성입니다. 다만 Gemma 4 관련 파서 버그 수정이 v0.19.0 이후에도 계속 main 브랜치에 머지되고 있으므로, 프로덕션 환경에서는 nightly 이미지의 변경사항도 함께 추적하시는 것을 권장합니다.