본문으로 건너뛰기
Community실험 리포트

LLM API 월 38만원을 9만원대로 줄인 순서: 측정 먼저, 그다음 라우팅·max_tokens·캐싱

임도윤

@rag_master

LLM API 비용이 매번 같은 데서 새서, 줄였던 항목을 효과 큰 순서로 정리해 둔다. 월 38만원 나오던 걸 9만원대까지 내렸고, 위에서부터 손댄 순서가 효과 순서이기도 하다. 다만 가장 큰 단일 항목은 모델이 아니라 프론트 코드였다.

1. 측정부터 — 엔드포인트별 토큰 로깅

결론부터 말하면 이게 1번이어야 했다. 나는 라우팅·캐싱부터 손댔다가 시간을 날렸다. 어디서 새는지 안 보고 추측으로 최적화하면 엉뚱한 데를 깎게 된다.

요청마다 endpoint / model / input_tokens / output_tokens를 로깅하고 엔드포인트별로 집계해서 상위부터 봤다. 그랬더니 검색 자동완성 엔드포인트 하나가 전체 비용의 40%를 먹고 있었다. 원인은 LLM이 아니라 디바운스 누락이었다. 타자 한 글자마다 호출이 나가고 있었던 거다.

const search = debounce((q) => callLLM(q), 300)

한 줄로 끝났다. 비용 항목이 모델 단가처럼 보여도 실제론 프론트 코드인 경우가 흔하다. 측정 없이 모델부터 의심했으면 끝까지 못 찾았다.

2. 모델 라우팅 — 단순 작업을 큰 모델에 안 보내기

전 요청을 최상위 모델에 때려박는 게 디폴트 함정이다. 분류·추출 같은 단순 작업이 트래픽의 절반 이상인 경우가 많은데, 그걸 다 비싼 모델로 돌리고 있었다.

model = "claude-haiku" if is_simple(task) else "claude-sonnet"

우리 케이스는 단순 작업이 약 70%였고, 이 라우터 한 겹으로 거의 절반이 깎였다. is_simple은 룰 기반으로 시작해도 충분하다. 라우팅 판단 자체를 LLM으로 돌리면 그게 또 비용이라 본말전도다.

3. max_tokens를 작업별로 빡세게

출력 토큰 상한을 안 박아두면 모델이 길게 뱉고, 그게 다 돈이다. 응답 평균이 200토큰인데 max_tokens=4096으로 열어두고 있었다. 작업별로 상한을 박고, 프롬프트에 "3문장 이내" 같은 길이 제약을 병행했다. 길이 제약을 프롬프트로만 주면 안 지킬 때가 있어서 max_tokens로 하드캡을 같이 거는 게 안전하다.

4. prompt caching — 반복되는 prefix

system 프롬프트 + few-shot이 2천 토큰쯤인데 매 요청 풀로 보내면 인풋이 누수된다. 안정적인 prefix(system, few-shot)에 cache를 마킹하면 hit 시 그 구간 인풋 단가가 크게 떨어진다. 적용은 코드 몇 줄이지만, TTL이 짧아서(5분 안팎) 트래픽이 뜸하면 효과가 미미하고, prefix가 자주 바뀌면 hit이 안 된다. 그래서 효과는 케이스를 탄다.

정리

순서가 핵심이다. 측정(4번 아니라 1번)부터 하고 진짜 범인을 찾은 다음 라우팅·max_tokens·캐싱을 얹어야 한다. 측정 없이 1번 모델 라우팅부터 손대면, 정작 비용의 40%를 먹던 디바운스 누락 같은 건 끝까지 안 보인다.

이 글에 대한 Q&A

이 글에 질문하기

이 글에서 궁금한 점이 있다면 먼저 질문을 남겨보세요.

댓글

첫 댓글을 남겨보세요.