AI Project Portfolio

강민성

LLM을 직접 올려 서비스에 붙이고, 회사 업무를 자동화해 봤습니다

Cukee — LLM 서빙·RAG 기반 AI 큐레이션 플랫폼 Doc2Edu Sync — LLM 기반 업무 자동화 PoC (카카오 엔터프라이즈)

Project 01

Cukee

AI 영화 큐레이션 플랫폼

사용자가 챗봇에게 말을 걸면 AI 큐레이터가 영화를 추천하고 3D 전시회로 만들어 주는 팀 프로젝트.
챗봇과 LLM 연결, RAG 파이프라인, GPU 운영, OpenAI 호환 API 구축을 담당했습니다.

기간

2025.11 ~ 2026.02

담당 영역

AI 서빙, RAG, GPU 운영, Public API

주요 기술

Qwen-14B, BGE-M3, pgvector, FastAPI, PyTorch, Docker, Prometheus, Grafana

T4 16GB 한 장에서 LLM 서빙

제약: 모델과 임베딩, 답변 생성 여유까지 16GB 안에

상황

T4의 VRAM은 16GB입니다. Qwen-14B는 fp16이면 파라미터 14B × 2바이트로 가중치만 약 28GB라 그대로는 올라가지 않습니다. 같은 카드에 BGE-M3 임베딩 모델도 함께 띄워야 했습니다.

판단

bitsandbytes 4-bit 양자화로 올렸습니다. 가중치가 fp16의 4분의 1 수준으로 줄어, 임베딩 모델과 답변 생성 중 늘어나는 메모리를 둘 여유가 생깁니다.

결과
  • VRAM: 로드 직후 약 8GB, 답변을 생성하는 동안에는 그보다 올라갑니다. 두 값이 달라 시점을 나눠 봤습니다
  • 임베딩: BGE-M3를 같은 카드에 함께 올린 상태로 16GB 안에서 서비스했습니다
  • 생성 길이: 처음 8192로 뒀는데 T4 속도로는 답변 하나 받기까지 너무 오래 걸려 2048 토큰으로 낮췄습니다

돌아가는 환경도 같이 맞춰야 했습니다

베이스 이미지

BGE-M3가 PyTorch 2.6.0을 요구했습니다. 베이스 이미지를 pytorch/pytorch:2.6.0-cuda12.4-cudnn9-runtime으로 바꿨습니다. T4가 CUDA 12.4를 지원하는지 먼저 확인했습니다.

의존성 대응

PyTorch를 올리자 bitsandbytes 빌드가 깨졌습니다. C 컴파일러가 없어서였습니다. build-essential과 triton을 같이 설치해 해결했습니다.

최종 구성

모델

Qwen-14B를 4-bit로 양자화해 올리고 BGE-M3 임베딩을 함께 띄웠습니다. 둘을 합쳐 T4 16GB 안에서 돌아갑니다.

이미지

PyTorch 2.6.0 + CUDA 12.4 런타임 이미지를 베이스로 쓰고, 양자화 라이브러리가 빌드되도록 build-essential과 triton을 추가했습니다.

메모리가 모자란 건 양자화로, 답이 늦게 나오는 건 생성 길이로 풀었습니다. GPU를 늘리지 않고 T4 한 장으로 서비스했습니다.

RAG 파이프라인과 프롬프팅

RAG 검색 파이프라인

임베딩

네이버 관람평을 직접 크롤링해 영화 17,026편에서 133만 건을 모았습니다. 임베딩 모델은 공개된 평가에서 가장 좋은 점수를 받은 BGE-M3를 골라, 이 리뷰들을 벡터로 바꿔 pgvector에 저장했습니다.

검색

적재(embedding_loader)와 검색(retrieval_service)을 나눠 구현했습니다. 요청이 오면 관련 영화를 찾아 생성 컨텍스트로 넘깁니다.

생성

FastAPI로 만든 AI 서버에 전시회 생성(/generate)과 영화 상세(/movie-detail) 엔드포인트를 붙였습니다.

판단: 왜 벡터 DB를 따로 두지 않았나

상황

서비스 데이터는 이미 PostgreSQL에 있었습니다. 자원도 빠듯했습니다. 애플리케이션 서버 저장 공간이 부족해 DB를 GPU 서버 쪽에 둬야 할 정도였습니다.

판단

