# 배포 테스트 — Qwen2.5-7B-Instruct-FP8 모델 전환 검토

**날짜**: 2026-05-15
**환경**: NVIDIA GB10 (DGX Spark) / 통합 메모리 121 GiB / vLLM in Docker `vllm_node`
**결론**: **7B 운영 부적합. 27B 유지 결정.**

---

## TL;DR

- 동기: 27B Qwen3.6-FP8이 48 tok/s로 느리게 느껴져, 더 가벼운 7B FP8로 교체 시 속도/메모리 이득을 볼 수 있는지 검토.
- 실측: 7B FP8(`RedHatAI/Qwen2.5-7B-Instruct-FP8-dynamic`)이 29.4 tok/s로 오히려 **느림** (27B는 `qwen3_next_mtp` speculative decoding 효과).
- 결정타: 7B는 **도구 호출을 회피하고 가짜 데이터를 만들어냄** — 운전원 안전 위험. 도구 토글 ON·에이전트 모드 ON에도 동작 안 함.
- 조치: 27B로 즉시 복귀. 검증 중 만든 시스템 프롬프트 강화는 그대로 유지(27B에도 도움).

---

## 1. 환경 정보

### 하드웨어
| 항목 | 값 |
|---|---|
| GPU/CPU | NVIDIA GB10 (Grace Blackwell, unified memory) |
| 통합 메모리 | 121 GiB (시스템·GPU 공유) |
| Swap | 15 GiB |

### 27B 운영 구성 (baseline)
| 항목 | 값 |
|---|---|
| 모델 | `Qwen/Qwen3.6-27B-FP8` |
| 메모리 점유 | ~97 GiB |
| `max-model-len` | 262,144 (256K) |
| `gpu-memory-utilization` | 0.80 |
| `speculative-config` | `qwen3_next_mtp`, 2 tokens |
| `tool-call-parser` | `qwen3_coder` |
| 측정 속도 | **48 tok/s** |
| GB10 튜닝 mod | `mods/vllm-tune-qwen--qwen3.6-27b-fp8-tp1` 적용 |

---

## 2. 7B FP8 띄우기 레시피 (재현용)

### 모델 선택
HF에서 `neuralmagic/Qwen2.5-7B-Instruct-FP8`은 **존재하지 않음** (404). neuralmagic이 RedHatAI로 흡수됨.

올바른 repo:
- ✅ `RedHatAI/Qwen2.5-7B-Instruct-FP8-dynamic` (다운로드 2378회, 공식 후속)
- 그 외: `CalamitousFelicitousness/...`, `llmcompressor-quants/...` 등 community 변형

### 27B 종료 + 7B 기동 절차

```bash
# 1. 현재 27B vllm 프로세스만 종료 (컨테이너는 sleep infinity로 유지)
docker exec vllm_node pkill -f "vllm serve"
sleep 5

# 2. 7B FP8 기동 (launch-cluster.sh exec 패턴)
cd /home/windpacer/ai-models/spark-vllm-docker
./launch-cluster.sh -t vllm-node-tf5 --solo -d \
  exec vllm serve RedHatAI/Qwen2.5-7B-Instruct-FP8-dynamic \
    --served-model-name Qwen2.5-7B-Instruct-FP8 \
    --max-model-len 32768 \
    --max-num-seqs 4 \
    --enable-prefix-caching \
    --gpu-memory-utilization 0.30 \
    --port 8000 --host 0.0.0.0 \
    --enable-chunked-prefill \
    --enable-auto-tool-choice \
    --tool-call-parser hermes \
    --trust-remote-code \
    -tp 1
```

### 27B 명령에서 제거한 인자 (Qwen3 전용)

| 인자 | 이유 |
|---|---|
| `--apply-mod mods/vllm-tune-qwen--qwen3.6-27b-fp8-tp1` | Qwen3.6-27B 전용 GEMM kernel config |
| `--load-format instanttensor` | Qwen3 instant tensor 최적화 |
| `--reasoning-parser qwen3` | Qwen3 reasoning trace (Qwen2.5 미지원) |
| `--default-chat-template-kwargs '{"preserve_thinking":true}'` | Qwen3 thinking block |
| `--speculative-config '{"method":"qwen3_next_mtp",...}'` | Qwen3 MTP — Qwen2.5에 적용 불가 |
| `--tool-call-parser qwen3_coder` | → `hermes`로 교체 (Qwen2.5는 hermes 포맷) |
| `--max-model-len 262144` | → 32768로 축소 (KV 캐시 절약) |
| `--gpu-memory-utilization 0.80` | → 0.30 (7B는 메모리 적게 필요) |
| `--max-num-batched-tokens 32768` | 32K로 충분 |
| `--override-generation-config` | 7B 기본값 사용 |

