Files
frankenstallm/source/eval/eos_audit_report.md
ModelHub XC d4abdb70fa 初始化项目,由ModelHub XC社区提供模型
Model: pathcosmos/frankenstallm
Source: Original Platform
2026-07-14 04:21:16 +08:00

5.0 KiB

EOS 토큰 처리 전수 감사 보고서

날짜: 2026-02-26
감사 대상: /PROJECT/0325120031_A/ghong/taketimes/llm-bang/
문제: SFT 모델이 "### 답변:" 이후 "### 질문:"을 반복 (반복률 57%)


결론 요약

🔴 근본 원인: 추론 시 프롬프트 템플릿 불일치 (EOS 버그 아님)

항목 학습 템플릿 추론 템플릿 (test_generation_params.py)
사용자 태그 <|user|>\n{instruction}\n ### 질문: {instruction}\n
어시스턴트 태그 <|assistant|>\n ### 답변:
종료 토큰 </s> (EOS, id=2) 없음 (stop_strings로 대체 시도)

모델은 <|user|> / <|assistant|> 포맷으로 학습됐으나, 추론 시 ### 질문: / ### 답변: 포맷으로 호출됨.
모델 입장에서 ### 질문: ### 답변:은 일반 텍스트 — EOS를 출력할 이유가 없으므로 무한 반복.


상세 감사 결과

체크포인트 1: SFTDataset — response 끝 EOS 토큰 부착

결과: 정상

sft_dataset.py Line ~52, ~87:

response = f"{output}{_EOS_STRING}"   # _EOS_STRING = "</s>"
response = f"{content}{_EOS_STRING}"  # conversation format도 동일

실제 검증: response_ids[-1] == 2 (EOS)

체크포인트 2: EOS 토큰 label = 학습 대상

결과: 정상

sft_dataset.py Line ~144-152:

resp_label_start = max(0, resp_start - 1)  # 1칸 왼쪽 시프트 (causal LM 관례)
resp_label_end = resp_label_start + len(response_ids)
labels[resp_label_start:resp_label_end] = response_ids
  • labels[resp_label_end - 1] = EOS (2) — EOS가 학습 대상에 포함됨 ✓
  • logits[마지막 응답 토큰 위치] → EOS 예측하도록 학습됨 ✓

체크포인트 3: prompt 부분 label = -1 (무시)

결과: 정상

labels 초기값이 -1이고, response 영역만 덮어쓰므로 prompt 전체는 -1

체크포인트 4: 트렁케이션으로 EOS 손실

결과: 무시 가능 수준

  • 전체 159,125 샘플 중 61개 (0.04%)만 max_seq_len=4096 초과
  • 이 61개에서만 EOS가 잘릴 수 있음 — 반복률 57%와 무관

⚠️ 체크포인트 5: 토크나이저 특수 토큰 미등록

결과: 경미한 문제

  • <|user|>token_to_id() = None (특수 토큰 아님, 서브워드로 분할됨)
  • <|assistant|>None (동일)
  • </s> → id=2 ✓ (정상 등록)

<|user|> / <|assistant|>가 단일 토큰이 아니라 서브워드 조각으로 분할됨.
학습/추론 모두 같은 토크나이저를 쓰면 동작은 하지만, 단일 특수 토큰으로 등록하는 것이 더 robust.

🔴 체크포인트 6: 추론 프롬프트 포맷 불일치 (근본 원인)

eval/test_generation_params.py:

"### 질문: 한국의 수도는 어디인가요?\n### 답변:",

eval/comprehensive_eval.py:

"한국의 수도는",  # 템플릿 없이 raw text

학습된 포맷:

<|user|>
한국의 수도는 어디인가요?
<|assistant|>
서울입니다.</s>

추론 시 올바른 프롬프트:

<|user|>
한국의 수도는 어디인가요?
<|assistant|>

수정 사항

Fix 1: 추론 프롬프트 템플릿 수정 (필수, 재학습 불필요)

eval/test_generation_params.pyeval/comprehensive_eval.py에서 프롬프트를 SFT 학습 템플릿에 맞게 변경:

# Before (WRONG)
prompt = "### 질문: 한국의 수도는 어디인가요?\n### 답변:"

# After (CORRECT)
prompt = "<|user|>\n한국의 수도는 어디인가요?\n<|assistant|>\n"

Fix 2: 트렁케이션 시 EOS 보장 (권장, 재학습 필요)

sft_dataset.py에서 truncation 후 EOS를 강제 삽입:

# 현재 (truncation 시 EOS 손실 가능)
response_ids = response_ids[:allowed_response]

# 수정안 (truncation 후 EOS 강제)
response_ids = response_ids[:allowed_response]
if response_ids and response_ids[-1] != self.eos_token_id:
    response_ids[-1] = self.eos_token_id  # 마지막 토큰을 EOS로 교체

Fix 3: <|user|> / <|assistant|> 특수 토큰 등록 (선택, 재학습 필요)

토크나이저에 특수 토큰으로 추가하면 단일 토큰으로 인코딩되어 더 안정적:

tokenizer.add_special_tokens(["<|user|>", "<|assistant|>"])

재학습 필요 여부

수정 재학습 필요 효과
Fix 1: 추론 템플릿 수정 반복 문제 해결 예상 (근본 원인)
Fix 2: 트렁케이션 EOS 보장 (0.04%만 해당) 미미
Fix 3: 특수 토큰 등록 장기적 안정성 향상

즉시 조치: Fix 1만으로 반복 문제 해결 가능. 재학습 불필요.


검증 방법

python eval/generate.py \
    --checkpoint checkpoints/korean_1b_sft \
    --prompt $'<|user|>\n한국의 수도는 어디인가요?\n<|assistant|>\n' \
    --max_new_tokens 200 \
    --temperature 0.7

반복이 멈추고 </s> (EOS)에서 정상 종료되면 Fix 1 성공.