전용 벡터 DB를 넣으면 검색은 강해집니다. 대신 배포하고 관리할 것이 하나 늘어납니다. 팀 프로젝트 규모에서는 그 이점보다 부담이 크다고 봤습니다. 이미 돌고 있는 PostgreSQL에 pgvector를 얹는 쪽을 골랐습니다.

결과

관리할 DB는 그대로 하나입니다. 대신 적재와 검색은 모듈로 갈라 뒀습니다. 나중에 데이터가 커져 전용 엔진이 필요해지면 검색 쪽만 갈아끼우면 됩니다.

프롬프팅과 페르소나

왜 학습이 아니라 프롬프팅인가

처음에는 큐레이터 말투를 LoRA로 학습시키려고 Llama 7B에 붙여 봤습니다. 어느 쪽이 나은지는 감으로 정하지 않았습니다. 같은 질문에 대한 두 응답을 어느 쪽이 무엇인지 가린 채 팀원들에게 보여주고, 말투가 끝까지 일관된 쪽에 투표하게 했습니다. 프롬프팅 쪽이 표를 더 받아 그쪽으로 정했습니다.

테마별 프롬프트와 캐싱

테마마다 시스템 프롬프트를 갈아 끼워 모델 하나로 열한 가지 말투를 냈습니다. 페르소나 요약은 매번 새로 만들면 같은 비용이 반복돼 캐싱해 두고 다시 썼습니다.

줄거리만 넣었을 때와 같은 질문으로 검색 결과를 나란히 비교했고, 리뷰를 넣은 쪽에서 팀원들이 "맞다"고 반응하는 경우가 더 잦았습니다. 정량 지표까지는 만들지 못했습니다.

GPU 모니터링

AI 서비스는 자원 소모가 널뜁니다. GPU 상태를 실시간으로 볼 수 있도록 모니터링 스택을 docker-compose에 같이 넣었습니다.

DCGM Exporter

NVIDIA GPU 메트릭 수집. VRAM 사용량, GPU Utilization, 온도, 전력 소비를 Prometheus 포맷으로 노출합니다.

Node Exporter

시스템 메트릭 수집. CPU와 RAM, 디스크 I/O도 같이 봐야 GPU 말고 다른 데서 막히는지 알 수 있습니다.

Prometheus

지표를 모으는 곳. 무한정 쌓이면 디스크가 차서 5GB·7일로 보존 기간을 잘라 뒀습니다.

Grafana

모은 지표를 한 화면에서 봅니다. GPU 사용량, 시스템 리소스, 모델 서빙 상태를 함께 띄웠습니다. /monitor/ 경로로 서빙합니다.

Cukee Grafana 대시보드

대시보드로 확인한 것

로드 직후

4-bit로 올린 Qwen-14B는 VRAM 약 8GB

답변 생성 중

추론이 돌면 로드 직후보다 사용량이 올라감

임베딩 동시 적재

BGE-M3를 같은 카드에 올린 상태로 16GB 안에서 운영

VRAM은 모델을 올린 직후보다 답변을 만드는 동안 더 올라갑니다. 로드 직후 값만 보고 여유가 있다고 판단하지 않도록 두 시점을 따로 봤습니다.

OpenAI 호환 Public API

모델을 외부에서 쓰게 하려면 GPU 서버를 열어야 합니다. 그냥 열면 누가 얼마나 쓰는지 알 수 없고, 서버가 바로 공격 대상이 됩니다. GPU 서버는 내부망에 두고 백엔드만 통로로 뒀습니다. 인증과 사용량 집계, 과금은 전부 그 지점에서 합니다. 외부에는 OpenAI 포맷과 호환되는 API로 냈습니다.

전체 요청 흐름

1
인증

Bearer 토큰 또는 X-API-Key 헤더로 API Key 검증. 만료·폐기 상태 확인

2
요청 파싱

OpenAI messages 배열에서 사용자 프롬프트 추출. ticketId로 페르소나 테마 매핑

3
GPU 서버 프록시

백엔드가 내부망(10.0.x.x:5000)의 AI 서버로 요청을 중계. 120초 타임아웃 + 에러 핸들링

4
응답 변환 + 로깅

AI 서버 응답을 OpenAI chat.completion 포맷으로 바꿔 반환. 토큰 사용량·비용·레이턴시를 DB에 기록

판단

왜 OpenAI 포맷에 맞췄나

우리 규격을 새로 만들면 쓰는 쪽이 그걸 배워야 합니다. 응답을 OpenAI chat completion 포맷에 맞추면 기존 SDK에서 주소만 바꿔 붙일 수 있습니다.