### 동기화 작업

```bash
# vllm_model 표시명을 UI에 맞게 변경
echo '{"vllm_model":"Qwen2.5-7B-Instruct-FP8"}' > mcp-server/llm-model.json
```

`appsettings.json`의 vLLM endpoint, `OllamaController.cs`, `KbEmbeddingClient.cs` 등은 **변경 불필요** — 포트 8000 동일.

### 기동 시간
- 가중치 다운로드: 152초 (~8 GiB, 첫 회만)
- 가중치 로드: 45초
- torch.compile 캐시: ~30초
- **합계 약 4분** (캐시 후 재기동은 ~75초)

---

## 3. 검증 결과

### 3.1 메모리 점유

| 모델 | 점유 | 가용 |
|---|---|---|
| 27B FP8 (max-len 256K, util 0.80) | 97 GiB | 11 GiB |
| 7B FP8 (max-len 32K, util 0.30) | ~25 GiB | ~85 GiB |

### 3.2 순수 채팅 속도 (3회 평균)

```
27B FP8 + qwen3_next_mtp : 48 tok/s
7B FP8 (vanilla)          : 29.4 tok/s
```

**역설**: 7B가 더 느림. 이유:
1. 27B는 MTP(Multi-Token Prediction) speculative decoding으로 한 step에 2~3토큰 생성
2. 27B는 GB10 전용 GEMM kernel mod 적용
3. 7B는 default kernel + vanilla autoregressive

### 3.3 도구 호출 능력 — 결정적 검증

#### 테스트 1: 일반 KB 질의
> "5월12일 6차플랜트 이상 상황 보고해줘"

- 7B 응답: `summarize_events(since=2026-05-12T00:00:00Z, area=P6)` ⭕ 도구 선택·area 매핑 정확
- 단 KST→UTC 변환 일부 생략 (27B는 `2026-05-11T15:00:00Z`로 정확)

#### 테스트 2: 시간 인자 정확도
> "지난 30분 동안 ficq-6101.pv의 값을 표시해줘"

- 7B 응답: `time_from: 2026-05-15T14:00:00, time_to: 2026-05-15T14:30:00` (**현재로부터 +16시간 미래!**)
- 결과 `count: 0` → "데이터 없습니다" 답변 (사실은 84건 존재)
- 원인: 7B가 "지난 30분"의 절대 시각 계산 못 함 → 학습 데이터에서 흔한 KST 14:00 차용 후 KST→UTC 변환 생략

**시스템 프롬프트 강화 후 재시도** (`BuildDateContextKo`에 시:분 + 상대시간 표 + 미래 금지):
- 결과: 시간은 그럴듯하게 표기되었으나 **도구 호출 자체를 skip**하고 가짜 값(50.2 → 51.1 단조 증가) 생성

#### 테스트 3: 결정적 검증 프롬프트

요청:
```
다음 3가지를 정확히 알려줘. 도구 결과만 인용, 추측 금지.
1) ficq-6101.pv 가장 최근 1건 (값 + UTC 시각)
2) ficq-6101.sp 가장 최근 1건 (값 + UTC 시각)
3) event_history_table 전체 이벤트 수
```

비교:

| 항목 | 7B 답변 | 실제 DB | 판정 |
|---|---|---|---|
| 1) ficq-6101.pv | `50.2` @ `22:19:00Z` | `47.4666...` @ `22:23:03.055Z` | ❌ |
| 2) ficq-6101.sp | `50.5` @ `22:18:00Z` | `34.4000...` @ `22:23:03.055Z` | ❌ |
| 3) event_history_table count | **`12345`** | **`53`** | ❌ |

**3가지 모두 fabrication**. 특히 `12345`는 챗봇 demo placeholder. "도구 결과 그대로 인용"이라고 7B가 표기까지 했음에도 실제 도구 호출 없음.

