
Projects
BizKit AI 모델 선택과 벤치마크
좋아 보이는 모델이 아니라 명함 텍스트 정확도, JSON 안정성, Milvus 검색 품질이라는 서비스 실패 조건을 분석해 모델을 선택한 기록입니다.
프로젝트 맥락
BizKit의 AI 기능은 한 종류의 모델만 붙이면 되는 구조가 아니었습니다. 명함 생성은 이미지 생성 모델과 텍스트 렌더링이 필요했고, 6각 차트와 직무 분석은 LLM의 구조화된 응답이 필요했습니다. 직무 필터링과 자연어 검색에는 임베딩 모델과 Vector DB가 필요했습니다.
그래서 모델 선택의 기준도 단순히 벤치마크 점수가 높은 모델이 아니었습니다. 서비스 기능에서 실패하면 안 되는 지점이 무엇인지 먼저 정하고, 그 제약을 만족하는 모델을 선택하는 방식으로 접근했습니다.
- 명함 생성: 배경 품질보다 텍스트 정확성과 레이아웃 안정성이 중요함
- 6각 차트 분석: 점수는 코드가 계산하고 LLM은 해석만 맡아야 함
- 직무 분석: JSON 스키마 준수와 한국어 설명 품질이 중요함
- 임베딩 검색: Milvus 통합 후에도 Recall, MRR, latency가 유지되어야 함
- 운영: RunPod에서 감당 가능한 VRAM, 응답 시간, cold start 리스크를 고려해야 함
이 글은 BizKit AI 서버 메인글의 상세 모델 선택 기록입니다. AI 서버 아키텍처 설계가 "왜 FastAPI와 RunPod 경로로 분리했는가"를 다룬다면, 이 글은 "어떤 모델을 어떤 근거로 골랐는가"를 다룹니다.
선택 기준
모델을 고를 때 가장 먼저 한 일은 기능별 실패 조건을 정의하는 것이었습니다. AI 모델은 데모에서는 좋아 보여도 서비스 계약 안에 들어오면 다른 문제가 생깁니다.
명함 생성에서는 이미지가 예쁜 것보다 텍스트가 틀리지 않는 것이 더 중요했습니다. 전화번호, 이메일, 주소가 한 글자라도 틀리면 결과물을 사용할 수 없습니다. 그래서 이미지 생성 모델에게 명함 전체를 맡기지 않고, 배경 생성과 텍스트 렌더링을 분리하는 방향으로 기준을 잡았습니다.
LLM은 자연어 설명을 잘 만드는 것만으로는 부족했습니다. 6각 차트와 직무 분석 결과는 프론트엔드가 바로 사용할 수 있는 JSON이어야 했습니다. 그래서 JSON 파싱 실패율, 스키마 준수, 점수 보정의 안정성을 평가 기준에 포함했습니다.
임베딩 모델은 오프라인 벤치마크만으로 결정하지 않았습니다. 실제 Milvus 인덱스에 넣었을 때 검색 품질과 응답 시간이 유지되는지 확인했습니다.
model decision criteria
-> service failure mode
-> benchmark metric
-> runtime cost
-> integration risk
-> final model
선별 데이터 정리 방식
벤치마크 데이터는 후보마다 같은 수준으로 남아 있지 않았습니다. 그래서 모든 후보를 억지로 같은 표에 넣기보다, 실제로 측정한 수치는 수치로 적고, 수치보다 서비스 제약으로 제외한 후보는 탈락 기준을 따로 적었습니다.
위키에 남긴 측정 데이터는 크게 세 묶음이었습니다.
| 영역 | 실행 환경 | 테스트 규모 | 주요 지표 |
|---|---|---|---|
| 이미지 생성 | Google Colab, A100 중심, Qwen-Image는 H100 | 프롬프트 3종 | 추론 시간, Peak VRAM, 배경 안정성 |
| LLM | RunPod Serverless, L40 | HEX 10개, Job 10개 | 품질 점수, latency, tokens/sec, 성공률, JSON 실패율 |
| 임베딩 | 로컬 벤치마크 + Milvus 통합 테스트 | corpus 100개, 검색/캐싱/분류 테스트셋 | Recall@5, MRR, F1, latency, cache gap |
| 구분 | 사용한 데이터 | 글에 반영한 방식 |
|---|---|---|
| 정량 측정 | latency, VRAM, quality score, success rate, Recall, MRR, F1 | 수치로 직접 표기 |
| 정성 판단 | 개인정보 리스크, API 의존성, 텍스트 정확도 리스크, 운영 복잡도 | 탈락 사유로 표기 |
| 제외한 값 | 추정 수치, 출처가 불명확한 비교값, 기억에 의존한 점수 | 표에 넣지 않음 |
최종 판단은 한 번의 최고 점수로 정하지 않았습니다. 먼저 서비스 실패 조건으로 후보를 줄이고, 남은 후보에서 성능과 운영 비용을 비교했습니다.
| 순서 | 확인한 기준 | 판단 내용 |
|---|---|---|
| 1 | 서비스 계약 | 결과가 프론트엔드와 백엔드 계약에 맞는가 |
| 2 | 운영 가능성 | RunPod에서 VRAM, latency, 비용을 감당할 수 있는가 |
| 3 | 벤치마크 | 기능별 품질 지표가 충분한가 |
| 4 | 통합 리스크 | API, Vector DB, 후처리 파이프라인과 안정적으로 붙는가 |
| 5 | 최종 결정 | 기능 실패 조건을 가장 적게 만드는 선택인가 |
이미지 생성 모델
이미지 생성 모델의 후보는 SDXL Base, SD3.5 Large Turbo, Z-Image Turbo, Korean SD v1.5, Qwen-Image, Gemini 3 Pro Image 계열이었습니다.
처음에는 텍스트까지 이미지 모델에 맡길 수 있는 모델이 매력적으로 보였습니다. 하지만 명함에서는 모델이 만든 글자 하나가 틀리면 바로 실패입니다. 그래서 최종 기준은 "명함 전체를 한 번에 생성하는 능력"이 아니라 "텍스트가 올라갈 수 있는 안정적인 배경을 빠르게 만드는 능력"이었습니다.
평가 기준은 다음과 같았습니다.
- 넓은 네거티브 스페이스를 만들 수 있는가
- 종이 질감, 조명, 여백이 안정적인가
- 텍스트 오버레이와 충돌하지 않는가
- 추론 시간과 VRAM이 RunPod 운영에 맞는가
- 추후 LoRA, ControlNet, 업스케일 확장이 가능한가
최종 선택은 SDXL Base였습니다.
selected image model: SDXL Base
resolution: 1104x624
steps: 25
latency: 2.86s ~ 2.88s
peak VRAM: 9.21GB
이미지 생성 모델은 아래 기준으로 선별했습니다.
| 후보 | 체크포인트/서비스 | 모델 규모 | 측정 추론 시간 | Peak VRAM | 배경 안정성 | 운영 판단 |
|---|---|---|---|---|---|---|
| SDXL Base | stabilityai/stable-diffusion-xl-base-1.0 | 3.5B / 약 7GB | 2.86s~2.88s | 9.21GB | 높음 | 선택 |
| SD3.5 Large Turbo | stable-diffusion-3.5-large-turbo | 8.1B / 약 16GB | 9.20s~12.74s | 6.09GB | 중 | 제외 |
| Z-Image Turbo | Tongyi-MAI/Z-Image-Turbo | 6B / 약 12~16GB | 2.80s~4.70s | 20.85GB | 중~높음 | 제외 |
| Korean SD v1.5 | Bingsu/my-korean-stable-diffusion-v1-5 | 860M~1B / 약 4~7GB | 1.76s~3.85s | 4.15GB | 낮음 | 제외 |
| Qwen-Image | Qwen/Qwen-Image-2512 | 20B / 약 40GB | 8.59s~10.36s | 59.71GB | 높음 | 제외 |
| Nano Banana Pro | Gemini 3 Pro Image 계열 | 비공개 | 12.45s~16.76s | 로컬 추론 불가 | 높음 | 기본 경로 제외 |
이미지 생성은 같은 프롬프트 3종을 기준으로 비교했습니다. 목표는 명함 전체를 예쁘게 그리는 것이 아니라, 텍스트 오버레이가 가능한 네거티브 스페이스와 종이 질감이 안정적으로 나오는지 확인하는 것이었습니다.
| 모델 | 프롬프트 | 해상도 | Steps | Guidance/CFG | 추론 시간 | Peak VRAM |
|---|---|---|---|---|---|---|
| SDXL Base | p1 | 1104 x 624 | 25 | 7.5 | 2.88s | 9.21GB |
| SDXL Base | p2 | 1104 x 624 | 25 | 7.5 | 2.86s | 9.21GB |
| SDXL Base | p3 | 1104 x 624 | 25 | 7.5 | 2.86s | 9.21GB |
| SD3.5 Large Turbo | p1 | 1104 x 624 | 4 | 0.0 | 12.74s | 6.09GB |
| SD3.5 Large Turbo | p2 | 1104 x 624 | 4 | 0.0 | 9.77s | 6.09GB |
| SD3.5 Large Turbo | p3 | 1104 x 624 | 4 | 0.0 | 9.20s | 6.09GB |
| Z-Image Turbo | p1 | 1104 x 624 | 9 | 0.0 | 4.70s | 20.85GB |
| Z-Image Turbo | p2 | 1104 x 624 | 9 | 0.0 | 2.81s | 20.85GB |
| Z-Image Turbo | p3 | 1104 x 624 | 9 | 0.0 | 2.80s | 20.85GB |
| Korean SD v1.5 | p1 | 1104 x 624 | 25 | 7.5 | 3.85s | 4.15GB |
| Korean SD v1.5 | p2 | 1104 x 624 | 25 | 7.5 | 1.77s | 4.15GB |
| Korean SD v1.5 | p3 | 1104 x 624 | 25 | 7.5 | 1.76s | 4.15GB |
| Qwen-Image | p1 | 1152 x 640 | 25 | 4.0 | 10.36s | 59.71GB |
| Qwen-Image | p2 | 1152 x 640 | 25 | 4.0 | 8.61s | 59.71GB |
| Qwen-Image | p3 | 1152 x 640 | 25 | 4.0 | 8.59s | 59.71GB |
| Nano Banana Pro | p1 | API | - | - | 14.55s | 클라우드 |
| Nano Banana Pro | p2 | API | - | - | 16.76s | 클라우드 |
| Nano Banana Pro | p3 | API | - | - | 12.45s | 클라우드 |
SD3.5 Large Turbo는 4 steps 모델이라 빠를 것으로 기대했지만, 실측에서는 9초 이상이 걸렸습니다. Z-Image Turbo는 속도는 괜찮았지만 Peak VRAM이 20.85GB까지 올라 동시성 운영에서 부담이 컸습니다. Korean SD v1.5는 가볍지만 배경 품질과 해상도 확장성에서 부족했습니다. Qwen-Image는 텍스트와 편집 능력은 좋지만 59.71GB VRAM이 필요해 배경 전용 모델로는 과했습니다.
Gemini 계열은 로컬 인프라 없이 빠르게 붙일 수 있다는 장점이 있지만, 위키 작성 당시 기준 이미지 1장당 약 $0.134 비용이 있었고, 외부 API 정책과 개인정보 전송 이슈를 함께 고려해야 했습니다. 무엇보다 명함의 전화번호, 이메일, 주소는 한 글자만 틀려도 사용할 수 없기 때문에 텍스트를 이미지 모델이 직접 그리는 구조는 기본 경로에서 제외했습니다.
그래서 명함 생성 파이프라인은 다음처럼 나눴습니다.
user style request
-> LLM converts style to SDXL prompt and layout hint
-> SDXL generates background image
-> renderer places verified text fields
-> final card image
이 구조의 핵심은 텍스트를 이미지 모델에게 맡기지 않는 점입니다. 배경은 생성 모델이 만들고, 이름, 전화번호, 이메일 같은 값은 렌더러가 확정합니다. 이 선택 덕분에 모델 품질과 서비스 정확도를 분리할 수 있었습니다.
LLM
LLM은 HEX 분석과 직무 분석 두 작업을 기준으로 평가했습니다.
HEX 분석은 GitHub 활동, 사용자 역량 정보, 동료 리뷰를 바탕으로 6개 축의 점수와 해석을 만드는 기능입니다. 여기서 점수 자체는 LLM이 만들면 안 됩니다. PR, 리뷰, README, 언어 다양성 같은 관측 가능한 데이터로 1차 점수를 계산하고, LLM은 그 점수의 의미를 설명하는 역할만 맡아야 했습니다.
직무 분석은 회사, 부서, 직무, 프로젝트 정보를 바탕으로 자기소개 문단과 검색 신뢰도를 반환하는 기능입니다. 이 기능에서는 한국어 설명 품질, JSON 형식 준수, 검색 컨텍스트를 반영하는 능력이 중요했습니다.
비교 대상은 EXAONE 3.5 7.8B, Llama 3.1 8B, Qwen2.5 7B, Qwen3 8B였습니다.
평가 기준은 다음과 같았습니다.
- JSON 스키마를 지키는가
- 점수 근거를 과하게 만들지 않는가
- 한국어 설명이 자연스러운가
- 같은 입력에서 일관된 결과를 내는가
- 환각 탐지와 근거 제한이 가능한가
- latency, tokens/sec가 서비스 운영에 맞는가
- 파싱 실패율이 낮은가
최종 선택은 Qwen2.5 7B Instruct였습니다.
selected LLM: Qwen2.5 7B Instruct
HEX quality score: 0.98
Job quality score: 0.86
average latency: 15386ms
throughput: 36.8 tokens/sec
success rate: 100.0%
JSON parse failure rate: 0.00%
LLM 후보는 다음 데이터로 정리했습니다.
| 후보 | HEX 점수 | Job 점수 | 평균 Latency | Tokens/sec | 종합 점수 | 결정 |
|---|---|---|---|---|---|---|
| Qwen2.5 7B Instruct | 0.98 | 0.86 | 15386ms | 36.8 | 0.78 | 선택 |
| Llama 3.1 8B Instruct | 0.98 | 0.85 | 18034ms | 26.6 | 0.76 | 제외 |
| Qwen3 8B | 0.97 | 0.84 | 25793ms | 24.5 | 0.75 | 제외 |
| EXAONE 3.5 7.8B Instruct | 0.87 | 0.86 | 15121ms | 31.4 | 0.72 | 제외 |
LLM 품질 평가는 HEX와 Job을 분리했습니다. HEX는 JSON 스키마 준수, 점수 정확도, 한국어 품질, 일관성, 환각 탐지를 봤고, Job은 JSON 스키마 준수, 내용 정확도, 한국어 품질, 검색 컨텍스트 활용, 환각 탐지를 봤습니다.
| 모델 | 태스크 | JSON | 정확도 | 한국어 품질 | 일관성/검색 컨텍스트 | 환각 지표 | 가중 평균 |
|---|---|---|---|---|---|---|---|
| Qwen2.5 7B Instruct | HEX | 1.000 | 0.998 | 0.929 | 0.990 | 1.000 | 0.984 |
| Qwen2.5 7B Instruct | Job | 1.000 | 0.990 | 0.994 | 0.830 | 0.280 | 0.863 |
| Llama 3.1 8B Instruct | HEX | 1.000 | 0.962 | 0.952 | 0.973 | 1.000 | 0.977 |
| Llama 3.1 8B Instruct | Job | 1.000 | 0.970 | 0.978 | 0.858 | 0.200 | 0.846 |
| Qwen3 8B | HEX | 1.000 | 1.000 | 0.971 | 0.990 | 0.840 | 0.969 |
| Qwen3 8B | Job | 1.000 | 1.000 | 0.996 | 0.828 | 0.140 | 0.844 |
| EXAONE 3.5 7.8B Instruct | HEX | 1.000 | 0.937 | 0.961 | 1.000 | 0.933 | 0.870 |
| EXAONE 3.5 7.8B Instruct | Job | 1.000 | 1.000 | 1.000 | 0.978 | 0.100 | 0.862 |
성능 평가는 RunPod Serverless L40 환경에서 cold start와 실제 요청 latency를 분리해 봤습니다.
| 모델 | Cold Start | 평균 Latency | P50 | P95 | P99 | Tokens/sec | 성공률 | 타임아웃률 | JSON 실패율 |
|---|---|---|---|---|---|---|---|---|---|
| Qwen2.5 7B Instruct | 33149ms | 15386ms | 12161ms | 40469ms | 40469ms | 36.8 | 100.0% | 0.00% | 0.00% |
| EXAONE 3.5 7.8B Instruct | 39944ms | 15121ms | 5975ms | 36664ms | 36664ms | 31.4 | 100.0% | 0.00% | 0.00% |
| Llama 3.1 8B Instruct | 39041ms | 18034ms | 14235ms | 77207ms | 77207ms | 26.6 | 100.0% | 0.00% | 0.00% |
| Qwen3 8B | 90573ms | 25793ms | 19334ms | 143156ms | 143156ms | 24.5 | 100.0% | 0.00% | 0.00% |
EXAONE은 Job 태스크에서 한국어 품질과 검색 컨텍스트 활용이 좋았습니다. 하지만 HEX 점수와 종합 점수에서 밀렸고, 처리량도 Qwen2.5보다 낮았습니다. Llama 3.1은 HEX 품질이 좋았지만 평균 latency와 tokens/sec에서 불리했습니다. Qwen3는 최신 계열의 장점이 있었지만 cold start가 90초를 넘고 P95 latency가 143초까지 올라 서비스 기본 모델로 두기 어려웠습니다.
그래서 최종 선택은 Qwen2.5 7B Instruct였습니다. 최고 품질 항목을 모두 독식한 모델은 아니지만, HEX 0.984, Job 0.863, 36.8 tokens/sec, JSON 실패율 0.00%를 동시에 만족했습니다.
LLM 선택에서 얻은 기준은 명확했습니다. 서비스의 핵심 값은 코드가 계산하고, LLM은 설명과 요약에만 쓰는 편이 안정적입니다.
GitHub metrics + review data
-> rule-based score calculation
-> LLM explanation
-> JSON schema validation
-> frontend response
임베딩 모델
임베딩 모델은 직무 필터링, 유사 직무 검색, 시맨틱 캐시를 위해 필요했습니다. 사용자가 입력한 회사, 부서, 직무가 개발과 관련 있는지 판단해야 했고, 비슷한 자기소개 생성 요청은 매번 LLM을 호출하지 않고 재사용할 수 있어야 했습니다.
비교 대상은 KURE-v1, BGE-m3-ko, BGE-m3, gte-base-korean, Qwen3-0.6b, KoELECTRA였습니다.
1차 벤치마크에서는 테스트 코퍼스 100개로 Recall@5, MRR, F1, latency, cache gap을 비교했습니다. 2차에서는 Milvus 통합 환경에서 IVF_FLAT과 HNSW 인덱스를 비교했습니다.
최종 선택은 BGE-m3-ko와 IVF_FLAT 조합이었습니다.
selected embedding model: BGE-m3-ko
selected index: Milvus IVF_FLAT
Recall@5: 1.0000
MRR: 0.9551
IT classification F1: 0.9846
Milvus E2E latency: 147.5ms
threshold: 0.81
임베딩 모델은 1차 오프라인 벤치마크와 2차 Milvus 통합 벤치마크를 나눠 봤습니다.
1차 벤치마크는 numpy 기반 비교로 진행했습니다. 테스트셋은 IT 직무 검색 38개 쿼리, 시맨틱 캐싱 18개 쌍, 자연어 검색 16개 쿼리, IT 분류 136개 입력으로 구성했습니다.
| 모델 | Recall@1 | Recall@5 | MRR | P@5 | NL-R@5 | NL-MRR | F1 | 최적 Thr | Cache Gap | Lat-1 | Lat-50 | 모델 크기 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| BGE-m3-ko | 0.9231 | 1.0000 | 0.9551 | 0.2308 | 1.0000 | 0.9062 | 0.9846 | 0.81 | 0.2073 | 206.2ms | 1524.5ms | 4364.4MB |
| BGE-m3 | 0.9231 | 1.0000 | 0.9551 | 0.2308 | 1.0000 | 0.8750 | 0.9846 | 0.84 | 0.1690 | 194.0ms | 1367.5ms | 8705.9MB |
| KURE-v1 | 0.8846 | 1.0000 | 0.9359 | 0.2308 | 1.0000 | 0.8771 | 0.9846 | 0.87 | 0.1750 | 200.6ms | 1337.7ms | 4364.3MB |
| gte-base-korean | 0.8846 | 1.0000 | 0.9295 | 0.2385 | 0.9375 | 0.8792 | 0.9846 | 0.86 | 0.2850 | 76.0ms | 552.0ms | 2362.4MB |
| Qwen3-0.6b | 0.7692 | 1.0000 | 0.8590 | 0.2308 | 0.9375 | 0.7479 | 0.9846 | 0.84 | 0.1680 | 188.0ms | 1911.3ms | 2303.1MB |
| KoELECTRA | 0.2308 | 0.4615 | 0.3449 | 0.0923 | 0.1875 | 0.1888 | 0.3810 | 0.30 | 0.0496 | 56.5ms | 343.5ms | 1723.7MB |
이 단계에서 BGE-m3-ko와 BGE-m3는 Recall@1, Recall@5, MRR이 같았습니다. 하지만 BGE-m3-ko는 한국어 특화 모델이면서 모델 크기가 BGE-m3의 절반 수준이었고, 자연어 검색 MRR도 더 높았습니다. KURE-v1은 Recall@5와 F1은 같았지만 MRR이 0.9359로 낮아 상위 정렬 품질에서 밀렸습니다.
Milvus 인덱스도 함께 비교했습니다.
| 모델 | 인덱스 | Recall@5 | MRR | F1 | 단일 쿼리 | 배치 쿼리 | E2E latency | 결정 |
|---|---|---|---|---|---|---|---|---|
| BGE-m3-ko | IVF_FLAT | 1.0000 | 0.9551 | 0.98 | 1.7ms | 8.0ms | 147.5ms | 선택 |
| BGE-m3-ko | HNSW | 1.0000 | 0.9551 | 0.98 | 2.0ms | 11.8ms | 153.3ms | 확장 후보 |
| KURE-v1 | IVF_FLAT | 1.0000 | 0.9359 | 0.98 | 2.9ms | 11.4ms | 164.4ms | 제외 |
| KURE-v1 | HNSW | 1.0000 | 0.9359 | 0.98 | 2.5ms | 12.5ms | 171.5ms | 제외 |
Milvus 통합 후에도 numpy 기반 테스트와 Recall@5, MRR, F1이 동일하게 유지됐습니다. BGE-m3-ko + IVF_FLAT 조합은 단일 쿼리 1.7ms, 배치 쿼리 8.0ms, E2E 147.5ms로 가장 빠르게 나왔습니다.
| 구성 | 삽입 시간 | 인덱스 빌드 | 컬렉션 로드 | Recall@1 | Recall@5 | MRR | F1 | Cache Gap | NL Recall@5 |
|---|---|---|---|---|---|---|---|---|---|
| BGE-m3-ko + IVF_FLAT | 3.09s | 0.52s | 4.01s | 0.9231 | 1.0000 | 0.9551 | 0.9846 | 0.2073 | 1.0000 |
| BGE-m3-ko + HNSW | 3.09s | 0.52s | 4.03s | 0.9231 | 1.0000 | 0.9551 | 0.9846 | 0.2073 | 1.0000 |
| KURE-v1 + IVF_FLAT | 3.08s | 0.51s | 3.18s | 0.8846 | 1.0000 | 0.9359 | 0.9846 | 0.1750 | 1.0000 |
| KURE-v1 + HNSW | 3.08s | 0.53s | 4.01s | 0.8846 | 1.0000 | 0.9359 | 0.9846 | 0.1750 | 1.0000 |
BGE-m3-ko는 KURE-v1과 비교했을 때 Recall@5는 같았지만 MRR이 더 높았습니다. 검색 결과의 첫 번째 후보가 더 정확하다는 뜻이므로, 직무 필터링과 시맨틱 캐시에는 중요한 차이였습니다. Milvus 통합 후에도 성능 저하가 없었고, E2E latency도 서비스 흐름에 넣을 수 있는 수준이었습니다.
인덱스는 HNSW도 후보였지만, 이 프로젝트 규모에서는 IVF_FLAT이 더 단순하고 예측 가능했습니다. 데이터 규모가 더 커지면 HNSW를 다시 검토할 수 있지만, 팀 프로젝트 단계에서는 검색 품질과 운영 단순성의 균형이 더 중요했습니다.
직무 필터링 시뮬레이션에서도 BGE-m3-ko는 임계값 0.81 기준으로 IT 직무는 LLM 호출 대상으로 남기고, 비 IT 직무는 정상적으로 스킵했습니다. 예를 들어 백엔드 엔지니어 0.9611, 머신러닝 엔지니어 0.9306, 데이터 엔지니어 0.9219는 호출 대상으로 분류됐고, 경영기획 담당자 0.3910, 객실승무원 0.4438, 바이어 0.3676은 스킵됐습니다. 이 결과는 임베딩 모델이 단순 검색뿐 아니라 LLM 호출 비용을 줄이는 게이트 역할도 할 수 있음을 보여줬습니다.
최종 조합
최종적으로 기능별 모델은 다음처럼 결정했습니다.
- 명함 배경 생성: SDXL Base
- LLM 분석과 자기소개 생성: Qwen2.5 7B Instruct
- 임베딩 검색과 시맨틱 캐시: BGE-m3-ko
- Vector DB 인덱스: Milvus IVF_FLAT
이 조합은 가장 화려한 모델을 모은 결과가 아닙니다. 각 기능에서 실패하면 안 되는 지점을 기준으로 고른 결과입니다.
명함 생성에서는 텍스트 정확도를 지키기 위해 배경 생성과 텍스트 렌더링을 분리했습니다. 6각 차트에서는 LLM이 점수를 만들지 않게 했습니다. 직무 분석에서는 JSON 스키마와 검색 신뢰도를 함께 봤습니다. 임베딩은 모델 단독 성능보다 Milvus 통합 후의 검색 품질을 기준으로 삼았습니다.
트레이드오프
이 선택에도 아쉬운 점은 있습니다. SDXL Base는 안정적이지만 최신 이미지 모델보다 스타일 표현력이 제한될 수 있습니다. Qwen2.5 7B는 운영 균형이 좋지만, 더 큰 모델을 쓰면 일부 설명 품질은 올라갈 수 있습니다. IVF_FLAT은 단순하고 예측 가능하지만, 데이터가 커지면 검색 비용이 커질 수 있습니다.
하지만 팀 프로젝트의 목표는 최고 성능 모델을 전부 붙이는 것이 아니었습니다. 제한된 시간 안에 서비스 기능으로 연결할 수 있고, 실패했을 때 원인을 추적할 수 있는 구조가 더 중요했습니다.
그래서 모델 선택은 아키텍처 선택과 연결됩니다. 모델별 자원 특성이 다르기 때문에 Spring 서버 안에 모두 넣지 않고, FastAPI와 RunPod 모델 서빙 경로로 분리했습니다. 이 구조에서는 모델을 바꾸더라도 도메인 서버의 배포 리스크를 줄일 수 있습니다.
배운 점
이번 모델 선택에서 가장 크게 배운 점은 "좋은 모델"과 "서비스에 맞는 모델"이 다르다는 것입니다.
이미지 생성에서는 텍스트를 잘 그리는 모델보다, 텍스트를 렌더러에게 넘길 수 있는 배경 모델이 더 적합했습니다. LLM에서는 추론 능력보다 JSON 안정성과 역할 제한이 중요했습니다. 임베딩에서는 모델 카드의 성능보다 Milvus 통합 후의 실제 검색 결과가 더 중요했습니다.
앞으로 AI 기능을 설계할 때도 모델을 먼저 고르기보다, 서비스 실패 조건과 운영 제약을 먼저 정의해야 합니다. 그 다음에 모델 벤치마크를 붙이면 선택 근거가 훨씬 명확해집니다.