왜 키마다 상한을 뒀나

한 키가 몰아 쓰면 GPU가 막혀 다른 요청까지 느려집니다. 콘솔에서 키를 발급·폐기하고 RPM·TPM·RPD 상한을 걸었습니다.

왜 요청마다 기록했나

토큰과 비용, 레이턴시를 요청 단위로 남기고 일별로 모았습니다. 과금 근거이면서, 느려졌을 때 어디부터 볼지 정하는 자료가 됩니다.

다른 팀은 쓰던 OpenAI SDK에서 주소만 바꾸면 이 모델을 부를 수 있습니다. 키 발급과 사용량 확인은 관리 콘솔에서 합니다.

Project 02

Doc2Edu Sync

카카오 엔터프라이즈 인턴 때 만든 LLM 업무 자동화 PoC

기술문서가 업데이트되면 교육 교안에서 수정이 필요한 슬라이드를 자동으로 찾아
LLM이 수정 방향까지 제시하는 파이프라인. 기획부터 설계·구현·시연까지 전 과정을 주도했습니다.

기간

2026.04 ~ 2026.05 (카카오 엔터프라이즈 클라우드솔루션팀)

담당 영역

기획, 아키텍처 설계, 구현, 시연 (전 과정)

주요 기술

OpenAI API, GitHub Actions, PostgreSQL, Python, Marp, SSH Tunnel

왜 자동화가 필요했나

그때까지는 기술문서가 바뀌면 사람이 직접 확인하고 교안에 반영했습니다. 무엇을 자동화할지 정하기 전에 이 방식의 문제부터 적어 봤습니다.

Before: 담당자가 릴리즈를 매번 따라가야 했음

매번 직접 확인

기술문서 릴리즈 주기를 교안 담당자가 매번 직접 확인하고 따라가야 합니다.

빠뜨리거나 잘못 반영

변경 누락, 오반영, 용어 불일치 같은 실수가 생기기 쉽습니다.

반영이 늦어짐

릴리즈 시점과 교안 반영 사이의 시간이 길어질 수 있습니다.

사람마다 다른 기준

어디를 어떻게 고칠지가 담당자 개인의 판단에 따라 달라집니다.

After: 시스템이 후보를 좁히고 사람은 검토만

기술문서 변경 감지

매일 자동 수집

→

관련 교안 범위 탐색

태그 매칭으로 후보 축소

→

LLM 수정 판단

필요 여부와 수정 방향 도출

→

사람이 검토

리포트 확인 후 최종 반영

기술문서와 교안이 어긋나는 일, 고칠 곳을 찾느라 드는 시간을 줄이려던 겁니다. 하루치를 한 번에 몰아 넘기면 API 호출과 토큰도 덜 씁니다. 나중에 Issue나 PR 생성까지 넓히려면 이 구조가 먼저 있어야 했습니다.

수집부터 리포트까지

사람이 매번 하던 기술문서-교안 현행화를 네 단계 파이프라인으로 바꿨습니다.

파이프라인 구조

1
변경 수집

하루 1회 자동 배치로 기술문서 commit을 모읍니다. 최근 24시간을 그냥 조회하지 않고 마지막 처리 지점 이후를 읽습니다. 배치가 하루 실패해도 다음 실행에서 그 사이 변경까지 이어서 가져오기 위해서입니다.

2
태그 후보 추출

commit 메시지만 봐서는 무엇이 바뀐 건지 알 수 없었습니다. 그래서 변경 파일 경로, frontmatter, diff 본문 세 곳에서 태그 후보를 뽑는 방식으로 바꿨습니다.

3
DB 매칭

교안 슬라이드에도 같은 기준으로 태그를 붙여 두고, 겹치는 것만 후보로 좁힙니다. 여기서 좁히지 못하면 다음 단계 비용이 그대로 늘어납니다.

4
LLM 판단 + 리포트

좁혀진 후보만 LLM에 넘겨 고칠 필요가 있는지 묻습니다. 답은 '기존/변경' 형식으로만 받습니다. 결과는 사람이 읽을 Markdown/PDF 리포트로 냅니다.

운영하면서 필요했던 것

실패해도 이어서

state 파일로 이미 가져온 건 다시 가져오지 않음. Actions가 실패해도 다음 실행에서 빠진 것부터 이어서 수집

인프라 접근

기술문서가 있는 GitHub Enterprise는 사내망 안에 있어 GitHub 클라우드 러너로는 닿지 않습니다. 사내망에 연결된 PC를 self-hosted runner로 쓰고, 태그를 저장한 카카오클라우드 Managed PostgreSQL에는 SSH 터널로 보안 연결을 거쳐 접속했습니다.