#### UI 측 도구 호출 카드
- 도구 토글: ON ✅
- 에이전트 모드(ReAct 강제): ON ✅
- **실제 도구 카드 표시: 없음** ❌

7B가 도구 호출 자체를 회피. ReAct 프롬프트도 무시.

### 3.4 일반 채팅 품질
- 한국어 자연스러움: ⭕ 양호 (시스템 프롬프트 한자 금지 강화 후)
- 프로젝트 설명 등 paraphrase: ⭕ 양호
- 시스템 프롬프트 내용 활용: ⭕ 양호

---

## 4. 결론

| 사용 유형 | 7B FP8 평가 |
|---|---|
| 일반 한국어 Q&A | ⭕ 사용 가능 |
| 시스템/문서 내용 paraphrase | ⭕ 사용 가능 |
| **실시간 도구 호출** | ❌ **결정적 실패** |
| **데이터 값 정확 인용** | ❌ **fabrication** |
| 속도 | ❌ 27B보다 느림 |
| 메모리 절약 | ⭕ 유일한 이점 (~70 GiB 절약) |

**판단 기준**: 운전원이 "최근 PV값"을 물었을 때 가짜 수치(50.2 vs 실제 47.47)를 사실로 받는 것은 산업 안전 위험. 12,345 같은 명백한 placeholder를 53건으로 보고하면 운영 판단 완전 왜곡.

→ **7B 운영 부적합 확정**.

---

## 5. 27B 롤백 절차

검증 종료 시 즉시 복귀.

### 롤백 스크립트 (`/tmp/restore-27b.sh`)

```bash
#!/bin/bash
# 27B vLLM 복귀
set -e
cd /home/windpacer/ai-models/spark-vllm-docker

# 현재 vllm 프로세스 종료
docker exec vllm_node pkill -f "vllm serve" 2>/dev/null || true
sleep 3

./launch-cluster.sh -t vllm-node-tf5 --solo -d \
  --apply-mod mods/vllm-tune-qwen--qwen3.6-27b-fp8-tp1 \
  exec vllm serve Qwen/Qwen3.6-27B-FP8 \
  --served-model-name Qwen3.6-27B-FP8 \
  --max-model-len 262144 \
  --max-num-seqs 4 \
  --enable-prefix-caching \
  --gpu-memory-utilization 0.80 \
  --port 8000 --host 0.0.0.0 \
  --load-format instanttensor \
  --enable-chunked-prefill \
  --enable-auto-tool-choice \
  --tool-call-parser qwen3_coder \
  --reasoning-parser qwen3 \
  --max-num-batched-tokens 32768 \
  --trust-remote-code \
  --default-chat-template-kwargs '{"preserve_thinking": true}' \
  --speculative-config '{"method": "qwen3_next_mtp", "num_speculative_tokens": 2}' \
  --generation-config auto \
  --override-generation-config '{"temperature": 0.6, "top_p": 0.95, "top_k": 20, "min_p": 0.0, "presence_penalty": 0.0, "repetition_penalty": 1.0}' \
  -tp 1
```

### 함께 복원
```bash
echo '{"vllm_model":"Qwen3.6-27B-FP8"}' > mcp-server/llm-model.json
```

C# 앱은 사용자 재시작 (`dotnet run --project src/Web/ExperionCrawler.csproj`).

### 로딩 시간 (캐시 hit)
- 약 75초 (가중치는 이미 `~/.cache/huggingface/`에 있음)

---

## 6. 향후 재시도 시 점검 항목

다른 모델/구성으로 재검토할 경우 반드시 통과해야 할 체크리스트:

### 필수 (이 중 하나라도 실패하면 운영 불가)
- [ ] **결정 프롬프트** (Section 3.3 테스트 3) 3개 항목 모두 도구 결과 정확 인용
- [ ] UI에서 `query_pv_history` / `run_sql` 도구 카드가 시각적으로 표시됨
- [ ] `event_history_table` 전체 카운트가 실제값과 일치 (추측 불가 숫자)
- [ ] 시각 인자에 미래 시각(NOW 이후) 절대 안 나옴
- [ ] 데이터 값이 단조 증가 demo 패턴(`50.2 → 51.1` 등)이 아님

