| name | metis-finetune-eval |
| description | End-to-end playbook to fine-tune and evaluate an LLM on ThakiCloud Metis (kubeflow-llm-training + vLLM serving on compute-h200). Covers served-model data generation, datasetโS3, TrainJob submission (with every admission gotcha), LoRA adapter serving for inference, and gate-based A/B eval. Use when "๋ชจ๋ธ ํ์ต์์ผ์ค", "ํ์ธํ๋ ๋๋ ค์ค", "TrainJob ๋์์ค", "ํ์ต ์ ํ ํ๊ฐ/A/B", "๋ฐ๋ชจ GPU๋ก ํ์ต", "fine-tune on the cluster", "train and evaluate", "serve the adapter and compare". General across models. Do NOT use for local/Colab training (use colab-unsloth-finetune) or serving-only ops (use demo-llm-inference-test). |
Metis Fine-tune & Evaluate (playbook)
ThakiCloud Metis(kubeflow-llm-training + vLLM)์์ ๋ฐ์ดํฐ ์์ฑ โ ํ์ต(TrainJob) โ ์ด๋ํฐ ์๋น โ
๊ฒ์ดํธ ๊ธฐ๋ฐ A/B ํ๊ฐ๊น์ง์ ๊ฒ์ฆ๋ ์ ์ฐจ. 2026-07-14 TDS-UI-generator ์ค์ ์์ ์ถ์ถ. ๋ฅ๋ ฅ์
harness๊ฐ ์๋๋ผ ์ด ๊ณ์ฝ์ ์๋ค([[thin-harness-fat-skills]]). ํ๊ฐ๋ ์คํ ๊ฒ์ดํธ๋ก ๋ซ๋๋ค
([[close-the-agent-loop]]ยท[[evaluator-must-act]]).
0. ํด๋ฌ์คํฐ ์งํ (์ ๋ณธ)
| ๋์ | ๊ฐ |
|---|
| GPU ํ์ต/์๋น ์ปจํ
์คํธ | tkai-prod-compute-h200 (cluster id cc3adee7-4c6b) |
| ์๋น ํ๋ฉด | tkai-prod-mgmt |
| ํ๋ก์ ํธ ๋ค์์คํ์ด์ค | project-<uuid> (Kueue localqueue default โ ClusterQueue gpu-cluster-queue) |
| TrainJob runtime | llm-training (trainer.kubeflow.org/v1alpha1, torch), ์ด๋ฏธ์ง cr2.thakicloud.net/ai-platform/kubeflow-llm-training |
| S3 (๋ชจ๋ธยท๋ฐ์ดํฐยท์ถ๋ ฅ) | s3://ai-platform/..., endpoint http://objects3-dev.thakicloud.net, ์ํฌ๋ฆฟ tkai-s3-secret(4ํค) |
| ์๋น ์ด๋ฏธ์ง | vllm/vllm-openai (dockerhub, no pull secret), S3 ๋ก๋ = --load-format=runai_streamer |
| GPU LimitRange | Container/Pod max cpu 8 / mem 80Gi (์ด๊ณผ ์ admission ๊ฑฐ๋ถ) |
unset KUBECONFIG ํ --context tkai-prod-compute-h200 ์ฌ์ฉ. ๋ชจ๋ธ์ s3://ai-platform/global/models/<hf-id> (bf16 ํ์ต๋ณธ ์กด์ฌ; NVFP4๋ ์๋น๋ณธ).
1. ๋ฐ์ดํฐ ์์ฑ (์๋น ๋ชจ๋ธ = ๋ถํธ์คํธ๋ฉ ์์ฑ๊ธฐ)
served vLLM์ pod ingress URL๋ก ์ง์ ํธ์ถ(https://<hash>-8000.demo.thakicloud.net?cluster=<id>).
โ ๏ธ demo.thakicloud.net์ SPA soft-404 โ ๋ฐ๋์ pod ingress URL์ ์ด๋ค. /v1/models๋ก ๋ชจ๋ธ๋ช
ํ์ธ.
Qwen3.6์ reasoning ๋ชจ๋ธ โ chat_template_kwargs:{"enable_thinking":false} ์ ํ๋ฉด content=null.
์ฌ์ฌ์ฉ: .claude/skills/tds-ui-generator/scripts/vllm_client.py(enable_thinking off + JSON ์ถ์ถ).
2. ๋ฐ์ดํฐ์
โ S3 (โ ๋ก๋ ๊ณ์ฝ ํ์)
- SFT ๋ก๋๋
messages ์ปฌ๋ผ๋ง ์ ์ง(prompt/completion์ strip). โ {"messages":[{role,content}...]} ํฌ๋งท.
- DPO๋
prompt/chosen/rejected. GRPO๋ prompt/answer.
- ์ ์ฉ prefix์ train split๋ง ๊ฒฉ๋ฆฌ(๋ก๋๊ฐ prefix ๋ด ๋ชจ๋ jsonl์ ๋จน์ผ๋ report.json ๋ฑ ์์ง ๋ง ๊ฒ).
- boto3 ์
๋ก๋:
tkai-s3-secret์ 4ํค(OBJECT_ACCESS_KEY_ID/SECRET/ENDPOINT_URL/REGION) + endpoint.
- held-out eval split๋ ๊ฐ์ messages ํฌ๋งท์ผ๋ก ๋ณ๋ prefix ์
๋ก๋.
3. ํ์ต (TrainJob) โ env ๊ณ์ฝ + admission ๊ฒ์ดํธ
ํ
ํ๋ฆฟ: assets/trainjob-template.yaml. env๋ train_entrypoint.build_config_from_env() ๊ณ์ฝ:
METHOD(sft|cpt|dpo|grpo|gkd) ยท MODEL_PATH(s3 bf16) ยท DATASET_PATH(s3 prefix) ยท OUTPUT_PATH(s3) ยท
STRATEGY(ddp|fsdp) ยท USE_LORA/LORA_R/LORA_ALPHA/LORA_DROPOUT ยท QUANTIZATION(none|4bit|8bit) ยท
EPOCHS/MAX_STEPS/BATCH_SIZE/GRAD_ACCUM_STEPS/LEARNING_RATE/MAX_LENGTH(โ ๏ธ๊ธฐ๋ณธ 128 ๊ณผ์, IR์ 2048) ยท
EVAL_DATASET_PATH+EVAL_STEPS(held-out eval ๊ณก์ = A/B ๋ฌด๋ฃ, ์ด๋ํฐ ์ธ๋ฉ๋ชจ๋ฆฌ๋ผ merge ๋ถ์).
โ admission 3์ค ๊ฒ์ดํธ(์์๋๋ก ๋ค ๊ฑธ๋ฆฐ๋ค):
- Kyverno: ๋ชจ๋ ์ปจํ
์ด๋์ cpuยทmemory requests+limits ํ์(GPU๋ง ์ง์ ์
JobCreationFailed).
- LimitRange: limits โค cpu 8 / mem 80Gi(์ด๊ณผ ์
requests must not be above the limitRange max).
- MultiKueue:
labels: kueue.x-k8s.io/queue-name: default, GPU ์ฌ์ ์์ผ๋ฉด Pending(preempt ๊ธ์ง โ ๋ฐ๋ชจ ์๋น ๋ณดํธ). GPU ํฌํ๋ฉด nvidia.com/gpu: "1"๋ก ์ถ์ํด 1์ฅ๋ง ๋น๋ฉด ์์.
์ ์ถ ์ kubectl apply --dry-run=server๋ก ๊ฒ์ฆ. ์ํ: get trainjob, get workloads(ADMITTED), pod ๋ก๊ทธ.
4. ์ด๋ํฐ ์๋น (inference/A/B์ฉ) โ s3-lora ํจ์
ํ
ํ๋ฆฟ: assets/serve-lora-template.yaml. bf16 base + --enable-lora --max-lora-rank <r> --lora-modules=<name>=/adapter.
โ vLLM LoRA ๋ก๋๋ s3:// ๋ฏธ์ง์(runai_streamer๋ --model์๋ง) โ --lora-modules=tds=s3://...์
No adapter found๋ก crash loop. initContainer๊ฐ ์ด๋ํฐ๋ฅผ S3โ๋ก์ปฌ emptyDir(/adapter)๋ก ๋ค์ด๋ก๋ ํ
๋ก์ปฌ ๊ฒฝ๋ก ์ง์ . base๋ --model=s3://... + --load-format=runai_streamer๋ก ์ง์ ๋ก๋(OK).
ํฌํธํฌ์๋๋ก /v1/models ํ์ธ(base+<adapter> ๋
ธ์ถ) โ model=<adapter>๋ก ์์ฑ.
5. A/B ํ๊ฐ (๊ฒ์ดํธ ๊ธฐ๋ฐ, ์คํ์ด ์ ์ผ ์ ํจ ์งํ)
์ฌ์ฌ์ฉ: .claude/skills/tds-ui-generator/scripts/ab_eval.py(held-out โ ์์ฑ โ ๊ฒ์ดํธ ํต๊ณผ์จ + F1).
before=base ์๋น, after=์ด๋ํฐ ์๋น, ๋์ผ ๊ฒ์ดํธยท๋์ผ held-out. ๊ตํ(3์ค ํ๋ก์ ์คํจ):
compile-okยทteacher-forced token-accยทlight-LoRA์ token-acc๋ ๋ชจ๋ autoregressive ์์ฑ ํ์ง๊ณผ
์ด๊ธ๋๋ค โ ์์ ํ registry/์คํ์ ๋ํ ์คํ ๊ฒ์ดํธ๋ง ์ง์ง ๊ฐญ์ ๋๋ฌ๋ธ๋ค. ๊ฒ์ดํธ ์คํ์ด
๋ถ์์ ํ๋ฉด valid ์ถ๋ ฅ์ false-negative๋ก ๋จ๊ตฌ๋, ํ๊ฐ ์ ์คํ ์ ์ ์ถ์ถ๋ก ์์ฑํ๋ค.
6. ์ ๋ฆฌ (ํ์)
ํ๊ฐ ํ ์๋น Deploymentยท์๋ฃ TrainJob ์ญ์ ๋ก GPU ๋ฐํ([[destructive-batch-guard]]๋ ์ญ์ ์ค์ฝํ ํ์ธ).
ํฌํธํฌ์๋ kill. ๋ฐ๋ชจ ์๋น(bench-*)์ ๊ฑด๋๋ฆฌ์ง ์๋๋ค.
gotchas (์คํจ์์)
- reasoning ๋ชจ๋ธ โ enable_thinking off ์ ํ๋ฉด ๋น ์๋ต. SPA soft-404 URL ์ฃผ์.
- SFT ๋ก๋
messages-only. MAX_LENGTH ๊ธฐ๋ณธ 128. Kyverno/LimitRange/Kueue 3์ค ๊ฒ์ดํธ.
- vLLM LoRA๋ ๋ก์ปฌ ๊ฒฝ๋ก๋ง(s3 crash). LoRA๋ ์ด๋ํฐ๋ง ์ ์ฅ(EVAL์ PEFT ๋ฏธ์ง์ โ merge ๋๋ ์๋น).
- token-acc ํฌํ = ์์ฑ ํ์ง ์๋. ๊ฒ์ดํธ ์คํ ์์ฑ๋๊ฐ ํ๋ณ๋ ฅ์ ์ข์ฐ.
- 300-step ๊ฒฝ๋ LoRA๋ 27B ์์ฑ์ ์ ๋ชป ๋ฐ๊ฟ โ ๋ฐ์ดํฐ๊ฐ ์ค์ ๊ณ์ฝ์ ์์ฐํด์ผ, ์ถฉ๋ถํ ๊ธธ๊ฒ.
์ํฌ๋ ์์ (์ ๊ณผ์ ์ค๋์)
.claude/skills/tds-ui-generator/ โ ์ด ํ๋ ์ด๋ถ์ ์ฒซ ์ค์ ์ ์ฉ(๋ฐ์ดํฐ์์งยท๊ฒ์ดํธยทํ์ตยท์๋นยทA/B ์คํฌ๋ฆฝํธ ์ ๋ณธ).