Gemini 토큰을 로컬에서 계산하는 방법
요약
Gemini 모델의 토큰화 방식을 탐구하고, 로컬 환경에서 텍스트 및 멀티모달 데이터의 토큰 수를 계산하거나 추정하는 방법을 다룹니다. Google Gen AI Python SDK를 활용하여 오프라인 토큰 계산 및 API 메타데이터 활용법을 안내합니다.
핵심 포인트
- 로컬 토크나이저를 사용한 오프라인 토큰 수 추정 방법
- 이미지, 오디오, 비디오 등 멀티모달 입력의 토큰화 수학 이해
- Google Gen AI Python SDK의 LocalTokenizer 클래스 활용
- API 응답 메타데이터를 통한 정밀한 토큰 사용량 추적
✨ 개요 (Overview)
이 기사는 Gemini가 데이터를 토큰화(tokenizes)하는 방식을 탐구하고, 로컬에서 토큰을 계산하거나 추정하는 방법을 보여줍니다. 오프라인에서 텍스트 토큰 수를 추정하기 위해 로컬 토크나이저(local tokenizer)를 사용하는 방법, 멀티모달 입력(이미지, 오디오, 비디오, PDF)에 대한 토큰화 수학(tokenization math)을 이해하는 방법, 그리고 정확한 추적 및 과금을 위해 API 응답에서 정밀한 토큰 사용 메타데이터를 검색하는 방법을 배우게 됩니다.
ℹ️ 전체 소스 코드는 Apache 2.0 라이선스 하에 이 노트북(모든 설정 세부 정보 및 향후 업데이트 포함)에서 확인할 수 있습니다. 또한 Colab에서 노트북을 직접 열 수도 있습니다. 이 기사는 “Run all”을 클릭하여 생성된 모든 결과를 재현합니다.
⚙️ 설정 (Setup)
🐍 Google Gen AI Python SDK
Gemini API를 호출하기 위해 Google Gen AI Python SDK를 사용합니다. Gemini API는 count_tokens 메서드를 제공하며, SDK는 LocalTokenizer 클래스의 실험적인 구현을 제공합니다.
local-tokenizer 추가 기능이 포함된 최신 버전의 google-genai 패키지가 있는지 확인하세요:
pip install --quiet "google-genai[local-tokenizer]>=2.9.0"
🛠️ Google Cloud 프로젝트 (Google Cloud Project)
Agent Platform에서 Gemini API 사용을 시작하려면 기존 Google Cloud 프로젝트가 있어야 하며 Agent Platform API를 활성화해야 합니다.
프로젝트 및 개발 환경 설정에 대해 자세히 알아보세요.
import os
PROJECT_ID = ""
LOCATION = "global"
...
🤖 Gen AI SDK 클라이언트 (Gen AI SDK Client)
Gemini API와 상호 작용하기 위해 genai.Client를 초기화합니다. 기업용으로 준비된 Agent Platform 백엔드(이전의 Vertex AI)를 사용하므로, Google Cloud project 및 location과 함께 enterprise=True를 전달합니다:
from google import genai
def print_configuration(client: genai.Client) -> None:
service = "Agent Platform" if client.vertexai else "Google AI"
...
ℹ️ location "global"에서 project "lpdemo-…"를 사용하여 Agent Platform API 사용 중
🧠 Gemini 모델 (Gemini Model)
gemini-3.1-flash-lite를 사용합니다.
토큰 계산 및 콘텐츠 생성(content generation)을 위한 기본 모델로 사용합니다. 이 가볍고 빠른 모델은 높은 처리량(high-throughput)이 필요한 작업에 이상적입니다.
MODEL_ID = "gemini-3.1-flash-lite"
🧩 기초: 토큰(Tokens)과 토크나이저(Tokenizers)
토큰 (Tokens)
대규모 언어 모델 (LLMs)은 우리의 입력을 직접 처리하거나, 우리가 보는 최종 텍스트 또는 미디어를 직접 생성하지 않습니다. 대신, 토큰 (tokens)이라고 불리는 근본적인 단위를 사용하여 작동하며, 이를 입력값으로 받아들이고 출력값으로 생성합니다.
LLM 요청을 보낼 때 발생하는 과정은 다음과 같습니다:
- 우리의 입력값은 토큰으로 변환됩니다. 즉, 토큰화 (tokenized)됩니다.
- 모델은 전체 문맥(context)을 바탕으로 가장 가능성이 높은 다음 토큰들을 나타내는 출력 토큰을 생성합니다.
- 이 출력 토큰들은 우리가 사용할 수 있는 최종 콘텐츠로 다시 변환됩니다.
토큰을 하나의 정보 조각이라고 생각할 수 있으며, 이 토큰화 (tokenization) 과정은 정보 압축 코덱 (information compression codec) 역할을 합니다:
- 인코딩 (Encoding): 입력 (Input) → 입력 토큰 (Input tokens)
- 디코딩 (Decoding): 출력 토큰 (Output tokens) → 출력 (Output)
토큰화 (Tokenization)는 정보를 적절한 수준의 의미론적 세밀함 (semantic granularity)으로 압축하는 데 필수적이며, 이를 통해 모델의 어텐션 메커니즘 (attention mechanism)이 제공된 데이터에 집중하고 이해를 발전시킬 수 있게 합니다.
토크나이저 (Tokenizers)
Gemini는 네이티브 멀티모달 (multimodal) 모델로 텍스트, 이미지, 오디오, 비디오 및 PDF를 수용합니다. 이러한 미디어 유형은 세 가지 토크나이저 세트에 의해 처리될 수 있습니다:
| 입력 (Input) | 텍스트 토크나이저 (Text Tokenizer) | 이미지 토크나이저 (Image Tokenizer) | 오디오 토크나이저 (Audio Tokenizer) | 비고 (Comment) |
|---|---|---|---|---|
| 텍스트 (Text) | ✅ | LLM이 챗봇에 불과했을 때의 기존 토크나이저 유형입니다. | ||
| 이미지 (Image) | ✅ | 이미지 | ||
| 오디오 (Audio) | ✅ | ✅ | 텍스트 토큰은 타임스탬프 (timestamps)에 사용됩니다. | |
| 비디오 (Video) | ✅ | ✅ | [✅] | 기본적으로 초당 하나의 프레임이 해당 타임스탬프와 함께 샘플링됩니다. 비디오의 경우 오디오는 선택 사항입니다. |
| ✅ | ✅ | PDF는 비전 토크나이저 (vision tokenizers)에 의해 처리됩니다. 텍스트 토큰은 OCR 및 페이지 번호 데이터에 사용됩니다. |
보시다시피, 모달리티 (modality)에 따라 최대 세 개의 토크나이저가 관여할 수 있습니다.
💡 모든 기저 토큰 (underlying tokens)이 반드시 과금되는 것은 아니라는 점을 명심하세요. 모달리티 (modality)별로 실제로 과금되는 토큰의 예시는 아래의 usage_metadata 섹션을 참조하세요.
어휘집 (Vocabulary)
LLM이 입력받거나 생성할 수 있는 고유 토큰의 전체 집합이 어휘집 (vocabulary)을 구성합니다. LLM 학습이 완료되면, 어휘집은 고정되며 추론 (inference)에 사용됩니다.
어휘집은 본질적으로 텍스트 시퀀스를 토큰 ID (token IDs, 이는 의미 공간 (semantic space)에서의 벡터 표현에 대응함)로 매핑하는 룩업 테이블 (lookup table)입니다. 이는 토크나이저 (tokenizer)가 단순히 이 어휘집을 사용하여 토큰을 인코딩 (encode) 및 디코딩 (decode)하는 알고리즘 (즉, 데이터를 토큰 ID로 변환하거나 그 반대로 변환하는 것)임을 의미합니다.
예를 들어, Gemini 텍스트 토크나이저는 일반적인 단어들을 다음과 같이 처리합니다:
| 텍스트 (Text) | 토큰 (Tokens) | 토큰화 (Tokenization) | 토큰 ID (Token IDs) |
|---|---|---|---|
| 1 | 1 | ||
| 1 | 2 | ||
| 2 | 2 | ||
| 2 | 2 |
💡 보시다시피, 동일한 어근을 가진 단어들이 반드시 동일한 방식으로 분할되는 것은 아닙니다. 텍스트 토크나이저는 음절 (syllables), 접두사 (prefixes), 또는 접미사 (suffixes)에 대한 개념이 없습니다. 이들은 언어학자나 문법학자처럼 생각하지 않고, 통계학자처럼 생각하며 통계적으로 최적화된 조합을 찾습니다.
🌐 기본: API 토큰 계산 (API Token Counting)
Gemini API를 사용하면 count_tokens 요청을 보내어 모든 멀티모달 (multimodal) 입력에 대한 토큰을 계산할 수 있습니다. 이를 사용하려면 인증이 필요하지만, 이 방법은 무료이므로 유료 요청을 보내기 전에 프롬프트 (prompt)를 검토할 수 있습니다. 마찬가지로, compute_tokens 메서드를 사용하면 해당 토큰 및 토큰 ID 목록을 가져올 수 있습니다.
이전 표를 재현해 보겠습니다:
from collections.abc import Iterator
import IPython.display
from google.genai.types import (
...
| 텍스트 (Text) | 토큰 (Tokens) | 토큰화 (Tokenization) | 토큰 ID (Token IDs) |
|---|---|---|---|
| 1 | 1 | ||
| 1 | 2 | ||
| 2 | 2 | ||
| 2 | 2 |
🚀 왜 로컬에서 토큰을 계산해야 할까요?
토큰을 로컬에서 계산(또는 단순히 추정)하는 것이 유용한 몇 가지 사용 사례는 다음과 같습니다:
오프라인 및 속도 (Offline & Speed): 토큰을 완전히 오프라인 상태에서 계산할 수 있습니다. 또한, 온라인 상태일 때도 로컬에서 계산하면 프롬프트 크기를 확인하기 위해 Gemini API로 네트워크 왕복(round-trip)을 기다릴 필요가 없습니다.
할당량 (Quotas): count_tokens 메서드는 무료이지만, 로컬에서 계산하면 대역폭을 절약하고 특히 대량의 토큰을 계산할 때 API 속도 제한(rate limits)에 걸리는 것을 방지할 수 있습니다.
지연 시간 (Latency): 응답을 받기 시작하기 전에 텍스트 입력 처리에 시간이 얼마나 걸릴지 추정할 수 있습니다 (특정 모델의 경우, 첫 번째 토큰 생성 시간(time-to-first-token latency)은 입력 토큰 수에 대략적으로 비례합니다).
비용 제어 (Cost Control): 유료 요청을 보내기 전에 API 비용을 추정하고 예산을 세울 수 있습니다.
라우팅 (Routing): 입력값이 어떤 토큰 수 범위(bucket)에 속하는지 알면 속도, 비용 또는 컨텍스트 크기에 따라 요청을 서로 다른 모델로 라우팅할 수 있습니다.
개인정보 보호 (Privacy): 민감한 데이터를 네트워크를 통해 전송하지 않고도 토큰 수를 검사할 수 있습니다.
🔤 로컬 텍스트 토크나이저 사용하기 (Using the Local Text Tokenizer)
사용 중인 특정 Gemini 모델을 위한 로컬 토크나이저(tokenizer)를 생성합니다:
from google.genai.local_tokenizer import LocalTokenizer
tokenizer = LocalTokenizer(model_name=MODEL_ID)
💡 참고 사항
- 토크나이저를 생성하는 데 몇 초가 소요되며, 이 과정에서 설정과 어휘 사전(vocabulary)이 메모리에 로드됩니다.
- 첫 호출 시, 토크나이저 데이터가 다운로드되어 로컬 캐시(cache)에 저장됩니다. 이 단계에는 인터넷 연결과 약 30MB의 저장 공간이 필요합니다.
- 완전히 오프라인 솔루션을 구축하려면 SDK 소스 코드를 확인하고 토크나이저 자산(assets)을 영구적으로 저장할 수 있습니다 (예: 영구 캐시 디렉토리를 구성하거나 컨테이너 이미지 빌드).
내부 토크나이저 이름을 확인하면 Gemma 오픈 웨이트 (open-weight) 모델이 Gemini 3 제품군과 동일한 텍스트 토크나이저를 공유한다는 것을 알 수 있습니다:
print(f'Text tokenizer name for "{MODEL_ID}": "{tokenizer._tokenizer_name}"')
Text tokenizer name for "gemini-3.1-flash-lite": "gemma4"
count_tokens()를 호출합니다.
작은 텍스트 입력에 대한 메서드 실행:
contents = "Hello World!"
result = tokenizer.count_tokens(contents)
print(f"{result.total_tokens=}")
result.total_tokens=3
이제, 로컬 토크나이저 (tokenizer)를 사용하여 이전의 API 토큰화 (tokenization) 테스트를 재현해 보겠습니다:
def display_token_info_from_local_tokenizer(tokenizer: LocalTokenizer, texts: list[str]) -> None:
def yield_data() -> Iterator[RowData]:
for text in texts:
...
| 텍스트 (Text) | 토큰 (Tokens) | 토큰화 (Tokenization) | 토큰 ID (Token IDs) |
|---|---|---|---|
| 1 | |||
| 1 | |||
| 2 | |||
| 2 | |||
| 2 | |||
| 2 |
💡 예상대로, 이번에는 100% 로컬 실행을 통해 정확히 동일한 결과를 얻었습니다.
마지막으로, Hamlet과 같이 더 긴 텍스트를 다운로드해 보겠습니다:
import requests
def get_text_from_url(content_url: str, force_encoding: str = "") -> str:
response = requests.get(content_url, timeout=10)
...
HAMLET
DRAMATIS PERSONAE
CLAUDIUS king of Denmark. (KING CLAUDIUS:)
...
Hamlet을 인코딩(encode)하는 데 몇 개의 토큰이 필요할까요?
result = tokenizer.count_tokens(contents)
print(f"{result.total_tokens=:,}")
result.total_tokens=54,660
💡 Hamlet은 로컬에서 순식간에 5만 개 이상의 토큰으로 분해됩니다. 만약 War and Peace를 토큰화한다면, 85만 개 이상의 토큰을 얻게 될 것입니다.
🕵️♂️ "숨겨진" 토큰 고려하기
Gemini에 요청을 보낼 때, 총 입력 토큰 (input token) 수는 항상 입력 데이터의 합계와 일치하지는 않습니다.
단순함을 유지하기 위해, 우리는 기본 파라미터 (parameter)로 텍스트 토큰 수를 테스트했습니다. count_tokens와 compute_tokens 메서드 모두 config 파라미터를 가지고 있습니다. 요청 구성 (request configuration)에 따라 입력 및 출력에 추가적인 토큰이 포함될 수 있습니다.
이러한 숨겨진 추가 사항들을 주의 깊게 살펴보세요:
System Instructions (시스템 지침): 설정한 모든 시스템 프롬프트는 총 토큰 수에 추가됩니다.
Thinking (사고 과정): 사고 과정(thinking) 기능이 활성화된 경우, 내부적인 사고 체인(chain of thought)이 추가적인 사고 토큰(thinking tokens)을 생성할 수 있습니다.
Tools and Functions (도구 및 함수): Python 실행이나 사용자 정의 함수와 같은 도구 목록을 제공하는 경우, 해당 도구의 선언(declarations), 호출(calls), 그리고 응답(responses)이 프롬프트 페이로드(payload)의 일부가 됩니다.
Response Schema (응답 스키마): 구조화된 출력(예: JSON)을 강제하려면 모델이 제공된 스키마 정의를 처리해야 하며, 이는 입력 토큰을 소비합니다.
Chat History (대화 기록): 멀티턴(multi-turn) 대화에서는 매 새로운 메시지가 전달될 때마다 전체 대화 기록이 모델로 다시 전송됩니다. 즉, 대화가 진행될수록 입력 토큰 수가 증가함을 의미합니다.
🧮 Multimodal Token Math (멀티모달 토큰 계산)
멀티모달 입력(이미지, 오디오, 비디오 및 문서)은 텍스트와 같은 방식으로 토큰화되지 않습니다. 이들은 일반적으로 모델(및 해당 모델의 기반 토크나이저), 미디어 유형, 그리고 요청 구성(request configuration)에 따라 특정 계산 규칙을 따릅니다.
멀티모달 입력의 경우, 다양한 미디어 유형에 대해 토큰 수가 어떻게 계산되는지에 대한 자세한 내용은 문서를 참조하세요:
단일 모달리티(modality)에 대해서도 일반적으로 여러 토큰화 옵션이 존재합니다. count_tokens 메서드와 계산 규칙을 사용하여 사용자 페이로드의 토큰 수를 추정할 수 있습니다. 더 명확한 파악을 위해, 실제 요청을 살펴보고 모달리티별로 토큰 수가 어떻게 나뉘는지 확인해 보겠습니다...
🎯 Tracking Actual Token Usage (실제 토큰 사용량 추적)
토큰 수를 추정하는 것도 매우 유용하지만, 실제 사용량을 정확한 토큰 단위까지 추적해야 할 때는 항상 API 응답에서 반환되는 usage_metadata를 신뢰해야 합니다. 이는 과금(billing)을 위한 단일 진실 공급원(single source of truth)입니다.
usage_metadata를 통해 모달리티별 토큰 수를 얻는 방법의 핵심은 다음과 같습니다:
class GenerateContentResponse:
# …
usage_metadata: Optional[GenerateContentResponseUsageMetadata]
...
🐍 몇 가지 헬퍼(helpers)를 정의해 보겠습니다:
from google.genai.types import (
FileData,
GenerateContentResponse,
...
몇 가지 예시를 확인해 보겠습니다...
🖼️ 이미지 토큰화 (Image Tokenization)
이미지 토큰 수(Image token counts)는 이미지 자체와 설정된 미디어 해상도(media resolution)에 따라 달라집니다:
class PartMediaResolutionLevel(StrEnum):
MEDIA_RESOLUTION_UNSPECIFIED = "MEDIA_RESOLUTION_UNSPECIFIED"
MEDIA_RESOLUTION_LOW = "MEDIA_RESOLUTION_LOW"
...
특정 미디어 해상도 레벨(media resolution level)에 대해, Gemini 3 토크나이저(tokenizers)는 이미지당 다음과 같은 최대 토큰 예산(maximum token budgets)을 사용합니다:
| |
|---|---|
| | 280 |
| | 560 |
| | 1,120 |
| | 2,240 |
🐍 이 고양이 이미지가 기본적으로 어떻게 토큰화되는지 확인해 보세요:
def display_tokens_for_image(
image_uri: str,
media_resolution_level: PartMediaResolutionLevel | None = None,
...
🧪 media_resolution_level=None
| TEXT | IMAGE | VIDEO | AUDIO | DOCUMENT | Total |
|---|---|---|---|---|---|
| 0 | 1,080 | 0 | 0 | 0 | 1,080 |
💡 이 이미지는 최대치인 1,120개 대신 단 1,080개의 토큰으로 토큰화되어 40개의 토큰을 절약했습니다! 상한선(upper limit)을 기본값으로 사용하는 대신 비용을 낮출 수 있도록 도와주는 세심한 설계입니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Hacker Noon AI의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기