Files
frankenstallm/source/scripts/test_ollama_repetition.py

149 lines
5.5 KiB
Python
Raw Permalink Normal View History

#!/usr/bin/env python3
"""
test_ollama_repetition.py Ollama 배포 모델 반복률 검증
ORPO eval과 동일한 프롬프트로 Ollama API 호출 n-gram 반복률 + EOS 종료율 측정.
목표: 3-gram rep < 3% (한국어 자연 반복 고려), EOS 종료율 > 95%
Usage:
python scripts/test_ollama_repetition.py [--model frankenstallm-3b] [--host localhost:11434]
"""
import argparse
import json
import urllib.request
import urllib.error
import sys
from collections import Counter
# ORPO eval에서 사용한 15개 한국어 프롬프트
TEST_PROMPTS = [
"대한민국의 수도는 어디인가요?",
"인공지능이란 무엇인가요?",
"한국의 전통 음식 중에서 김치에 대해 설명해주세요.",
"프로그래밍을 배우려면 어떻게 해야 하나요?",
"지구 온난화의 원인과 대책에 대해 설명해주세요.",
"한국어의 특징을 3가지 설명해주세요.",
"좋은 리더의 자질에 대해 논해주세요.",
"우주 탐사의 의미와 중요성을 설명해주세요.",
"건강한 생활 습관 5가지를 추천해주세요.",
"인터넷이 현대 사회에 미친 영향을 분석해주세요.",
"한국의 교육 제도의 장단점을 설명해주세요.",
"환경 보호를 위해 개인이 할 수 있는 일을 알려주세요.",
"4차 산업혁명이 일자리에 미치는 영향을 분석해주세요.",
"독서의 중요성과 효과적인 독서 방법을 알려주세요.",
"한국 문화의 세계화에 대해 어떻게 생각하시나요?",
]
def compute_ngram_repetition(text: str, n: int) -> float:
"""n-gram 반복률 계산 (0.0 ~ 1.0)"""
tokens = text.split()
if len(tokens) < n:
return 0.0
ngrams = [tuple(tokens[i:i+n]) for i in range(len(tokens) - n + 1)]
if not ngrams:
return 0.0
counts = Counter(ngrams)
repeated = sum(c - 1 for c in counts.values() if c > 1)
return repeated / len(ngrams)
def call_ollama(prompt: str, model: str, host: str, timeout: int = 120) -> dict:
"""Ollama API 호출"""
url = f"http://{host}/api/generate"
payload = json.dumps({
"model": model,
"prompt": prompt,
"stream": False,
}).encode("utf-8")
req = urllib.request.Request(url, data=payload, headers={"Content-Type": "application/json"})
try:
with urllib.request.urlopen(req, timeout=timeout) as resp:
return json.loads(resp.read().decode("utf-8"))
except urllib.error.URLError as e:
return {"error": str(e), "response": ""}
except Exception as e:
return {"error": str(e), "response": ""}
def main():
parser = argparse.ArgumentParser(description="Ollama 반복률 검증")
parser.add_argument("--model", default="frankenstallm-3b", help="Ollama 모델 이름")
parser.add_argument("--host", default="localhost:11434", help="Ollama 서버 주소")
args = parser.parse_args()
print(f"{'='*70}")
print(f" Ollama 반복률 검증: {args.model}")
print(f" 서버: {args.host}")
print(f" 프롬프트: {len(TEST_PROMPTS)}")
print(f"{'='*70}\n")
results = []
for i, prompt in enumerate(TEST_PROMPTS, 1):
print(f"[{i:2d}/{len(TEST_PROMPTS)}] {prompt[:40]}...")
resp = call_ollama(prompt, args.model, args.host)
if "error" in resp and resp["error"]:
print(f" ERROR: {resp['error']}")
results.append({"prompt": prompt, "error": resp["error"]})
continue
text = resp.get("response", "")
eos_done = resp.get("done", False)
rep1 = compute_ngram_repetition(text, 1)
rep2 = compute_ngram_repetition(text, 2)
rep3 = compute_ngram_repetition(text, 3)
rep4 = compute_ngram_repetition(text, 4)
results.append({
"prompt": prompt,
"response_len": len(text),
"word_count": len(text.split()),
"eos_done": eos_done,
"rep1": rep1, "rep2": rep2, "rep3": rep3, "rep4": rep4,
})
preview = text[:100].replace("\n", " ")
print(f" 응답: {preview}...")
print(f" 길이: {len(text)}자, EOS: {eos_done}, "
f"rep(1/2/3/4): {rep1:.2%}/{rep2:.2%}/{rep3:.2%}/{rep4:.2%}")
print()
# --- Summary ---
valid = [r for r in results if "error" not in r or not r.get("error")]
if not valid:
print("ERROR: 유효한 응답 없음")
sys.exit(1)
avg_rep3 = sum(r["rep3"] for r in valid) / len(valid)
eos_rate = sum(1 for r in valid if r["eos_done"]) / len(valid)
errors = len(results) - len(valid)
print(f"{'='*70}")
print(f" 결과 요약")
print(f"{'='*70}")
print(f" 유효 응답: {len(valid)}/{len(results)} (에러: {errors})")
print(f" 평균 3-gram 반복률: {avg_rep3:.2%} (목표: < 3%)")
print(f" EOS 종료율: {eos_rate:.0%} (목표: > 95%)")
print()
# Pass/Fail
# 한국어는 조사/접속사 자연 반복으로 어절 기준 3-gram rep 1.5~2%가 자연 floor
# 퇴행적 반복(30%+)과 구별하여 3% 기준 적용
rep_pass = avg_rep3 < 0.03
eos_pass = eos_rate > 0.95
overall = rep_pass and eos_pass
print(f" 3-gram 반복률: {'PASS ✓' if rep_pass else 'FAIL ✗'} ({avg_rep3:.2%})")
print(f" EOS 종료율: {'PASS ✓' if eos_pass else 'FAIL ✗'} ({eos_rate:.0%})")
print(f" 종합: {'PASS ✓' if overall else 'FAIL ✗'}")
print(f"{'='*70}")
sys.exit(0 if overall else 1)
if __name__ == "__main__":
main()