운영 도구

개발을 모르는 담당자도 쓸 수 있게 Actions 실행·상태 조회·리포트 확인을 GUI 도구로 만듦

LLM에 무엇을 넣고, 어디까지 믿을지

판단 1: 토큰 비용

상황

기술문서와 교안 전체를 LLM에 그대로 넣으면 검토 범위가 넓어지고 토큰 비용이 통제되지 않습니다.

판단

태그로 수정 후보 슬라이드만 골라 LLM에 넘기도록 했습니다. 입력이 줄면 검토 범위도 토큰 비용도 같이 줄어듭니다.

결과

매일 배치가 돌아도 LLM 입력은 그날 바뀐 범위만큼만 늘어납니다.

판단 2: 답을 어디까지 믿을지

상황

LLM이 후보 범위 밖의 슬라이드까지 판단하거나, 모호한 수정 의견을 내면 결과를 신뢰할 수 없습니다.

판단

후보 밖 슬라이드는 아예 판단하지 못하게 막고, '기존/변경' 형식으로 나온 수정안만 결과로 받았습니다.

결과

리포트에는 무엇을 무엇으로 바꿀지가 적힌 항목만 남습니다.

이 두 가지를 정해 둔 상태로 Phase 1~3을 두 달 안에 마치고 팀 앞에서 시연했습니다.

어디까지 자동화할 것인가

LLM이 판단한 대로 교안을 바로 고치게 만들 수도 있었습니다. 그렇게 하지 않았습니다. 사람이 마지막에 확인하는 지점을 일부러 남겨 뒀습니다.

자동화 범위에 대한 판단

현재 (PoC 완료 범위)

LLM은 고칠 필요가 있는지와 어떻게 고칠지를 제안합니다. 결과는 사람이 보기 쉬운 Markdown/PDF 리포트로 나옵니다. 교안에는 손대지 않습니다. LLM이 틀려도 잘못된 교안이 나가지 않습니다.

다음 단계

사람이 승인한 항목만 교안에 자동 반영되게 넓힙니다. 범위는 넓히되 결정권은 사람이 그대로 갖습니다.

그 이후

수정할 항목을 GitHub Issue로 만들거나 교안 수정 PR을 자동으로 올려, 평소처럼 리뷰하고 머지하는 흐름에 태웁니다.

검증과 남은 과제

재현 가능한 검증 모드

기간을 직접 지정해 과거 변경분을 다시 돌려보는 모드를 만들었습니다. 이 모드는 상태 파일을 건드리지 않습니다. 운영 데이터를 그대로 두고 결과만 확인할 수 있습니다.

태그 정밀도

서비스 태그와 기능 태그가 함께 맞을 때만 후보로 삼는 규칙을 계속 보강해야 합니다. 후보가 넓으면 비용이 늘고, 좁으면 놓칩니다.

LLM 평가 기준

'기존: … / 변경: …' 형식의 구체적 수정안만 통과시키는 기준을 더 정교화하는 것이 다음 과제입니다.

지금은 리포트까지만 자동이고, 교안 수정은 사람이 합니다. 자동으로 넘길 범위는 사람이 승인한 항목부터 조금씩 넓힐 계획이었습니다.

Summary

두 프로젝트 요약

T4 한 장에서 LLM 서빙

Qwen-14B를 4-bit로 양자화해 임베딩 모델과 함께 16GB에 올리고, 응답 속도를 보고 생성 길이를 2048로 낮췄습니다. 말투는 블라인드 비교를 거쳐 학습 대신 프롬프팅으로 구현했습니다.

데이터부터 만든 RAG

네이버 관람평 133만 건을 직접 모아 BGE-M3로 임베딩하고, pgvector로 찾은 영화를 생성 컨텍스트로 넘기는 파이프라인을 구현했습니다.

사내 업무 자동화

카카오 엔터프라이즈에서 사람이 매번 하던 교안 현행화를 LLM 파이프라인으로 바꾸는 PoC를 기획부터 시연까지 맡았습니다. 교안을 고치는 마지막 단계는 사람에게 남겼습니다.

밖에서 쓰는 API

인증과 과금, 키별 사용량 상한을 붙인 OpenAI 호환 API와 관리 콘솔을 만들었습니다. 다른 팀은 SDK 주소만 바꿔 쓰면 됩니다.