Gemini 2.5 Flash thinking budget: 추론 토큰을 얼마나 쓸지 요청마다 다르게 정하는 법
프로덕션에서 LLM API를 운영하다 보면 어느 순간 반드시 마주치는 질문이 있습니다. "이 요청, 굳이 추론까지 써야 할까?" 솔직히 저도 처음엔 추론 모드를 켜두면 그냥 다 더 잘되는 줄 알았습니다. 그런데 청구서를 열어보고서야 생각이 바뀌었습니다. 번역 API 하나에 thinking 토큰이 붙어서 비용이 수배로 뛰어 있는 걸 보면, 뭔가 잘못되고 있다는 신호입니다.
Gemini 2.5 Flash가 흥미로운 이유는 추론을 ON/OFF로만 다루지 않는다는 점입니다. thinkingBudget이라는 파라미터 하나로 얼마나 깊이 생각할지를 토큰 단위로 조절할 수 있습니다. 덕분에 동일한 모델 엔드포인트를 단순 분류 작업과 복잡한 수학 추론에 동시에 쓰면서, budget 값만 바꿔 비용과 품질을 각기 다르게 튜닝하는 패턴이 가능해졌습니다.
이 글은 그 트레이드오프를 프로덕션 관점에서 뜯어봅니다. 어떤 설정이 언제 맞고, 어떤 함정이 있는지, 모니터링은 어떻게 해야 하는지, 그리고 무엇보다 태스크별 budget을 정할 때 무엇을 기준으로 삼아야 하는지를 다룹니다.
thinking budget이 뭔지부터 제대로 이해하기
숨겨진 추론의 작동 방식
Gemini 2.5 Flash는 최종 응답을 내보내기 전에 내부적으로 숨겨진 추론(hidden reasoning)을 수행할 수 있습니다. 사용자 눈에는 보이지 않지만, 이 과정에서 소비한 토큰은 고스란히 과금됩니다. thinkingBudget은 이 내부 추론에 쓸 수 있는 최대 토큰 수를 정하는 파라미터입니다.
값의 범위는 0부터 24576까지이고, 특수값 -1(동적 모드)도 있습니다. 아래 다이어그램은 요청이 들어왔을 때 budget 값에 따라 처리 경로가 어떻게 갈리는지를 보여줍니다.
가장 중요한 개념 하나: thinking budget은 최솟값이 아니라 상한값입니다. budget을 8192로 설정했다고 해서 모델이 반드시 8192 토큰을 쓰는 게 아닙니다. 단순한 프롬프트라면 훨씬 적게 쓸 수 있고, 실제 소비한 토큰만 과금됩니다. 반대로 budget 상한에 딱 걸리면 추론이 중간에 끊기고 그 상태로 응답이 나오니 주의가 필요합니다.
비용 구조 확인 방법
thinking 토큰의 정확한 단가는 시점과 모델 버전에 따라 달라지므로, Gemini API 공식 가격 페이지를 직접 확인하는 편이 안전합니다. 중요한 건 세 가지 단가가 별도로 존재한다는 점입니다: 입력 토큰, 일반 output 토큰, 그리고 thinking 토큰. 이 셋의 단가가 서로 다르기 때문에, 같은 요청량이라도 어떤 토큰이 얼마나 나갔느냐에 따라 청구액이 크게 갈립니다.
체감을 잡기 위한 계산은 직접 해보는 게 낫습니다. 예를 들어 하루 100만 요청이 나가는 서비스에서 요청당 평균 thinking 토큰이 4000개라면, 하루 40억 thinking 토큰이 됩니다. 여기에 공식 페이지의 thinking 단가를 곱해 보면, 이 항목 하나가 별도 예산 라인이 되어야 하는 규모인지 즉시 판단이 섭니다.
태스크별로 budget을 다르게 설정하기
코드 구조 (개념적 예시)
아래는 google-generativeai SDK를 사용한 개념적 예시입니다. SDK가 빠르게 진화 중이므로 실제로는 사용하는 버전의 문서에서 ThinkingConfig 노출 경로를 확인해야 합니다. 2026년 8월 기준으로는 최상위 genai.types 네임스페이스나 genai.protos 아래에 노출되는 경우가 많으며, v1beta 하위 gRPC 경로를 직접 임포트하는 방식은 SDK 업데이트에 따라 깨지기 쉽습니다.
import google.generativeai as genai
from google.generativeai import types as genai_types
model = genai.GenerativeModel("gemini-2.5-flash")
def call_with_budget(prompt: str, budget: int) -> str:
response = model.generate_content(
prompt,
generation_config=genai.GenerationConfig(
thinking_config=genai_types.ThinkingConfig(
thinking_budget=budget
)
)
)
return response.textSDK 버전에 따라 genai.Client().models.generate_content(...) 형태의 신규 인터페이스를 권장할 수도 있습니다. 프로젝트 시작 시점에 pip show google-generativeai로 설치 버전을 확인하고, 해당 버전의 공식 예제를 따르는 편이 안전합니다.
태스크 유형별 라우팅
프로덕션에서 자주 채택하는 패턴은, 동일한 모델 엔드포인트에 태스크 유형별로 다른 budget을 지정하는 것입니다.
이 라우팅 자체는 별도 분류 모델이 필요한 게 아니라, 요청의 출처(어떤 기능에서 호출됐는지)나 간단한 규칙으로 결정할 수 있습니다.
BUDGET_MAP = {
"translate": 0,
"classify": 0,
"moderate": 0,
"summarize": 512,
"qa_simple": 1024,
"code_gen": 4096,
"code_review": 8192,
"math_solve": 16384,
"realtime_chat": 1024,
}
def route_request(task_type: str, prompt: str) -> str:
budget = BUDGET_MAP.get(task_type, 2048)
return call_with_budget(prompt, budget)숫자 자체는 출발점일 뿐이고, 뒤에서 다룰 SLA 역산 방식으로 각자의 서비스에 맞게 조정해야 합니다.
태스크별 반응 차이의 직관
공개된 실험 사례들이 공통적으로 시사하는 방향은 이렇습니다. 번역·분류·모더레이션처럼 답이 짧고 결정론적인 태스크에서는 추론을 늘려도 정확도 향상이 잘 관측되지 않고, 지연과 비용만 커집니다. 반면 수학·다단계 추론·복잡한 코드 리뷰에서는 thinking 토큰이 최종 응답의 정답률에 눈에 띄는 영향을 줍니다. 정확한 수치와 배수는 프롬프트, 모델 버전, 측정 방식에 따라 달라지므로, 자기 서비스의 대표 프롬프트로 A/B를 돌려 재현하는 편이 신뢰할 만합니다.
수학 문제에서 추론을 통해 논리를 내부에서 정리하면, 최종 응답에서 장황한 설명 없이 핵심만 출력할 수 있게 되는 경우도 있습니다. 반대로 번역은 추론이 아무 도움이 안 됩니다. 생각을 많이 한다고 '고양이'가 'cat' 이상의 번역이 나오지는 않으니까요.
트레이드오프: 무엇을 얻고 무엇을 잃는가
| 설정 | 속도 | 비용 | 왜 이 budget이 이 태스크에 맞나 |
|---|---|---|---|
| budget=0 | 가장 빠름 | 가장 낮음 | 결정론적 매핑(번역·분류)에는 추론이 정답률에 기여하지 않음 |
| budget≈1024 | 빠름 | 낮음 | 문장 단위 요약·간단한 QA는 짧은 정리 단계만 있으면 충분 |
| budget≈4096~8192 | 보통 | 중간 | 코드 생성·구조화 추출은 제약 조건을 여러 개 조합해야 하므로 중간 추론이 오류를 줄임 |
| budget≈24576 | 느림 | 높음 | 다단계 수학·정합성 검증에는 긴 사고 사슬이 실제 정답률에 반영됨 |
| budget=-1 (동적) | 예측 불가 | 예측 불가 | 프로덕션 비권장, 탐색용으로만 유용 |
흔히 빠지는 함정들
동적 모드(-1)를 프로덕션에 그냥 쓰는 경우. 모델이 budget을 자율 결정하므로, 단순 작업에도 thinking을 과도하게 쓰는 overthinking 현상이 발생할 수 있습니다. 단순 문제에도 불필요하게 긴 추론을 하는 경향은 학술적으로도 논의되고 있습니다(arXiv 2507.04023). 비용 예측이 불가능해지므로, 개발·테스트 단계 외엔 고정값을 쓰는 게 안전합니다.
thinking 토큰을 모니터링 못 하는 경우. thinking 토큰은 응답 텍스트에 노출되지 않습니다. 그런데 과금은 됩니다. usageMetadata 필드 이름과 thinking 토큰이 어느 필드로 집계되는지는 SDK/API 버전에 따라 달라지고 있어, 최근 버전에서는 thoughts_token_count처럼 별도 필드로 분리해 노출하는 방향으로 정리되는 추세입니다. 어느 필드에 잡히는지 반드시 응답 객체를 실제로 dump해 확인한 뒤, 요청별로 budget 설정값·실제 thinking 토큰·일반 output 토큰을 각각 기록해두는 편이 좋습니다.
import logging
def call_with_logging(task_type: str, prompt: str) -> str:
budget = BUDGET_MAP.get(task_type, 2048)
response = model.generate_content(
prompt,
generation_config=genai.GenerationConfig(
thinking_config=genai_types.ThinkingConfig(
thinking_budget=budget
)
)
)
usage = response.usage_metadata
log_entry = {
"task_type": task_type,
"budget_set": budget,
"prompt_tokens": getattr(usage, "prompt_token_count", None),
"output_tokens": getattr(usage, "candidates_token_count", None),
"thinking_tokens": getattr(usage, "thoughts_token_count", None),
}
logging.info(log_entry)
return response.textgetattr로 감싼 이유는 SDK 버전에 따라 필드 유무가 다르기 때문입니다. 실서비스에 반영 전에 실제 응답 스키마를 한 번 로그로 찍어보고 필드명을 확정하는 것을 권장합니다.
budget 상한에 걸릴 때의 응답 품질. budget 상한에 도달하면 모델은 추론을 중단하고 그 시점 상태로 응답을 생성합니다. 불완전한 추론 결과가 최종 응답에 영향을 줄 수 있습니다. 매우 복잡한 태스크에서 budget을 너무 낮게 잡으면, 추론이 중간에 잘린 채 나온 응답이 오히려 아무 추론도 안 한 것보다 나쁠 수 있습니다.
즉, 복잡한 태스크에 대해서는 budget을 '조금 올린' 정도가 가장 위험한 구간입니다. 아예 안 쓰거나(0), 확실히 넉넉하게 주는 편이 안전합니다.
SLA에서 budget을 역산하는 사고 흐름
이 글이 실제로 남기고 싶은 조언은 이겁니다. 태스크 유형별로 숫자를 감으로 배치하지 말고, 응답 품질 SLA와 지연 SLA를 먼저 정의하고 거기서 budget을 역산하세요. 대략 이런 순서로 접근하면 감이 흔들리지 않습니다.
- 태스크별 성공 기준을 문장으로 먼저 정의합니다. 예: "코드 리뷰 태스크는 규칙 위반 감지율 90% 이상, p95 지연 3초 이하."
budget=0으로 대표 프롬프트 100~500건을 돌려 baseline 정답률과 지연을 잡습니다.- budget을 로그 스케일로 늘려가며(0 → 512 → 2048 → 8192 → 24576) 정답률·지연·요청당 비용을 측정합니다.
- 정답률 향상이 지연·비용 증가를 정당화하는 최소 budget을 그 태스크의 운영값으로 확정합니다.
- 트래픽 패턴이 바뀌거나 프롬프트가 개편되면 이 측정을 다시 돌립니다.
이 절차의 핵심은 budget 값을 '태스크 유형'이 아니라 'SLA 곡선의 knee point'에서 정한다는 점입니다. 같은 코드 생성이라도 사내 도구용이면 knee가 낮은 쪽, 고객 응대용이면 knee가 높은 쪽으로 자연스럽게 밀립니다.
정리하며
thinking budget은 요청별로 추론에 얼마를 지불할지 앱 계층에서 결정할 수 있게 해 주는, 지금까지의 LLM API에서는 드물게 통제권이 확실히 넘어온 파라미터입니다. 모든 요청에 동일한 추론 깊이를 적용하는 건 대체로 비용 낭비이거나 품질 포기 중 하나로 귀결됩니다.
출발점을 잡는다면 모든 요청에 budget=0으로 시작해서, SLA를 만족하지 못하는 태스크만 위 역산 절차로 budget을 올리는 방식이 현실적입니다. 이미 나간 비용은 돌려받을 수 없으니까요. 동적 모드(-1)는 '이 태스크에 얼마나 추론이 필요한가'를 탐색하는 개발 단계 도구로만 쓰고, 프로덕션에는 반드시 고정값으로 내려보내는 편이 청구서를 안정시킵니다.
멀티모델 라우팅과 budget 라우팅 중 뭐가 나은지는 상황마다 다릅니다. 다만 단일 모델 + budget 축 하나로 운영 매트릭스를 단순화할 수 있는지 먼저 검토해 보는 것은 충분히 가치 있는 시도입니다.
참고 자료
- Gemini API Pricing - Google AI for Developers
- Start building with Gemini 2.5 Flash - Google Developers Blog
- Thinking | Firebase AI Logic - Google 공식 문서
- The Overthinker's Guide to Reasoning Models (arXiv 2507.04023)
- Gemini 2.5 Flash API Pricing & Benchmarks | OpenRouter
- Gemini pricing in 2026 - CloudZero