### 우대 (있으면 좋음)
- [ ] 한자(중국어) 글자 응답에 섞이지 않음
- [ ] KST/UTC 라벨 정확
- [ ] 27B 대비 동등 또는 더 빠른 tok/s
- [ ] `plant_context.md` 의 area 매핑(`P1~P10`) 따라 인자 생성

### 검토 후보 (참고)
| 모델 | 비고 |
|---|---|
| **`gemma4-quantized` (사용자 우선순위)** | **다음 검토 1순위.** 70~80 GiB 점유 예상 (27B와 유사 위치). 배포 환경에서도 이걸 쓰기로 계획됨. 검증 시 GB10 호환 양자화 종류(AWQ/GPTQ/FP8/MXFP4 등) 확인 필요. tool-call-parser는 Gemma 전용 파서(`gemma`/`hermes` 호환 여부) 점검 |
| `RedHatAI/Qwen2.5-72B-Instruct-FP8` | 72B FP8 ~72 GiB. 도구 호출 신뢰성 27B에 근접 예상 |
| `exaone3.5:7.8b` (LG) | 한국어 특화 7.8B. 함수 호출 미지원일 수 있어 사전 확인 필요 |
| `Qwen3-30B-A3B-Instruct-2507` | Qwen3 차세대 MoE 30B (3B active). 출시되면 검토 가치 |

### Gemma4 검토 시 사전 준비 (TODO)
- [ ] HF에서 GB10·FP8 호환 정확한 repo 식별 (예: `google/gemma-4-27b-it`의 FP8/AWQ 변형, RedHatAI/neuralmagic 양자화 후속)
- [ ] vLLM의 Gemma 지원 버전 확인 — `--tool-call-parser` 옵션 (`gemma`, `pythonic`, `hermes` 중 어느 것)
- [ ] 기존 mod 디렉토리에 Gemma 관련 자료 유무 점검 (`/home/windpacer/ai-models/spark-vllm-docker/mods/fix-gemma4-tool-parser`가 이미 있음 — Gemma4 도구 파서 패치 시사)
- [ ] speculative decoding 가능 여부 (Gemma3는 미지원, Gemma4는 별도 확인)
- [ ] 27B FP8 (97 GiB)을 종료한 뒤에만 충분한 여유. 동시 운영은 메모리 부족 가능성 (배포 환경 기준 80GB로 가능)

---

## 7. 부산물 — 이 검증 과정에서 추가된 코드 개선

7B 검증 실패와 무관하게 시스템 전반에 도움이 되는 변경:

| 파일 | 개선 내용 |
|---|---|
| `src/Web/Controllers/OllamaController.cs` | `BuildDateContextKo`에 KST/UTC 현재 시:분 + 상대시간 변환 표(지난 30분/1시간/24시간/오늘/어제) + 미래 시각 금지 명시 |
| `src/Web/Controllers/OllamaController.cs` | `BaseSystemPromptKo`에 한자(중국어) 금지 강화 — 자주 출현하는 9글자(`请·您·是·了·我·会·什么·可以·需要`) 명시 |
| `src/Web/Controllers/OllamaController.cs` | `GetModels`에 Ollama `/api/show` 호출 → `capabilities=['embedding']` 모델(bge-m3, nomic-embed-text) 채팅 셀렉터에서 자동 제외 |
| `prompts/plant_context.md` | P1~P10/UTIL/PACKING area 매핑 + `event_history_table` vs `v_tag_summary` area 형식 차이 안내 + 의도별 권장 도구 표 |
| `mcp-server/server.py` | `_CLASSIFY_RULES`에 `이상.*상황\|상황.*보고\|이상.*보고\|비정상.*상황\|abnormal` → `summarize_events` 추가 |

27B에도 도움 — 그대로 유지.

---

## 8. 메모

- `/tmp/restore-27b.sh`는 임시 파일. 재기동 후 사라짐. 영구 보관하려면 `scripts/restore-27b.sh`로 옮기는 것 권장.
- 7B 가중치는 `~/.cache/huggingface/hub/models--RedHatAI--Qwen2.5-7B-Instruct-FP8-dynamic/`에 캐시됨 (~8 GiB). 디스크 정리 시 삭제 가능.
- 다음에 7B를 다시 시도할 경우 **결정 프롬프트(Section 3.3 테스트 3)부터** 30분 안에 통과 여부 판정 가능. 시간 낭비 방지.
