frankenstallm / source /scripts /quality_gate.sh
somebody-to-love's picture
Upload folder using huggingface_hub
841e474 verified
Raw History Blame
18.9 kB
#!/usr/bin/env bash
# =============================================================================
# quality_gate.sh โ€” Phase ์™„๋ฃŒ ์ž๋™ ํ’ˆ์งˆ ๊ฒŒ์ดํŠธ ๊ฒ€์ฆ
#
# Usage:
# bash scripts/quality_gate.sh <phase>
#
# Phases:
# pretrain โ€” ์‚ฌ์ „ํ•™์Šต ๊ฒŒ์ดํŠธ (val_loss, loss ๋‹จ์กฐ ๊ฐ์†Œ)
# sft โ€” SFT ๊ฒŒ์ดํŠธ (val_loss ์ˆ˜๋ ด, ๋ฐ˜๋ณต๋ฅ , KoBEST)
# orpo โ€” ORPO ๊ฒŒ์ดํŠธ (๋ฐ˜๋ณต๋ฅ , KoBEST, chosen > rejected)
# deploy โ€” ๋ฐฐํฌ ๊ฒŒ์ดํŠธ (GGUF perplexity, Ollama ์‘๋‹ต)
# all โ€” ๋ชจ๋“  ๊ฒŒ์ดํŠธ ์ˆœ์ฐจ ์‹คํ–‰
#
# Exit codes:
# 0 โ€” ๊ฒŒ์ดํŠธ ํ†ต๊ณผ
# 1 โ€” ๊ฒŒ์ดํŠธ ์‹คํŒจ (๊ธฐ์ค€ ๋ฏธ๋‹ฌ)
# 2 โ€” ํ•„์ˆ˜ ํŒŒ์ผ / ์˜์กด์„ฑ ์—†์Œ (์‹คํ–‰ ๋ถˆ๊ฐ€)
# =============================================================================
set -uo pipefail
PROJECT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
# ---------------------------------------------------------------------------
# ์ƒ‰์ƒ ์ถœ๋ ฅ ํ—ฌํผ
# ---------------------------------------------------------------------------
_RED='\033[0;31m'
_GREEN='\033[0;32m'
_YELLOW='\033[1;33m'
_BLUE='\033[0;34m'
_NC='\033[0m'
log_info() { echo -e "${_BLUE}[INFO]${_NC} $*"; }
log_ok() { echo -e "${_GREEN}[PASS]${_NC} $*"; }
log_warn() { echo -e "${_YELLOW}[WARN]${_NC} $*"; }
log_fail() { echo -e "${_RED}[FAIL]${_NC} $*"; }
log_skip() { echo -e " [SKIP] $*"; }
# ---------------------------------------------------------------------------
# ์œ ํ‹ธ๋ฆฌํ‹ฐ: Python ํ•œ ์ค„ ํ‘œํ˜„์‹ ํ‰๊ฐ€ (๋ถ€๋™์†Œ์ˆ˜์  ๋น„๊ต)
# ---------------------------------------------------------------------------
py_eval() {
python3 -c "import sys; sys.exit(0 if ($1) else 1)"
}
py_value() {
python3 -c "print($1)"
}
# ---------------------------------------------------------------------------
# ์œ ํ‹ธ๋ฆฌํ‹ฐ: JSON์—์„œ ๊ฐ’ ์ถ”์ถœ
# ---------------------------------------------------------------------------
json_get() {
local file="$1" key="$2"
python3 -c "
import json, sys
try:
d = json.load(open('$file'))
keys = '$key'.split('.')
for k in keys:
d = d[k]
print(d)
except Exception as e:
print('NOT_FOUND')
sys.exit(1)
"
}
# ---------------------------------------------------------------------------
# ๊ฒŒ์ดํŠธ ๊ฒฐ๊ณผ ์ง‘๊ณ„
# ---------------------------------------------------------------------------
GATE_PASS=0
GATE_FAIL=0
GATE_SKIP=0
record_pass() { GATE_PASS=$((GATE_PASS + 1)); log_ok "$*"; }
record_fail() { GATE_FAIL=$((GATE_FAIL + 1)); log_fail "$*"; }
record_skip() { GATE_SKIP=$((GATE_SKIP + 1)); log_skip "$*"; }
# =============================================================================
# Gate 1: Pretrain
# =============================================================================
gate_pretrain() {
echo ""
echo "=================================================================="
echo " Gate: PRETRAIN"
echo " ๊ธฐ์ค€: val_loss < 2.5 | loss ๋‹จ์กฐ ๊ฐ์†Œ ํ™•์ธ"
echo "=================================================================="
# ์ตœ์‹  ์ฒดํฌํฌ์ธํŠธ ๋””๋ ‰ํ† ๋ฆฌ ํƒ์ƒ‰
CKPT_BASE="$PROJECT_DIR/checkpoints"
METRICS_FILE=""
# metrics.json ๋˜๋Š” train_log.jsonl ํƒ์ƒ‰
for candidate in \
"$CKPT_BASE/korean_3b_fp8_pretrain/metrics.json" \
"$CKPT_BASE/korean_3b_pretrain/metrics.json" \
"$PROJECT_DIR/outputs/pretrain_metrics.json" \
"$PROJECT_DIR/logs/pretrain_metrics.json"
do
if [[ -f "$candidate" ]]; then
METRICS_FILE="$candidate"
break
fi
done
if [[ -z "$METRICS_FILE" ]]; then
log_warn "์‚ฌ์ „ํ•™์Šต ๋ฉ”ํŠธ๋ฆญ ํŒŒ์ผ์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค."
log_warn "์ฐพ๋Š” ๊ฒฝ๋กœ: $CKPT_BASE/korean_3b_*/metrics.json"
log_warn "๋ฉ”ํŠธ๋ฆญ ํŒŒ์ผ์ด ์—†์œผ๋ฉด ํ•™์Šต ์Šคํฌ๋ฆฝํŠธ์—์„œ ์•„๋ž˜ ํ˜•์‹์œผ๋กœ ์ €์žฅํ•˜์„ธ์š”:"
log_warn ' {"val_loss": 2.3, "loss_history": [3.1, 2.8, 2.5, 2.3]}'
record_skip "๋ฉ”ํŠธ๋ฆญ ํŒŒ์ผ ์—†์Œ โ€” ๊ฒŒ์ดํŠธ ๊ฑด๋„ˆ๋œ€"
return 0
fi
log_info "๋ฉ”ํŠธ๋ฆญ ํŒŒ์ผ: $METRICS_FILE"
# val_loss ํ™•์ธ
VAL_LOSS=$(json_get "$METRICS_FILE" "val_loss" 2>/dev/null || echo "NOT_FOUND")
if [[ "$VAL_LOSS" == "NOT_FOUND" ]]; then
record_skip "val_loss ํ‚ค ์—†์Œ โ€” ๊ฑด๋„ˆ๋œ€"
else
log_info "val_loss = $VAL_LOSS (๊ธฐ์ค€: < 2.5)"
if py_eval "$VAL_LOSS < 2.5" 2>/dev/null; then
record_pass "val_loss $VAL_LOSS < 2.5"
else
record_fail "val_loss $VAL_LOSS >= 2.5 (๊ธฐ์ค€ ๋ฏธ๋‹ฌ)"
fi
fi
# loss ๋‹จ์กฐ ๊ฐ์†Œ ํ™•์ธ (loss_history)
python3 - "$METRICS_FILE" <<'PYEOF'
import json, sys
metrics_file = sys.argv[1]
try:
d = json.load(open(metrics_file))
history = d.get("loss_history", [])
except Exception as e:
print(f"[SKIP] loss_history ์ฝ๊ธฐ ์‹คํŒจ: {e}")
sys.exit(0)
if len(history) < 2:
print(f"[SKIP] loss_history ๋ฐ์ดํ„ฐ ๋ถ€์กฑ ({len(history)}๊ฐœ)")
sys.exit(0)
# ์ „์ฒด ์ถ”์„ธ๊ฐ€ ๊ฐ์†Œํ•˜๋Š”์ง€ ํ™•์ธ (์ฒ˜์Œ 1/4 vs ๋งˆ์ง€๋ง‰ 1/4 ํ‰๊ท  ๋น„๊ต)
n = len(history)
q = max(1, n // 4)
early_avg = sum(history[:q]) / q
late_avg = sum(history[-q:]) / q
if late_avg < early_avg:
print(f"[PASS] loss ๋‹จ์กฐ ๊ฐ์†Œ ํ™•์ธ: ์ดˆ๊ธฐ avg={early_avg:.4f} โ†’ ์ตœ๊ทผ avg={late_avg:.4f}")
sys.exit(0)
else:
print(f"[FAIL] loss ๊ฐ์†Œ ๋ฏธํ™•์ธ: ์ดˆ๊ธฐ avg={early_avg:.4f}, ์ตœ๊ทผ avg={late_avg:.4f}")
sys.exit(1)
PYEOF
local mono_exit=$?
if [[ $mono_exit -eq 0 ]]; then
GATE_PASS=$((GATE_PASS + 1))
elif [[ $mono_exit -eq 1 ]]; then
GATE_FAIL=$((GATE_FAIL + 1))
fi
# exit 0 (SKIP) ๋Š” ์ด๋ฏธ ์ฒ˜๋ฆฌ๋จ
}
# =============================================================================
# Gate 2: SFT
# =============================================================================
gate_sft() {
echo ""
echo "=================================================================="
echo " Gate: SFT"
echo " ๊ธฐ์ค€: val_loss ์ˆ˜๋ ด | ๋ฐ˜๋ณต๋ฅ  < 15% | KoBEST > 55%"
echo "=================================================================="
METRICS_FILE=""
for candidate in \
"$PROJECT_DIR/outputs/sft_metrics.json" \
"$PROJECT_DIR/logs/sft_metrics.json" \
"$PROJECT_DIR/checkpoints/sft/metrics.json"
do
if [[ -f "$candidate" ]]; then
METRICS_FILE="$candidate"
break
fi
done
if [[ -z "$METRICS_FILE" ]]; then
log_warn "SFT ๋ฉ”ํŠธ๋ฆญ ํŒŒ์ผ์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค."
log_warn ' {"val_loss": 1.8, "rep_rate": 0.08, "kobest_score": 0.62}'
record_skip "SFT ๋ฉ”ํŠธ๋ฆญ ํŒŒ์ผ ์—†์Œ โ€” ๊ฒŒ์ดํŠธ ๊ฑด๋„ˆ๋œ€"
return 0
fi
log_info "๋ฉ”ํŠธ๋ฆญ ํŒŒ์ผ: $METRICS_FILE"
# val_loss ์ˆ˜๋ ด (์ƒ๋Œ€ ๋ณ€ํ™”์œจ < 1% โ€” ๋งˆ์ง€๋ง‰ ๋‘ ์ฒดํฌํฌ์ธํŠธ)
python3 - "$METRICS_FILE" <<'PYEOF'
import json, sys
metrics_file = sys.argv[1]
try:
d = json.load(open(metrics_file))
history = d.get("val_loss_history", [])
except Exception as e:
print(f"[SKIP] val_loss_history ์ฝ๊ธฐ ์‹คํŒจ: {e}")
sys.exit(0)
if len(history) < 2:
# ๋‹จ์ผ val_loss๋งŒ ์žˆ์œผ๋ฉด ๋‹จ์ˆœ ํ™•์ธ
val_loss = d.get("val_loss")
if val_loss is not None:
print(f"[INFO] val_loss = {val_loss} (์ˆ˜๋ ด ํžˆ์Šคํ† ๋ฆฌ ์—†์Œ โ€” ๋‹จ์ผ ๊ฐ’ ํ™•์ธ ๊ฑด๋„ˆ๋œ€)")
sys.exit(0)
last = history[-1]
second = history[-2]
rel_change = abs(last - second) / max(abs(second), 1e-9)
if rel_change < 0.01:
print(f"[PASS] val_loss ์ˆ˜๋ ด (์ƒ๋Œ€๋ณ€ํ™”์œจ {rel_change*100:.3f}% < 1%): {second:.4f} โ†’ {last:.4f}")
sys.exit(0)
else:
print(f"[FAIL] val_loss ๋ฏธ์ˆ˜๋ ด (์ƒ๋Œ€๋ณ€ํ™”์œจ {rel_change*100:.3f}% >= 1%): {second:.4f} โ†’ {last:.4f}")
sys.exit(1)
PYEOF
local conv_exit=$?
[[ $conv_exit -eq 0 ]] && GATE_PASS=$((GATE_PASS + 1)) || GATE_FAIL=$((GATE_FAIL + 1))
# ๋ฐ˜๋ณต๋ฅ  ํ™•์ธ
REP_RATE=$(json_get "$METRICS_FILE" "rep_rate" 2>/dev/null || echo "NOT_FOUND")
if [[ "$REP_RATE" == "NOT_FOUND" ]]; then
record_skip "rep_rate ํ‚ค ์—†์Œ โ€” ๊ฑด๋„ˆ๋œ€"
else
REP_PCT=$(py_value "$REP_RATE * 100")
log_info "๋ฐ˜๋ณต๋ฅ  = ${REP_PCT}% (๊ธฐ์ค€: < 15%)"
if py_eval "$REP_RATE < 0.15" 2>/dev/null; then
record_pass "๋ฐ˜๋ณต๋ฅ  ${REP_PCT}% < 15%"
else
record_fail "๋ฐ˜๋ณต๋ฅ  ${REP_PCT}% >= 15% (๊ธฐ์ค€ ๋ฏธ๋‹ฌ)"
fi
fi
# KoBEST ํ™•์ธ
KOBEST=$(json_get "$METRICS_FILE" "kobest_score" 2>/dev/null || echo "NOT_FOUND")
if [[ "$KOBEST" == "NOT_FOUND" ]]; then
record_skip "kobest_score ํ‚ค ์—†์Œ โ€” ๊ฑด๋„ˆ๋œ€"
else
KOBEST_PCT=$(py_value "$KOBEST * 100")
log_info "KoBEST = ${KOBEST_PCT}% (๊ธฐ์ค€: > 55%)"
if py_eval "$KOBEST > 0.55" 2>/dev/null; then
record_pass "KoBEST ${KOBEST_PCT}% > 55%"
else
record_fail "KoBEST ${KOBEST_PCT}% <= 55% (๊ธฐ์ค€ ๋ฏธ๋‹ฌ)"
fi
fi
}
# =============================================================================
# Gate 3: ORPO
# =============================================================================
gate_orpo() {
echo ""
echo "=================================================================="
echo " Gate: ORPO"
echo " ๊ธฐ์ค€: ๋ฐ˜๋ณต๋ฅ  < 5% | KoBEST > 60% | chosen > rejected 90%+"
echo "=================================================================="
METRICS_FILE=""
for candidate in \
"$PROJECT_DIR/outputs/orpo_metrics.json" \
"$PROJECT_DIR/logs/orpo_metrics.json" \
"$PROJECT_DIR/checkpoints/orpo/metrics.json"
do
if [[ -f "$candidate" ]]; then
METRICS_FILE="$candidate"
break
fi
done
if [[ -z "$METRICS_FILE" ]]; then
log_warn "ORPO ๋ฉ”ํŠธ๋ฆญ ํŒŒ์ผ์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค."
log_warn ' {"rep_rate": 0.03, "kobest_score": 0.63, "chosen_win_rate": 0.92}'
record_skip "ORPO ๋ฉ”ํŠธ๋ฆญ ํŒŒ์ผ ์—†์Œ โ€” ๊ฒŒ์ดํŠธ ๊ฑด๋„ˆ๋œ€"
return 0
fi
log_info "๋ฉ”ํŠธ๋ฆญ ํŒŒ์ผ: $METRICS_FILE"
# ๋ฐ˜๋ณต๋ฅ  (๋” ์—„๊ฒฉ: < 5%)
REP_RATE=$(json_get "$METRICS_FILE" "rep_rate" 2>/dev/null || echo "NOT_FOUND")
if [[ "$REP_RATE" == "NOT_FOUND" ]]; then
record_skip "rep_rate ํ‚ค ์—†์Œ โ€” ๊ฑด๋„ˆ๋œ€"
else
REP_PCT=$(py_value "$REP_RATE * 100")
log_info "๋ฐ˜๋ณต๋ฅ  = ${REP_PCT}% (๊ธฐ์ค€: < 5%)"
if py_eval "$REP_RATE < 0.05" 2>/dev/null; then
record_pass "๋ฐ˜๋ณต๋ฅ  ${REP_PCT}% < 5%"
else
record_fail "๋ฐ˜๋ณต๋ฅ  ${REP_PCT}% >= 5% (๊ธฐ์ค€ ๋ฏธ๋‹ฌ)"
fi
fi
# KoBEST (๋” ์—„๊ฒฉ: > 60%)
KOBEST=$(json_get "$METRICS_FILE" "kobest_score" 2>/dev/null || echo "NOT_FOUND")
if [[ "$KOBEST" == "NOT_FOUND" ]]; then
record_skip "kobest_score ํ‚ค ์—†์Œ โ€” ๊ฑด๋„ˆ๋œ€"
else
KOBEST_PCT=$(py_value "$KOBEST * 100")
log_info "KoBEST = ${KOBEST_PCT}% (๊ธฐ์ค€: > 60%)"
if py_eval "$KOBEST > 0.60" 2>/dev/null; then
record_pass "KoBEST ${KOBEST_PCT}% > 60%"
else
record_fail "KoBEST ${KOBEST_PCT}% <= 60% (๊ธฐ์ค€ ๋ฏธ๋‹ฌ)"
fi
fi
# Chosen win rate (chosen log-prob > rejected log-prob ๋น„์œจ)
CHOSEN_WIN=$(json_get "$METRICS_FILE" "chosen_win_rate" 2>/dev/null || echo "NOT_FOUND")
if [[ "$CHOSEN_WIN" == "NOT_FOUND" ]]; then
record_skip "chosen_win_rate ํ‚ค ์—†์Œ โ€” ๊ฑด๋„ˆ๋œ€"
else
WIN_PCT=$(py_value "$CHOSEN_WIN * 100")
log_info "Chosen win rate = ${WIN_PCT}% (๊ธฐ์ค€: >= 90%)"
if py_eval "$CHOSEN_WIN >= 0.90" 2>/dev/null; then
record_pass "Chosen win rate ${WIN_PCT}% >= 90%"
else
record_fail "Chosen win rate ${WIN_PCT}% < 90% (๊ธฐ์ค€ ๋ฏธ๋‹ฌ)"
fi
fi
}
# =============================================================================
# Gate 4: Deploy
# =============================================================================
gate_deploy() {
echo ""
echo "=================================================================="
echo " Gate: DEPLOY"
echo " ๊ธฐ์ค€: Q4_K_M perplexity < F16 ร— 1.05 | Ollama 5๊ฐœ ํ”„๋กฌํ”„ํŠธ ์‘๋‹ต"
echo "=================================================================="
local MODEL_NAME="frankenstallm-3b"
local GGUF_DIR="$PROJECT_DIR/outputs/gguf"
local F16_GGUF="$GGUF_DIR/${MODEL_NAME}-f16.gguf"
local Q4KM_GGUF="$GGUF_DIR/${MODEL_NAME}-Q4_K_M.gguf"
# --- GGUF ํŒŒ์ผ ์กด์žฌ ํ™•์ธ ---
if [[ ! -f "$Q4KM_GGUF" ]]; then
log_warn "Q4_K_M GGUF ํŒŒ์ผ ์—†์Œ: $Q4KM_GGUF"
log_warn "๋จผ์ € ์‹คํ–‰: bash scripts/convert_3b_gguf.sh"
record_skip "GGUF ํŒŒ์ผ ์—†์Œ โ€” perplexity ๊ฒŒ์ดํŠธ ๊ฑด๋„ˆ๋œ€"
else
# perplexity ์ธก์ • (llama-perplexity ๋˜๋Š” Python fallback)
LLAMA_PPL_BIN="$PROJECT_DIR/outputs/llama.cpp/build/bin/llama-perplexity"
if [[ ! -f "$LLAMA_PPL_BIN" ]]; then
log_warn "llama-perplexity ๋ฐ”์ด๋„ˆ๋ฆฌ ์—†์Œ โ€” ๋นŒ๋“œ ์‹œ๋„ ์ค‘ ..."
cmake --build "$PROJECT_DIR/outputs/llama.cpp/build" \
--target llama-perplexity -j "$(nproc)" &>/dev/null || true
fi
# ์ƒ˜ํ”Œ ํ…์ŠคํŠธ๋กœ perplexity ๋น„๊ต
SAMPLE_TEXT="$PROJECT_DIR/outputs/gguf/ppl_sample.txt"
if [[ ! -f "$SAMPLE_TEXT" ]]; then
# ์งง์€ ํ•œ๊ตญ์–ด ์ƒ˜ํ”Œ ์ƒ์„ฑ
cat > "$SAMPLE_TEXT" <<'SAMPLE'
์ธ๊ณต์ง€๋Šฅ์€ ํ˜„๋Œ€ ์‚ฌํšŒ์—์„œ ๋งค์šฐ ์ค‘์š”ํ•œ ๊ธฐ์ˆ ๋กœ ์ž๋ฆฌ์žก๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.
๊ธฐ๊ณ„ ํ•™์Šต๊ณผ ๋”ฅ๋Ÿฌ๋‹์˜ ๋ฐœ์ „์œผ๋กœ ์ธํ•ด ๋‹ค์–‘ํ•œ ๋ถ„์•ผ์—์„œ ํ˜์‹ ์ด ์ด๋ฃจ์–ด์ง€๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.
์ž์—ฐ์–ด ์ฒ˜๋ฆฌ ๊ธฐ์ˆ ์˜ ๋ฐœ์ „์€ ์ธ๊ฐ„๊ณผ ์ปดํ“จํ„ฐ์˜ ์ƒํ˜ธ์ž‘์šฉ ๋ฐฉ์‹์„ ๊ทผ๋ณธ์ ์œผ๋กœ ๋ณ€ํ™”์‹œํ‚ค๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.
ํ•œ๊ตญ์–ด๋Š” ๊ต์ฐฉ์–ด๋กœ์„œ ํŠน์œ ์˜ ํ˜•ํƒœ๋ก ์  ํŠน์„ฑ์„ ๊ฐ€์ง€๊ณ  ์žˆ์–ด ์ž์—ฐ์–ด ์ฒ˜๋ฆฌ์— ๋…ํŠนํ•œ ๋„์ „์„ ์ œ์‹œํ•ฉ๋‹ˆ๋‹ค.
๋Œ€๊ทœ๋ชจ ์–ธ์–ด ๋ชจ๋ธ์˜ ๋“ฑ์žฅ์œผ๋กœ ๊ธฐ๊ณ„ ๋ฒˆ์—ญ, ํ…์ŠคํŠธ ์š”์•ฝ, ์งˆ์˜์‘๋‹ต ๋“ฑ์˜ ์„ฑ๋Šฅ์ด ํฌ๊ฒŒ ํ–ฅ์ƒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.
SAMPLE
fi
if [[ -f "$LLAMA_PPL_BIN" && -f "$F16_GGUF" ]]; then
log_info "Perplexity ์ธก์ • ์ค‘ (F16 vs Q4_K_M) ..."
PPL_F16=$(timeout 120 "$LLAMA_PPL_BIN" -m "$F16_GGUF" -f "$SAMPLE_TEXT" 2>&1 \
| grep -oP "Perplexity: \K[0-9.]+" | head -1 || echo "0")
PPL_Q4=$(timeout 120 "$LLAMA_PPL_BIN" -m "$Q4KM_GGUF" -f "$SAMPLE_TEXT" 2>&1 \
| grep -oP "Perplexity: \K[0-9.]+" | head -1 || echo "0")
if [[ "$PPL_F16" == "0" || "$PPL_Q4" == "0" ]]; then
record_skip "Perplexity ์ธก์ • ์‹คํŒจ โ€” ๊ฑด๋„ˆ๋œ€"
else
THRESHOLD=$(py_value "$PPL_F16 * 1.05")
log_info "F16 PPL = $PPL_F16 | Q4_K_M PPL = $PPL_Q4 | ๊ธฐ์ค€: < $THRESHOLD"
if py_eval "$PPL_Q4 < $PPL_F16 * 1.05" 2>/dev/null; then
record_pass "Q4_K_M PPL $PPL_Q4 < F16 PPL ร— 1.05 ($THRESHOLD)"
else
record_fail "Q4_K_M PPL $PPL_Q4 >= F16 PPL ร— 1.05 ($THRESHOLD)"
fi
fi
else
record_skip "llama-perplexity ๋˜๋Š” F16 GGUF ์—†์Œ โ€” perplexity ๊ฒŒ์ดํŠธ ๊ฑด๋„ˆ๋œ€"
fi
fi
# --- Ollama ์‘๋‹ต ํ…Œ์ŠคํŠธ ---
if ! command -v ollama &>/dev/null; then
record_skip "ollama ์—†์Œ โ€” ์‘๋‹ต ํ…Œ์ŠคํŠธ ๊ฑด๋„ˆ๋œ€"
return 0
fi
if ! ollama list 2>/dev/null | grep -q "$MODEL_NAME"; then
log_warn "Ollama์— $MODEL_NAME ๋ชจ๋ธ์ด ๋“ฑ๋ก๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค."
log_warn "๋จผ์ € ์‹คํ–‰: bash scripts/deploy_3b_ollama.sh"
record_skip "Ollama ๋ชจ๋ธ ๋ฏธ๋“ฑ๋ก โ€” ์‘๋‹ต ํ…Œ์ŠคํŠธ ๊ฑด๋„ˆ๋œ€"
return 0
fi
log_info "Ollama ์‘๋‹ต ํ…Œ์ŠคํŠธ (5๊ฐœ ํ”„๋กฌํ”„ํŠธ) ..."
declare -a PROMPTS=(
"์•ˆ๋…•ํ•˜์„ธ์š”."
"1 ๋”ํ•˜๊ธฐ 1์€ ๋ฌด์—‡์ธ๊ฐ€์š”?"
"ํŒŒ์ด์ฌ์ด๋ž€ ๋ฌด์—‡์ธ๊ฐ€์š”?"
"ํ•œ๊ตญ์˜ ์ˆ˜๋„๋Š” ์–ด๋””์ธ๊ฐ€์š”?"
"์˜ค๋Š˜ ๋‚ ์”จ๊ฐ€ ์ข‹๋„ค์š”."
)
local PASS=0 FAIL=0
for i in "${!PROMPTS[@]}"; do
local PROMPT="${PROMPTS[$i]}"
local NUM=$((i + 1))
if RESP=$(timeout 45 ollama run "$MODEL_NAME" "$PROMPT" 2>&1) && [[ -n "$RESP" ]]; then
log_ok " ํ”„๋กฌํ”„ํŠธ $NUM ์‘๋‹ต OK (${#RESP}์ž)"
PASS=$((PASS + 1))
else
log_fail " ํ”„๋กฌํ”„ํŠธ $NUM ์‘๋‹ต ์‹คํŒจ"
FAIL=$((FAIL + 1))
fi
done
log_info "Ollama ์‘๋‹ต: $PASS/5 ์„ฑ๊ณต"
if [[ $FAIL -eq 0 ]]; then
record_pass "Ollama 5๊ฐœ ํ”„๋กฌํ”„ํŠธ ๋ชจ๋‘ ์‘๋‹ต ์„ฑ๊ณต"
else
record_fail "Ollama ์‘๋‹ต ์‹คํŒจ $FAIL/5"
fi
}
# =============================================================================
# ์ตœ์ข… ์š”์•ฝ ์ถœ๋ ฅ
# =============================================================================
print_summary() {
local phase="$1"
local TOTAL=$((GATE_PASS + GATE_FAIL + GATE_SKIP))
echo ""
echo "=================================================================="
echo " Quality Gate ๊ฒฐ๊ณผ: $phase"
echo " PASS: $GATE_PASS | FAIL: $GATE_FAIL | SKIP: $GATE_SKIP | TOTAL: $TOTAL"
echo "=================================================================="
if [[ $GATE_FAIL -eq 0 ]]; then
echo -e "${_GREEN} [GATE PASSED]${_NC} ๋ชจ๋“  ๊ฒ€์ฆ ๊ธฐ์ค€ ํ†ต๊ณผ"
echo ""
return 0
else
echo -e "${_RED} [GATE FAILED]${_NC} ${GATE_FAIL}๊ฐœ ๊ฒ€์ฆ ๊ธฐ์ค€ ๋ฏธ๋‹ฌ"
echo " ์‹คํŒจ ํ•ญ๋ชฉ์„ ์ˆ˜์ •ํ•œ ํ›„ ๋‹ค์‹œ ์‹คํ–‰ํ•˜์„ธ์š”."
echo ""
return 1
fi
}
# =============================================================================
# ์ง„์ž…์ 
# =============================================================================
PHASE="${1:-}"
if [[ -z "$PHASE" ]]; then
echo "Usage: bash scripts/quality_gate.sh <phase>"
echo " phase: pretrain | sft | orpo | deploy | all"
exit 2
fi
echo ""
echo "=================================================================="
echo " Quality Gate ๊ฒ€์ฆ ์‹œ์ž‘: $PHASE"
echo " ํ”„๋กœ์ ํŠธ: $PROJECT_DIR"
echo " ์‹œ๊ฐ : $(date '+%Y-%m-%d %H:%M:%S')"
echo "=================================================================="
case "$PHASE" in
pretrain)
gate_pretrain
print_summary "pretrain"
;;
sft)
gate_sft
print_summary "sft"
;;
orpo)
gate_orpo
print_summary "orpo"
;;
deploy)
gate_deploy
print_summary "deploy"
;;
all)
gate_pretrain
gate_sft
gate_orpo
gate_deploy
print_summary "all"
;;
*)
echo "ERROR: ์•Œ ์ˆ˜ ์—†๋Š” phase: $PHASE"
echo "Usage: bash scripts/quality_gate.sh <pretrain|sft|orpo|deploy|all>"
exit 2
;;
esac