얼마 전 Google에서 TranslateGemma라는 모델이 오픈소스로 공개됐다. Gemma 3를 기반으로 한 번역 특화 모델인데, 55개 언어를 지원하고 텍스트뿐만 아니라 이미지 속 텍스트까지 추출해서 번역할 수 있다는 점이 인상적이었다. 게다가 4B, 12B, 27B 등 다양한 크기로 제공되어서 개인 노트북이나 클라우드 환경에서도 충분히 사용할 수 있다.
"이거 한번 써봐야겠다!" 싶어서 공식 문서대로 테스트해 보기로 했다. 그런데 이 모델을 돌려보다가 이상한 문제를 발견했다. 짧은 문장에 대한 번역을 생성하는데 응답 시간이 비정상적으로 오래 걸리는 것이다. 처음에는 "GPU가 느린가? 모델 자체가 무거운가?" 싶었는데, raw 로그를 확인해 보니 내부적으로 <end_of_turn> 토큰을 끝없이 생성하고 있었다. 알고 보니 EOS 토큰 설정을 명시하지 않으면 이런 일이 발생한다.
이 글에서는 TranslateGemma가 무엇인지, 그리고 이 모델에서 발견한 EOS 토큰 성능 이슈에 대해 정리해봤다. 왜 이런 문제가 발생하고, 어떻게 해결할 수 있는지 살펴보자.
TranslateGemma란?
TranslateGemma는 Google에서 개발한 경량 오픈소스 번역 모델 제품군이다. Gemma 3 모델을 기반으로 번역 태스크에 특화된 파인튜닝을 거쳤다.
주요 특징
| 특징 | 설명 |
|---|---|
| 기반 모델 | Gemma 3 (Google의 최신 오픈소스 LLM) |
| 모델 크기 | 4B, 12B, 27B 파라미터 |
| 지원 언어 | 55개 언어 (ISO 639-1 코드 지원) |
| 입력 형식 | 텍스트, 이미지 (896x896) |
| 컨텍스트 | 최대 2K 토큰 |
| 배포 환경 | 노트북, 데스크톱, 개인 클라우드 |
일반적인 번역 모델과 달리, TranslateGemma는 이미지에서 텍스트를 추출해서 번역할 수 있다는 점이 특별하다. 예를 들어 체코어로 된 도로 표지판 사진을 입력하면, 텍스트를 인식해서 독일어로 번역해 준다.
사용 예시
공식 문서에 나온 코드를 그대로 따라해봤다:
import torch
from transformers import AutoModelForImageTextToText, AutoProcessor
model_id = "google/translategemma-27b-it"
processor = AutoProcessor.from_pretrained(model_id)
model = AutoModelForImageTextToText.from_pretrained(model_id, device_map="auto")
# ---- 텍스트 번역 ----
messages = [
{
"role": "user",
"content": [
{
"type": "text",
"source_lang_code": "cs", # 체코어
"target_lang_code": "de-DE", # 독일어
"text": "V nejhorším případě i k prasknutí čočky.",
}
],
}
]
inputs = processor.apply_chat_template(
messages, tokenize=True, add_generation_prompt=True,
return_dict=True, return_tensors="pt"
).to(model.device, dtype=torch.bfloat16)
input_len = len(inputs['input_ids'][0])
with torch.inference_mode():
generation = model.generate(**inputs, do_sample=False)
generation = generation[0][input_len:]
decoded = processor.decode(generation, skip_special_tokens=True)
print(decoded)
# 출력: "Im schlimmsten Fall kann es auch zum Platzen der Linse kommen."
이미지에서 텍스트 추출 + 번역도 가능하다:
# ---- 이미지 속 텍스트 추출 및 번역 ----
messages = [
{
"role": "user",
"content": [
{
"type": "image",
"source_lang_code": "cs",
"target_lang_code": "de-DE",
"url": "https://c7.alamy.com/comp/2YAX36N/traffic-signs-in-czech-republic-pedestrian-zone-2YAX36N.jpg",
},
],
}
]
inputs = processor.apply_chat_template(
messages, tokenize=True, add_generation_prompt=True,
return_dict=True, return_tensors="pt"
).to(model.device, dtype=torch.bfloat16)
with torch.inference_mode():
generation = model.generate(**inputs, do_sample=False)
generation = generation[0][input_len:]
decoded = processor.decode(generation, skip_special_tokens=True)
print(decoded)
# 이미지 속 체코어 표지판 텍스트를 독일어로 번역
코드가 꽤 직관적이다. source_lang_code와 target_lang_code만 지정하면 되고, 이미지 번역도 type: "image"로 URL만 바꿔주면 된다.
문제의 발단: 왜 이렇게 느리지?
위의 코드를 실행해 봤다. 간단한 체코어 문장 하나를 독일어로 번역하는 건데, 응답 시간이 비정상적으로 오래 걸리는 것이다. (Blackwell Rtx 6000 pro server edition vram 96GB에서 테스트를 진행했다. )
예상:
- 응답: "Im schlimmsten Fall kann es auch zum Platzen der Linse kommen."
- 소요 시간: ~1초
실제:
- 응답: "Im schlimmsten Fall kann es auch zum Platzen der Linse kommen." (정상적으로 보임!)
- 소요 시간: 5~7초 (왜 이렇게 느리지?)
겉으로는 정상적인 번역 결과가 나오는데 시간만 엄청 오래 걸린다. "27B 모델이 원래 이렇게 느린가? GPU가 문제인가?" 싶어서 raw token ids를 찍어봤더니...
# 디코딩 전 raw token ids 확인
print(generation)
# tensor([..., 12847, 106, 106, 106, 106, 106, ..., 106])
# 106 = <end_of_turn> 토큰이 끝없이 반복됨!
print(f"생성된 토큰 수: {len(generation)}")
# 생성된 토큰 수: 256 (실제 번역은 20토큰인데!)
내부적으로 <end_of_turn> 토큰을 계속 생성하고 있었다! processor.decode(..., skip_special_tokens=True)가 이 토큰들을 텍스트로 변환하지 않아서 출력에는 안 보이는 것뿐, 실제로는 기본 max_new_tokens 설정에 도달할 때까지 불필요한 토큰을 계속 생성하고 있었던 거다.
처음에는 "내 코드가 잘못됐나?" 생각했는데, 여러 TranslateGemma 변형 모델(4B, 12B, 27B)과 다른 Gemma-3 IT 모델들에서도 똑같은 문제가 발생했다. 이건 분명 뭔가 잘못된 거다.
EOS vs end_of_turn: 뭐가 다른 건데?
이 문제를 이해하려면 먼저 EOS 토큰이 뭔지 알아야 한다.
EOS 토큰이란?
EOS(End of Sequence) 토큰은 언어 모델이 텍스트 생성을 멈춰야 할 시점을 알려주는 특수 토큰이다. 사람으로 치면 문장 끝에 찍는 마침표 같은 거라고 보면 된다.
Transformer 모델의 생성 메커니즘은 간단하다:
- 모델이 다음 토큰 예측
- 예측한 토큰이 EOS 토큰이면 → 생성 중단
- 아니면 → 계속 생성 (max_new_tokens 한계까지)
쉽게 말해, EOS 토큰이 없으면 모델은 "언제 멈춰야 하는지" 모른다는 것이다.
Gemma의 두 가지 EOS 토큰
Gemma 모델은 두 가지 버전이 있다:
- PT (Pre-trained): 사전 학습만 된 베이스 모델
- IT (Instruct-tuned): 대화형으로 파인튜닝된 모델
문제는 이 두 모델이 서로 다른 EOS 토큰을 사용한다는 것이다.
| 모델 타입 | EOS 토큰 | 토큰 ID |
|---|---|---|
| PT (Pre-trained) | <eos> |
1 |
| IT (Instruct-tuned) | <end_of_turn> |
106 |
IT 모델은 대화 턴이 끝날 때마다 <end_of_turn> 토큰을 사용한다. 이게 학습 데이터에 포함되어 있고, 모델도 이 토큰을 보고 "아, 내 차례 끝났구나" 하고 멈추도록 학습되었다.
하지만 HuggingFace의 모델 설정 파일(generation_config.json)에는 이 정보가 없었다.
왜 이런 일이 발생했나?
1. 관련 이슈와 공식 입장
이 문제는 2024년부터 커뮤니티에서 계속 제기되었다:
| 이슈 | 날짜 | 핵심 내용 |
|---|---|---|
| Issue #32110 | 2024.07 | Gemma 템플릿이 assistant 응답 끝에 EOS를 안 붙임 |
| gemma-2-9b-it Discussion #46 | 2024 | EOS token ids 업데이트 요청 |
| gemma-3-1b-it Discussion #5 | 2024 | 동일 이슈 수정 |
| Issue #38182 | 2025 | Gemma-3 IT 모델의 EOS가 <end_of_turn>(106)인데 tokenizer는 <eos>(1) |
| gemma-3-27b-it Discussion #47 | 2025 | 모델 업데이트 후 생성 멈추지 않음 |
그런데 중요한 점이 있다. Hugging Face maintainer @Rocketknight1의 공식 답변을 보면:
"This isn't really a bug" (Issue #38182)
"This is the correct behaviour" (Issue #32110)
즉, 이것은 버그가 아니라 의도된 동작이다. 모델은 학습 시 <end_of_turn>으로 종료를 배웠지만, <eos> 토큰도 emit할 수 있다. 하지만 사용자가 명시적으로 eos_token_id를 설정하지 않으면 기본값으로 동작하면서 성능 문제가 발생할 수 있다.
2. 왜 기본 설정이 최적이 아닌가?
"버그가 아니라면 왜 이런 문제가 생기는 걸까?" 몇 가지 관점에서 생각해 볼 수 있다.
관점 1: PT vs IT 모델의 EOS 토큰 차이
Gemma 기술 보고서에 따르면:
- PT 모델:
<eos>(1) 사용 - IT 모델:
<end_of_turn>(106) 사용
IT 모델은 대화형 학습 데이터로 파인튜닝되어 <end_of_turn>을 종료 신호로 배웠다. 하지만 generation_config.json의 기본 eos_token_id는 여전히 1로 설정되어 있다.
이는 범용성을 위한 선택일 수 있다. 모델이 다양한 상황에서 유연하게 동작하도록 하기 위해 기본값을 보수적으로 설정한 것이다.
관점 2: max_new_tokens로 제어하는 설계
Google은 내부 테스트에서 아마 이렇게 사용했을 것이다:
max_new_tokens를 명시적으로 설정- 또는 코드에서
eos_token_id=106을 직접 전달
즉, 사용자가 적절한 파라미터를 설정한다는 가정 하에 설계되었을 수 있다.
관점 3: TranslateGemma의 기본 설정
TranslateGemma는 번역 특화 모델이지만, Gemma-3 IT의 기본 설정을 그대로 상속받은 것으로 보인다. 번역 태스크에서는 출력 길이가 예측 가능하므로 max_new_tokens로 제어하는 것이 일반적이지만, 이를 명시하지 않으면 불필요한 토큰 생성이 발생한다.
성능 영향은 얼마나 될까?
버그는 아니지만, EOS 토큰 설정을 최적화하지 않으면 성능에 심각한 영향이 있다.
영향 분석
| 영향 항목 | 최적화 전/후 차이 | 설명 |
|---|---|---|
| 추론 속도 | ⚠️ 5~10배 느려짐 | EOS 설정 O: 2-20 토큰 생성 후 종료 EOS 설정 X: 기본 max_new_tokens까지 계속 생성 |
| GPU 메모리 | ⚠️ 불필요한 KV 캐시 증가 | 불필요한 토큰을 계속 생성하느라 메모리 낭비 |
| 사용자 경험 | ⚠️ 응답 지연 | 겉으로는 정상이지만 응답 시간이 수 배 느려짐 |
| 학습(Fine-tuning) | ⚠️ 학습 효율 저하 | 파인튜닝 시 EOS를 올바르게 설정하지 않으면 학습이 비효율적 |
특히 첫 사용자가 이 문제를 놓치기 쉽다. 공식 예시 코드에는 eos_token_id 설정이 명시되어 있지 않아서, 그대로 사용하면 성능 이슈가 발생한다.
실제 영향 사례
TranslateGemma에서 실제 측정한 결과다:
# EOS 최적 설정 (eos_token_id 명시)
# "V nejhorším případě i k prasknutí čočky." (체코어) → 독일어 번역
generation = model.generate(**inputs, do_sample=False, eos_token_id=[1, 106])
# 생성된 토큰 수: 18개 (실제 번역 결과만)
# 소요 시간: 1.2초
# Token IDs: [..., 12847, 106] # 106에서 정상 종료
# 출력: "Im schlimmsten Fall kann es auch zum Platzen der Linse kommen."
# EOS 미설정 (기본값 사용 - 성능 저하)
generation = model.generate(**inputs, do_sample=False)
# 생성된 토큰 수: 256개 (기본 max_new_tokens까지 계속)
# 소요 시간: 6.8초
# Token IDs: [..., 12847, 106, 106, 106, ..., 106] # 106이 238번 반복!
# 출력: "Im schlimmsten Fall kann es auch zum Platzen der Linse kommen." (동일)
# 겉으로는 정상 응답이지만 내부적으로 106 반복 생성!
추론 속도 5.6배 차이, 생성 토큰 14배 증가, GPU 시간/비용 낭비. 이거 프로덕션에서 쓰면 답 없다.
어떻게 해결하나?
해결 방법 1: generation_config.json 수정
가장 간단한 방법은 모델의 설정 파일을 직접 수정하는 것이다.
# 모델 다운로드 위치 확인
# 보통 ~/.cache/huggingface/hub/models--google--translategemma-27b-it/
generation_config.json 파일을 열어서 수정:
기존:
{
"cache_implementation": "hybrid",
"do_sample": true,
"top_k": 64,
"top_p": 0.95,
"transformers_version": "4.57.3"
}
수정 후:
{
"cache_implementation": "hybrid",
"do_sample": true,
"eos_token_id": [1, 106],
"top_k": 64,
"top_p": 0.95,
"transformers_version": "4.57.3"
}
eos_token_id를 배열 형태 [1, 106]로 바꿔준다. 이렇게 하면 모델이 <eos>(1) 또는 <end_of_turn>(106) 둘 중 하나를 만나면 멈춘다.
해결 방법 2: 코드에서 직접 지정 (권장)
파일 수정이 번거롭다면 코드에서 직접 지정할 수도 있다.
import torch
from transformers import AutoModelForImageTextToText, AutoProcessor
model_id = "google/translategemma-27b-it"
processor = AutoProcessor.from_pretrained(model_id)
model = AutoModelForImageTextToText.from_pretrained(model_id, device_map="auto")
messages = [...] # 번역 메시지
inputs = processor.apply_chat_template(
messages, tokenize=True, add_generation_prompt=True,
return_dict=True, return_tensors="pt"
).to(model.device, dtype=torch.bfloat16)
input_len = len(inputs['input_ids'][0])
# 방법 1: generate() 호출 시 직접 전달 (가장 간단)
with torch.inference_mode():
generation = model.generate(
**inputs,
eos_token_id=[1, 106], # 명시적 지정
do_sample=False
)
# 방법 2: model config 수정
model.config.eos_token_id = [1, 106]
with torch.inference_mode():
generation = model.generate(**inputs, do_sample=False)
# 방법 3: IT 모델에서는 106만 사용 (더 안전)
model.config.eos_token_id = 106
with torch.inference_mode():
generation = model.generate(**inputs, do_sample=False)
generation_config.json 수정만으로 충분한가?
대부분의 경우 Yes, 하지만 완벽하지는 않다.
설정 우선순위는 이렇다:
1순위: generate() 호출 시 직접 전달한 kwargs
2순위: generation_config.json
3순위: config.json (model.config)
즉, generate(eos_token_id=X)로 직접 전달하면 그게 최우선이고, 그다음이 generation_config.json이다.
주의사항:
| 상황 | 필요한 추가 조치 |
|---|---|
| 파인튜닝(Training) | tokenizer_config.json도 수정 필요 |
| vLLM/Ollama 등 3rd party 엔진 | 배열 [1, 106] 지원 안 할 수 있음 → 106만 사용 |
| config.json에도 eos_token_id 있는 경우 | 둘 다 맞춰줘야 혼란 방지 |
특히 파인튜닝할 때는 주의해야 한다. generation_config.json만 수정하고 tokenizer_config.json은 안 건드리면, 학습 시에는 여전히 잘못된 EOS가 사용된다.
vLLM, Ollama 등에서 사용할 때
vLLM이나 Ollama 같은 추론 엔진은 배열 형태의 eos_token_id를 지원하지 않을 수 있다. 이 경우:
{
"eos_token_id": 106
}
IT 모델에서는 106만 사용하는 게 안전하다. 어차피 대화형 모델에서는 <end_of_turn>이 실제 종료 시점이니까.
EOS 토큰과 학습(Fine-tuning)
이 문제가 특히 치명적인 건 파인튜닝할 때다.
왜 학습에서 문제가 되는가?
모델이 대화를 생성하는 법을 배울 때, 학습 데이터는 보통 이런 형태다:
<start_of_turn>user
What is Python?<end_of_turn>
<start_of_turn>model
Python is a high-level programming language...<end_of_turn>
이때 <end_of_turn> 토큰이 "내 답변이 끝났어!" 신호가 된다. 모델은 이 패턴을 보고 "아, 여기서 멈춰야 하는구나" 배운다.
하지만 EOS 토큰 설정이 잘못되면?
# 잘못된 설정
tokenizer.eos_token_id = 1 # <eos>
# 학습 데이터 토큰화
tokens = tokenizer("...Python is a language...<end_of_turn>")
# <end_of_turn>(106)이 일반 텍스트로 취급됨!
# EOS 토큰(1)이 문장 끝에 없음!
모델은 <end_of_turn>을 그냥 일반 텍스트로 보게 되고, "언제 멈춰야 하는지" 제대로 못 배운다. 결과적으로 파인튜닝 후 모델이 계속 횡설수설하게 된다.
올바른 파인튜닝 설정
from transformers import AutoTokenizer, AutoModelForCausalLM
model = AutoModelForCausalLM.from_pretrained("google/gemma-3-12b-it")
tokenizer = AutoTokenizer.from_pretrained("google/gemma-3-12b-it")
# 1. EOS 토큰을 end_of_turn으로 설정
tokenizer.eos_token = "<end_of_turn>"
tokenizer.eos_token_id = 106
# 2. 모델 config도 맞춰줌
model.config.eos_token_id = 106
# 3. generation_config도 맞춤
model.generation_config.eos_token_id = 106
# 이제 학습 시작
trainer = Trainer(
model=model,
tokenizer=tokenizer,
...
)
trainer.train()
이렇게 하면 학습 데이터 토큰화할 때 <end_of_turn>이 제대로 EOS 토큰으로 인식되고, 모델도 "여기서 멈춰야 해!" 배우게 된다.
Summary
이번 내용을 정리하면:
핵심 포인트
- Gemma IT 모델은 학습 시
<end_of_turn>(106)을 EOS 토큰으로 사용한다 - 하지만 기본 설정(
generation_config.json)은 범용적으로 설정되어 있다 - 사용자가 명시적으로
eos_token_id를 설정하지 않으면 불필요한 토큰을 계속 생성한다 - 이는 버그가 아니라 의도된 동작이지만, 성능 최적화를 위해서는 설정이 필요하다
- 파인튜닝할 때는 특히 주의 - tokenizer와 config를 모두 올바르게 설정해야 한다
Hugging Face 공식 입장
Maintainer @Rocketknight1의 답변:
- "This isn't really a bug" - 버그가 아닌 의도된 동작
- "This is the correct behaviour" - 모델이 다양한 종료 방식을 학습했음
- 사용자가 적절한
eos_token_id를 설정하면 최적 성능 확보
TranslateGemma 최적 설정
import torch
from transformers import AutoModelForImageTextToText, AutoProcessor
model_id = "google/translategemma-27b-it"
processor = AutoProcessor.from_pretrained(model_id)
model = AutoModelForImageTextToText.from_pretrained(model_id, device_map="auto")
# 추천: eos_token_id 명시적 설정
with torch.inference_mode():
generation = model.generate(
**inputs,
eos_token_id=[1, 106], # 또는 106만 사용
do_sample=False
)
앞으로 주의할 점
- TranslateGemma 사용 시
eos_token_id=[1, 106]또는106을 명시 - 성능 테스트 시 raw token IDs 확인하여 불필요한 생성 체크
- 파인튜닝 전에 tokenizer, config, generation_config 모두 확인
- vLLM/Ollama 같은 3rd party 엔진 쓸 때 배열 지원 여부 체크
나도 처음엔 "내가 뭘 잘못 쓴 건가?" 의심했는데, 알고 보니 설정 최적화가 필요한 부분이었다. Hugging Face maintainer의 답변을 보고 나서야 이것이 버그가 아니라 사용자가 이해하고 설정해야 하는 부분임을 알게 되었다. 혹시 비슷한 상황으로 고생하는 사람이 있다면 이 글이 도움이 되길 바란다!
260129 추가)
google이 pr을 받아줬다~~~
올린지 꽤 됐었는데 따봉이 몇개 박히니까 받아줬네~~

Reference
Issues & Discussions:
- Gemma-3 EOS token issue #38182 - Maintainer: "This isn't really a bug"
- Gemma template won't end with eos_token #32110 - Maintainer: "This is the correct behaviour"
- Ollama Gemma 3 end_of_turn issue #11276 - Ollama 특정 이슈 (다른 문제)
공식 문서:
- TranslateGemma Model Card - Google 공식 모델 페이지
- Gemma Tokenizer Documentation
- Hugging Face Transformers Text Generation
- Understanding EOS and PAD tokens in transformers
Related Issues:
'알쓸신잡' 카테고리의 다른 글
| PaddleOCR-VL-1.5-초경량 OCR의 새로운 강자 (0) | 2026.02.02 |
|---|---|
| STAX - 구글이 내놓은 LLM 평가 플랫폼 (0) | 2026.01.26 |
| 옵시디언 새탭에서 파일 열기 (0) | 2026.01.21 |
| 리눅스 파일경로 전체 복사 (0) | 2026.01.16 |
| macOS sshfs로 다른 시스템 마운트하기 (0) | 2025.09.14 |