{"id":50325025,"url":"https://github.com/pathcosmos/frankenstallm_test","last_synced_at":"2026-05-29T05:04:29.002Z","repository":{"id":343927149,"uuid":"1178457722","full_name":"pathcosmos/frankenstallm_test","owner":"pathcosmos","description":null,"archived":false,"fork":false,"pushed_at":"2026-04-07T10:04:25.000Z","size":28199,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"master","last_synced_at":"2026-04-07T12:08:17.439Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"HTML","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/pathcosmos.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-03-11T03:24:21.000Z","updated_at":"2026-04-07T10:04:34.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/pathcosmos/frankenstallm_test","commit_stats":null,"previous_names":["pathcosmos/frankenstallm_test"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/pathcosmos/frankenstallm_test","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pathcosmos%2Ffrankenstallm_test","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pathcosmos%2Ffrankenstallm_test/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pathcosmos%2Ffrankenstallm_test/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pathcosmos%2Ffrankenstallm_test/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/pathcosmos","download_url":"https://codeload.github.com/pathcosmos/frankenstallm_test/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pathcosmos%2Ffrankenstallm_test/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33637487,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-05-29T02:00:06.066Z","response_time":107,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-05-29T05:04:20.424Z","updated_at":"2026-05-29T05:04:28.988Z","avatar_url":"https://github.com/pathcosmos.png","language":"HTML","funding_links":[],"categories":[],"sub_categories":[],"readme":"# FRANKENSTALLM 3B 평가 프레임워크\n\n한국어 특화 커스텀 LLM인 **FRANKENSTALLM 3B**를 5개 비교 모델과 함께 7개 트랙으로 종합 평가하는 프레임워크.\n\n## 빠른 시작 (Quick Start)\n\n```bash\n# 1. 리포지토리 클론\ngit clone https://github.com/lanco/frankenstallm_test.git\ncd frankenstallm_test\n\n# 2. Ollama 설치 (이미 설치되어 있으면 건너뛰기)\ncurl -fsSL https://ollama.com/install.sh | sh\n\n# 3. Python 패키지 설치\npip install -r requirements.txt\nsudo apt install -y fonts-nanum  # 한국어 폰트 (리포트 차트용)\n\n# 4. 비교 모델 다운로드\nollama pull qwen2.5:3b\nollama pull gemma3:4b\nollama pull phi4-mini\nollama pull exaone3.5:2.4b\nollama pull llama3.2:3b\n\n# 5. 평가 실행\npython run_evaluation.py\n```\n\n---\n\n## 시스템 요구사항\n\n| 항목 | 최소 | 권장 |\n|------|------|------|\n| OS | Ubuntu 22.04+ | Ubuntu 24.04 |\n| Python | 3.12+ | 3.12 |\n| RAM | 16GB | 32GB |\n| GPU | 없음 (CPU 가능) | NVIDIA 16GB+ VRAM |\n| 디스크 | 20GB 여유 | 50GB 여유 |\n\n---\n\n## 환경 구성 상세\n\n### Ollama 설치\n\n```bash\ncurl -fsSL https://ollama.com/install.sh | sh\n```\n\n### Ollama 모델 저장 경로 설정 (선택)\n\n기본 경로(`~/.ollama/models`)를 변경하려면:\n\n```bash\nexport OLLAMA_MODELS=/var/ollama/models\n```\n\n\u003e `eval_framework/config.py`에서 `os.environ.setdefault(\"OLLAMA_MODELS\", \"/var/ollama/models\")`로 설정되어 있음. 다른 경로를 사용한다면 `config.py`도 수정.\n\n### Python 패키지\n\n```bash\npip install -r requirements.txt\n```\n\n필수 패키지: `matplotlib`, `requests`, `numpy`, `scipy`\n\n### 한국어 폰트 (리포트 차트용)\n\n```bash\nsudo apt install -y fonts-nanum\n```\n\n### 시스템 튜닝 (선택)\n\n대용량 모델 로딩 시 swap thrashing 방지:\n\n```bash\nsudo sysctl -w vm.swappiness=10\n```\n\n---\n\n## 모델 설치\n\n### 비교 모델 (5개)\n\n```bash\nollama pull qwen2.5:3b       # Alibaba Qwen 2.5, 3.1B, Q4_K_M\nollama pull gemma3:4b         # Google Gemma 3, 4.3B, Q4_K_M\nollama pull phi4-mini          # Microsoft Phi-4 Mini, 3.8B, Q4_K_M\nollama pull exaone3.5:2.4b    # LG AI EXAONE 3.5, 2.7B, Q4_K_M\nollama pull llama3.2:3b       # Meta LLaMA 3.2, 3.2B, Q4_K_M\n```\n\n### FRANKENSTALLM v2 커스텀 모델 (3개)\n\nFRANKENSTALLM v2 GGUF 파일을 준비한 후 Modelfile 템플릿으로 등록:\n\n```bash\n# 1. GGUF 파일을 적절한 위치에 배치\n# 2. modelfiles/ 의 Modelfile에서 \u003cPATH_TO_GGUF\u003e를 실제 경로로 수정\n# 3. Ollama에 등록\n\nollama create frankenstallm-3b-v2-Q4_K_M -f modelfiles/Modelfile.v2-Q4_K_M\nollama create frankenstallm-3b-v2-Q8_0   -f modelfiles/Modelfile.v2-Q8_0\nollama create frankenstallm-3b-v2-f16    -f modelfiles/Modelfile.v2-f16\n```\n\n각 모델의 양자화별 파일 크기:\n\n| 양자화 | 파일 크기 | 파라미터 수 | 비고 |\n|--------|-----------|-------------|------|\n| Q4_K_M | 757 MB | 1.2B | 가장 빠름, 기본 권장 |\n| Q8_0 | 1.2 GB | 1.2B | 균형 |\n| F16 | 2.3 GB | 1.2B | 최고 품질, GPU 권장 |\n\n\u003e **v1 모델 주의사항**: v1 GGUF는 SPM 토크나이저의 `byte_to_token` 매핑 결함으로 llama.cpp 계열 엔진에서 SIGABRT 크래시 발생. **v2만 사용 가능.**\n\n---\n\n## EVAFRILL-Mo 모델 설정 (PyTorch 직접 추론)\n\n### EVAFRILL-Mo-3B란?\n\nEVAFRILL-Mo-3B는 **Mamba-2 + Transformer 하이브리드 아키텍처**(2.94B 파라미터)의 한국어 LLM이다. 26개 레이어 중 24개가 Mamba-2 SSM 블록, 2개가 Attention(GQA) 블록으로 구성된 실험적 모델.\n\n\u003e **Ollama/GGUF로 실행할 수 없다.** Mamba-2 SSM 아키텍처는 llama.cpp/GGUF 포맷이 지원하지 않으므로, `eval_framework/evafrill_runner.py`를 통해 **PyTorch로 직접 추론**한다.\n\n\u003e 아키텍처 상세(hybrid pattern, config.json 파라미터, SLERP 머지 설명, GGUF 미지원 기술 분석, 추론 파이프라인 등)는 [MODEL_DETAILS.md 섹션 3.6](MODEL_DETAILS.md#36-evafrill-mo-3b-slerp-evafrill-mo-3b-slerp)을 참조.\n\n### GGUF/Ollama 미지원 이유\n\n| 제약 | 설명 |\n|------|------|\n| **아키텍처 미지원** | llama.cpp GGUF는 LLaMA, Qwen, Gemma 등 표준 Transformer만 지원. Mamba-2의 Selective State Space(SSM) 연산은 GGUF에 구현되어 있지 않음 |\n| **KV 캐시 부재** | Mamba-2는 KV 캐시 대신 hidden state를 유지. llama.cpp의 KV 캐시 기반 최적화 파이프라인과 호환 불가 |\n| **커스텀 커널** | Mamba-2의 `selective_scan`, `causal_conv1d` 등은 별도 CUDA 커널이 필요. GGUF 추론 엔진에 해당 커널 없음 |\n| **결과** | 매 토큰 생성마다 full-sequence forward pass 필요 → O(n^2) 총 생성 비용, TPS 극히 낮음 |\n\n\u003e 이것은 구현 미비가 아니라 **아키텍처의 근본적 차이**에 의한 제약이다.\n\n### 모델 소스 코드 설치\n\nEVAFRILL-Mo의 커스텀 모듈(`model/config.py`, `model/transformer.py`, `model/mamba_block.py` 등)이 필요하다.\n\n```bash\n# 소스 코드 클론\ngit clone https://github.com/pathcosmos/EVAFRILL-Mo /home/lanco/models/EVAFRILL-Mo\n\n# 디렉토리 구조 확인\nls /home/lanco/models/EVAFRILL-Mo/model/\n# config.py  transformer.py  attention.py  mamba_block.py  layers.py  lora.py  __init__.py\n```\n\n\u003e `evafrill_runner.py`가 이 경로를 `sys.path`에 추가하여 `from model.config import LMConfig` 등을 import한다.\n\n### 체크포인트 다운로드\n\n체크포인트는 [HuggingFace Hub](https://huggingface.co/pathcosmos/EVAFRILL-Mo-3B)에서 다운로드한다. **SLERP 체크포인트가 권장 최종 모델**이다.\n\n```bash\n# HuggingFace에서 전체 클론 (Git LFS 필요)\ngit lfs install\ngit clone https://huggingface.co/pathcosmos/EVAFRILL-Mo-3B /home/lanco/models/EVAFRILL-Mo-3B\n\n# 또는 slerp 디렉토리만 수동 배치\nmkdir -p /home/lanco/models/EVAFRILL-Mo-3B/slerp\n```\n\n필요 파일 3개:\n\n| 파일 | 크기 | 설명 |\n|------|------|------|\n| `config.json` | 687 B | 모델 아키텍처 설정 (vocab, layers, hybrid pattern 등) |\n| `model.safetensors` | 5.9 GB | 모델 가중치 (BF16, SafeTensors 형식) |\n| `tokenizer.json` | 4.2 MB | HuggingFace Tokenizer (vocab 64,000) |\n\n**체크포인트 계보:**\n\n```\npretrain (319K steps, 55B tokens)\n    └── sft-v2 (Supervised Fine-Tuning)\n            ├── dpo-r1 → dpo-r2 → dpo-r3 (Direct Preference Optimization)\n            └── orpo (Odds Ratio Preference Optimization)\n\nSLERP = sft-v2 ⊕ dpo-r2 (Spherical Linear Interpolation, alpha=0.5)\n```\n\n\u003e **SLERP(Spherical Linear Interpolation)**: 가중치를 단위 구면 위에서 보간하여 두 학습 변형의 장점을 결합하는 기법. 단순 선형 평균보다 학습된 표현을 더 잘 보존한다.\n\n### 의존성 (Python 패키지)\n\n```bash\n# 필수 (requirements.txt에 포함되지 않음 — EVAFRILL 전용)\npip install torch safetensors tokenizers PyYAML\n\n# 선택 (GPU 가속 커널, 없으면 PyTorch 순수 구현으로 fallback)\npip install mamba_ssm causal_conv1d\n```\n\n| 패키지 | 용도 | 필수 여부 |\n|--------|------|:---------:|\n| `torch` | PyTorch 추론 엔진 | 필수 |\n| `safetensors` | 모델 가중치 로딩 | 필수 |\n| `tokenizers` | HuggingFace Tokenizer | 필수 |\n| `PyYAML` | 모델 설정 직렬화 | 필수 |\n| `mamba_ssm` | Mamba-2 Triton CUDA 커널 | 선택 (성능 향상) |\n| `causal_conv1d` | Causal Conv1D CUDA 커널 | 선택 (성능 향상) |\n| `flash_attn` | FlashAttention-2 | 선택 (추론 시 비활성화됨) |\n\n### 시스템 요구사항\n\n| 항목 | 최소 | 권장 |\n|------|------|------|\n| GPU VRAM | 8 GB | 16 GB+ |\n| 정밀도 | BF16 지원 (NVIDIA Ampere+) | BF16 |\n| RAM | 16 GB | 32 GB |\n| CPU 추론 | 가능 (극히 느림, ~0.5 TPS) | GPU 사용 권장 |\n\n### 실행 방법\n\n**자동 (평가 프레임워크)** — `run_evaluation.py`에서 EVAFRILL 모델명이 감지되면 자동으로 `evafrill_runner.py`로 위임:\n\n```bash\npython run_evaluation.py --models evafrill-mo-3b-slerp --tracks 6\n```\n\n**독립 사용 (Python 코드):**\n\n```python\nfrom eval_framework.evafrill_runner import generate, load_model, unload_model\n\n# 추론\nresult = generate(\"한국어로 인사해주세요.\")\nprint(result[\"response\"])\nprint(f\"속도: {result['tokens_per_sec']:.1f} TPS\")\nprint(f\"생성 토큰: {result['eval_count']}개\")\n\n# 메모리 해제\nunload_model()\n```\n\n### 성능 특성\n\n| 항목 | 값 | 비고 |\n|------|---|------|\n| GPU TPS | ~4.8 | RTX 5060 Ti 16GB 기준 |\n| CPU TPS | ~0.5 (추정) | 실용적이지 않음 |\n| 타임아웃 | 600초 (10분) | `config.py`에서 설정 |\n| 최대 생성 길이 | 512 토큰 | `num_predict` 기본값 |\n| 메모리 사용 | ~6-8 GB VRAM | 5.9 GB 가중치 + 활성화 값 |\n\n\u003e **느린 이유**: Mamba-2 아키텍처에 KV 캐시가 없어 매 토큰마다 전체 시퀀스를 forward pass해야 함. n개 토큰 생성 시 총 O(n^2) 연산. Ollama 모델(Q4_K_M)의 100-200 TPS와 비교하면 20-40배 느림. 기술적 상세는 [MODEL_DETAILS.md의 GGUF 미지원 기술적 설명](MODEL_DETAILS.md#ggufollama-미지원-기술적-설명) 참조.\n\n### 경로 변경\n\n모델 파일이 다른 위치에 있다면 `eval_framework/evafrill_runner.py`의 두 경로를 수정:\n\n```python\n# evafrill_runner.py line 19 — 모델 소스 코드 (커스텀 Python 모듈)\n_EVAFRILL_SRC = Path(\"/home/lanco/models/EVAFRILL-Mo\")\n\n# evafrill_runner.py line 31 — 체크포인트 (가중치, 토크나이저)\nEVAFRILL_CHECKPOINT = Path(\"/home/lanco/models/EVAFRILL-Mo-3B/slerp\")\n```\n\n---\n\n## GPU vs CPU 모드\n\n### GPU 모드 (권장)\n\n기본 동작. Ollama가 자동으로 NVIDIA GPU를 감지하여 사용.\n\n```bash\nollama serve  # GPU 자동 감지\n```\n\n`config.py`가 `nvidia-smi`로 GPU 가용성을 확인하고 타임아웃을 자동 조정:\n- GPU 모드: 기본 타임아웃 (Q4_K_M: 120초, Q8_0: 180초, F16: 300초)\n- CPU 모드: 타임아웃 2배 자동 적용\n\n### CPU 모드\n\nGPU 없이 실행하려면:\n\n```bash\nCUDA_VISIBLE_DEVICES=\"\" ollama serve\n```\n\n\u003e CPU 모드는 상당히 느림. F16 모델은 응답 하나에 수 분 소요 가능.\n\n---\n\n## 평가 실행\n\n### 기본 실행 (전체 7트랙)\n\n```bash\npython run_evaluation.py\n```\n\n### 특정 트랙만 실행\n\n```bash\npython run_evaluation.py --tracks 1 4 5\npython run_evaluation.py --tracks 6      # Track 6만 (성능 벤치마크)\n```\n\n### 특정 모델만 실행\n\n```bash\npython run_evaluation.py --models frankenstallm-3b-v2-Q4_K_M qwen2.5:3b\n```\n\n### 기존 결과로 리포트만 생성\n\n```bash\npython run_evaluation.py --report-only\n```\n\n### 7개 평가 트랙\n\n| 트랙 | 이름 | 설명 | LLM-as-Judge |\n|------|------|------|:---:|\n| 1 | Korean Bench | KoBEST 4개 태스크 (BoolQ, COPA, SentiNeg, HellaSwag) | |\n| 2 | KO-Bench | 8개 카테고리 한국어 생성 품질 | O |\n| 3 | Korean Deep | 심층 한국어 이해력 | O |\n| 4 | Code \u0026 Math | 코딩/수학 문제 해결 | |\n| 5 | Consistency | 응답 일관성 테스트 | |\n| 6 | Performance | 토큰 속도, 레이턴시, 동시성 | |\n| 7 | Pairwise | 모델 쌍대비교 | O |\n\n### 3단계 분할 실행 전략\n\n전체 실행이 오래 걸리므로 단계별 분할 권장:\n\n```bash\n# Stage 1: 자동 채점 트랙 (LLM Judge 불필요)\npython run_evaluation.py --tracks 1 4 5 6\n\n# Stage 2: LLM-as-Judge 트랙 (Claude CLI 필요)\npython run_evaluation.py --tracks 2 3 7\n\n# Stage 3: 리포트 생성\npython run_evaluation.py --report-only\n```\n\n---\n\n## 체크포인트 \u0026 이어하기\n\n평가가 중단되어도 자동으로 체크포인트가 저장됨 (`results/*_checkpoint.json`).\n\n재실행 시 체크포인트를 자동 로드하여 이어서 진행:\n\n```bash\n# 중단 후 동일 명령으로 재실행하면 자동 이어하기\npython run_evaluation.py --tracks 1 4 5\n```\n\n---\n\n## Claude CLI 설정\n\nTrack 2, 3, 7은 **LLM-as-Judge**로 Claude를 사용. `claude` CLI가 PATH에 있어야 함.\n\n```bash\n# Claude CLI 설치 확인\nwhich claude\n\n# 테스트\nclaude -p \"Hello\"\n```\n\n\u003e Claude CLI가 없으면 Track 2, 3, 7은 건너뛰고 나머지 트랙만 실행하면 됨.\n\n---\n\n## Ollama Watchdog\n\nOllama가 대용량 모델 로딩 중 크래시할 수 있음. 자동 재시작 스크립트:\n\n```bash\nchmod +x ollama_watchdog.sh\n./ollama_watchdog.sh \u0026\n```\n\n---\n\n## 트러블슈팅\n\n### Ollama GPU 크래시\n\nGPU VRAM 부족 시 Ollama가 크래시할 수 있음:\n\n```bash\n# GPU 상태 확인\nnvidia-smi\n\n# GPU 메모리 비우기 — 로드된 모델 모두 해제\ncurl http://localhost:11434/api/ps  # 현재 로드된 모델 확인\n```\n\n### Ollama 반복 재시작\n\n```bash\n# Ollama 프로세스 정리 후 재시작\npkill -9 -f ollama\nsleep 3\nollama serve \u0026\n```\n\n### v1 모델 SIGABRT\n\nv1 모델(`frankenstallm-3b-Q4_K_M` 등)은 SPM 토크나이저 결함으로 실행 불가. **v2 모델만 사용.**\n\n### \"모델을 찾을 수 없음\" 오류\n\n```bash\n# 설치된 모델 목록 확인\nollama list\n\n# Ollama 모델 저장 경로 확인\necho $OLLAMA_MODELS\n```\n\n### 한국어 차트 깨짐\n\n```bash\nsudo apt install -y fonts-nanum\n# matplotlib 캐시 삭제\npython -c \"import matplotlib; print(matplotlib.get_cachedir())\"\nrm -rf ~/.cache/matplotlib\n```\n\n### EVAFRILL 관련 오류\n\n**`ModuleNotFoundError: No module named 'model.config'`**\n- 모델 소스 코드가 클론되지 않았거나 경로가 잘못됨\n- 확인: `ls /home/lanco/models/EVAFRILL-Mo/model/config.py`\n- 해결: `git clone https://github.com/pathcosmos/EVAFRILL-Mo /home/lanco/models/EVAFRILL-Mo`\n\n**`FileNotFoundError: model.safetensors`**\n- 체크포인트 파일이 없거나 경로가 잘못됨\n- 확인: `ls -la /home/lanco/models/EVAFRILL-Mo-3B/slerp/model.safetensors` (5.9 GB여야 함)\n- 135 B이면 Git LFS 포인터만 있는 상태 → `git lfs pull` 필요\n\n**`RuntimeError: CUDA out of memory`**\n- EVAFRILL은 BF16으로 ~6-8 GB VRAM 필요\n- 다른 모델이 VRAM을 점유 중이면: `curl http://localhost:11434/api/ps`로 확인 후 해제\n- GPU 메모리 부족 시 CPU 모드로 fallback됨 (극히 느림)\n\n**`ModuleNotFoundError: No module named 'torch'`**\n- PyTorch 미설치. `pip install torch` (CPU) 또는 CUDA 버전 설치\n\n### EVAFRILL CUDA 실패 → Ollama 무한 재시작 문제 (2026-03-30 수정)\n\n**증상:**\nEVAFRILL-Mo 모델 로딩 시 `CUDA error: unknown error (cudaErrorUnknown)` 발생 후, 이후 모든 Ollama 모델이 로딩 불가. Ollama 재시작이 무한 반복되며 평가가 표류(drift)함.\n\n**근본 원인:**\n`cudaErrorUnknown`은 일반 OOM과 달리 **GPU 드라이버 레벨 오염**을 일으킨다. EVAFRILL은 PyTorch로 `cuda:0`에 직접 텐서를 올리는데(`evafrill_runner.py:load_model()`), 이 과정에서 CUDA 컨텍스트가 비복구 상태로 손상되면 **같은 GPU를 공유하는 Ollama(별도 프로세스)**도 CUDA를 사용할 수 없게 된다.\n\n**인과 체인:**\n\n```\nEVAFRILL model.to(cuda:0) 실패\n  → GPU 드라이버 오염 (cudaErrorUnknown)\n  → 정리 코드 없이 return False\n  → 트랙 전환 시 GPU 상태 확인 없음\n  → Ollama 재시작 시 config.GPU_AVAILABLE=True (import 시점 캐시)\n  → Ollama가 GPU 모드로 재시작 → GPU 초기화 실패\n  → 60초 대기 → 재시작 3회 → 전부 실패\n  → 외부 재시도 루프와 중첩 → 수시간 표류\n  → SSH 세션 타임아웃으로 끊김\n```\n\n**수정 내용 (3-레이어 방어):**\n\n| 레이어 | 파일 | 변경 |\n|--------|------|------|\n| 발생 지점 | `evafrill_runner.py` | `model.to(cuda:0)` 실패 시 `del model` + `gc.collect()` + `torch.cuda.synchronize()` + `empty_cache()` 정리. `gpu_is_healthy()`(nvidia-smi)로 드라이버 오염 여부 진단 로그 |\n| 복구 지점 | `runner.py` | `_restart_ollama()`가 재시작 전 `_gpu_healthy_now()`로 GPU 상태 **동적** 확인 (import 시점 캐시 `config.GPU_AVAILABLE` 대신). GPU 죽었으면 `nvidia-smi --gpu-reset` 시도, 실패 시 `CUDA_VISIBLE_DEVICES=\"\"` CPU 모드 폴백. `switch_model()`에서 EVAFRILL 실패 시 `evafrill_runner.unload_model()`로 CUDA 정리 후, `_gpu_healthy_now()`가 이상 감지한 경우에만 GPU 리셋 시도 |\n| 전환 지점 | `run_evaluation.py` | 트랙 간 쿨다운에서 GPU 헬스체크 추가. 이상 감지 시 GPU 리셋 시도 후, **리셋 성공 여부와 무관하게** `_restart_ollama()` 호출 (GPU 오염 후에는 Ollama도 재시작 필요) |\n\n**수정 후 동작:**\n\n```\nEVAFRILL model.to(cuda:0) 실패\n  → del model + gc.collect() + CUDA cleanup\n  → nvidia-smi로 GPU 상태 확인\n  → GPU 오염 감지 → nvidia-smi --gpu-reset 시도\n  → 리셋 성공: Ollama GPU 모드로 정상 재시작\n  → 리셋 실패: Ollama CPU 모드로 폴백 (느리지만 평가 계속 진행)\n```\n\n**핵심 변경 함수:**\n\n| 함수 | 파일 | 역할 |\n|------|------|------|\n| `_cuda_cleanup()` | `evafrill_runner.py` | CUDA 실패 후 gc + synchronize + empty_cache + reset_peak_memory_stats (각 단계 try-except 래핑) |\n| `gpu_is_healthy()` | `evafrill_runner.py` | nvidia-smi 호출로 GPU 드라이버 상태 동적 확인 |\n| `_gpu_healthy_now()` | `runner.py` | Ollama 재시작 전 GPU 상태 동적 확인 |\n| `_try_gpu_reset()` | `runner.py` | `nvidia-smi --gpu-reset -i 0` 실행, 성공 여부 반환 |\n| `switch_model()` (수정) | `runner.py` | EVAFRILL 실패 시 `unload_model()` + 조건부 GPU 리셋 추가 |\n\n**관련 기술 배경:**\n- `cudaErrorUnknown` vs `cudaErrorMemoryAllocation`: OOM은 프로세스 레벨로 `empty_cache()`로 복구 가능하지만, unknown error는 드라이버 레벨 오염으로 GPU 리셋 또는 리부팅이 필요\n- `config.GPU_AVAILABLE`은 `config.py` import 시점에 1회만 평가되는 상수. CUDA 실패 후에도 `True`로 유지되어 Ollama를 계속 GPU 모드로 재시작하는 것이 무한 루프의 직접 원인이었음\n- `nvidia-smi --gpu-reset`은 실행 중인 CUDA 프로세스가 없어야 동작함. Ollama를 먼저 종료(`pkill`)한 뒤 리셋해야 함\n\n---\n\n## 테스트 (pytest)\n\n### 테스트 설치 및 실행\n\n```bash\n# 1. 개발 의존성 설치\npip install -r requirements-dev.txt\n\n# 2. 전체 테스트 실행\npytest tests/ -v\n\n# 3. 단위 테스트만\npytest tests/unit/ -v\n\n# 4. 통합 테스트만\npytest tests/integration/ -v\n\n# 5. 커버리지 리포트\npytest tests/ --cov=eval_framework --cov-report=term-missing\n```\n\n### 테스트 구조\n\n**116개 테스트** — 모두 Ollama 서버나 GPU 없이 실행 가능 (mock 기반)\n\n| 파일 | 테스트 수 | 대상 모듈 |\n|------|----------|----------|\n| `tests/unit/test_judge.py` | 31 | `_call_judge`, `_extract_json`, `score_response`, `score_pairwise`, `score_with_criteria` |\n| `tests/unit/test_runner.py` | 31 | `generate`, `chat`, `switch_model`, `wait_for_ollama`, health check, checkpoint I/O |\n| `tests/unit/test_scoring.py` | 17 | `aggregate_accuracy`, `aggregate_judge_scores`, `fit_bradley_terry`, `build_scorecard` |\n| `tests/unit/test_config.py` | 11 | `_gpu_available`, 타임아웃 계산, 모델 리스트 일관성 |\n| `tests/unit/test_evafrill_runner.py` | 9 | `is_evafrill`, `_top_p_filtering` (torch CPU) |\n| `tests/unit/test_data_externalization.py` | 9 | Track 2/7 JSON 로딩, 스키마 검증, fallback |\n| `tests/integration/test_judge_pipeline.py` | 3 | score → aggregate → Elo 파이프라인 |\n| `tests/integration/test_model_lifecycle.py` | 3 | 모델 전환 A→B→C, 서버 재시작, evafrill↔ollama |\n| `tests/integration/test_track_execution.py` | 2 | Track 7 최소 실행, 체크포인트 이어하기 |\n\n### 커버리지 현황\n\n| 모듈 | 커버리지 |\n|------|---------|\n| `judge.py` | 97% |\n| `scoring.py` | 98% |\n| `config.py` | 98% |\n| `runner.py` | 83% |\n\n---\n\n## 프로젝트 구조\n\n```\nfrankenstallm_test/\n├── run_evaluation.py          # 메인 실행 스크립트\n├── benchmark.py               # 단독 벤치마크\n├── ollama_watchdog.sh         # Ollama 자동 재시작\n├── requirements.txt           # Python 의존성 (런타임)\n├── requirements-dev.txt       # Python 의존성 (개발/테스트)\n├── pytest.ini                 # pytest 설정\n├── eval_framework/            # 평가 프레임워크 코어\n│   ├── config.py              # 설정 (모델, 타임아웃, 파라미터)\n│   ├── runner.py              # Ollama API 실행 엔진\n│   ├── judge.py               # LLM-as-Judge (Ollama gemma3:12b)\n│   ├── evafrill_runner.py     # EVAFRILL-Mo-3B PyTorch 직접 추론\n│   ├── scoring.py             # 스코어카드 계산 + Bradley-Terry Elo\n│   ├── report.py              # HTML/Markdown 리포트 생성\n│   └── tracks/                # 7개 평가 트랙\n│       ├── track1_korean_bench.py\n│       ├── track2_ko_bench.py\n│       ├── track3_korean_deep.py\n│       ├── track4_code_math.py\n│       ├── track5_consistency.py\n│       ├── track6_performance.py\n│       └── track7_pairwise.py\n├── tests/                     # pytest 테스트 스위트\n│   ├── conftest.py            # 공유 fixtures (Ollama mock, 샘플 데이터)\n│   ├── unit/                  # 단위 테스트 (6 파일, 108개)\n│   │   ├── test_judge.py\n│   │   ├── test_runner.py\n│   │   ├── test_scoring.py\n│   │   ├── test_config.py\n│   │   ├── test_evafrill_runner.py\n│   │   └── test_data_externalization.py\n│   └── integration/           # 통합 테스트 (3 파일, 8개)\n│       ├── test_judge_pipeline.py\n│       ├── test_model_lifecycle.py\n│       └── test_track_execution.py\n├── data/                      # 벤치마크 데이터셋\n│   ├── code_problems/\n│   ├── ko_bench/\n│   │   └── questions.json     # Track 2 질문 (80개, 외부화)\n│   ├── korean_deep/\n│   ├── math_problems/\n│   └── track7_prompts.json    # Track 7 프롬프트 (20개, 외부화)\n├── results/                   # 평가 결과 (체크포인트 포함)\n├── reports/                   # 생성된 리포트\n├── modelfiles/                # FRANKENSTALLM Modelfile 템플릿\n│   ├── Modelfile.v2-Q4_K_M\n│   ├── Modelfile.v2-Q8_0\n│   └── Modelfile.v2-f16\n├── MODEL_DETAILS.md           # 전체 14개 모델 상세 스펙 (EVAFRILL 아키텍처/SLERP 포함)\n└── TEST_LOG.md                # 테스트 진행 기록\n```\n\n---\n\n## 라이선스\n\nPrivate research project.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpathcosmos%2Ffrankenstallm_test","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpathcosmos%2Ffrankenstallm_test","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpathcosmos%2Ffrankenstallm_test/lists"}