
Ollama로 로컬에서 임베딩 모델 실행하기: 콜드 스타트와 웜 GPU 지연 시간 비교
Ollama를 사용하면 임베딩 모델을 로컬에서 실행하여 인덱싱하는 모든 청크(chunk)마다 OpenAI에 백만 토큰당 $0.02를 지불하는 일을 멈출 수 있습니다. 대신 감수해야 할 대가는 GPU 소유, 콜드 스타트(cold start) 문제, 그리고 운영(ops) 부담입니다. Ollama는 API 키 없이 포트 11434에서 이러한 모델을 서비스합니다. 다음은 ollama pull부터 쿼리에 응답하는 웜(warm) 상태의 벡터 검색에 이르기까지의 전체 워크플로우입니다.
핵심 요약
- Ollama는 API 키 없이 토큰당 $0의 비용으로
http://localhost:11434의POST /api/embed를 통해 로컬에서 임베딩을 서비스합니다. - 현재 권장되는 엔드포인트는
/api/embed(배열 입력 지원)이며,/api/embeddings는 레거시로서 주로 404 오류의 원인입니다. - 인기 있는 로컬 모델:
nomic-embed-text(768차원),mxbai-embed-large(1024),bge-m3(1024),embeddinggemma(768). - 벡터 DB 컬럼의 차원과 임베딩 차원을 일치시키고,
keep_alive설정으로 모델을 상주시켜 콜드 스타트 지연 시간을 피하세요.
Ollama로 로컬에서 임베딩을 실행하려면 무엇이 필요한가?
로컬에서 임베딩을 실행하는 데 필요한 것은 세 가지 요소뿐입니다: 임베딩 모델, 포트 11434에서 실행되는 Ollama 서버, 그리고 출력을 저장할 벡터 스토어입니다. Ollama가 모델을 다운로드하고 서비스하며, 여러분의 코드는 텍스트를 /api/embed로 전송합니다. 생성된 벡터는 pgvector, Qdrant 또는 Chroma 같은 데이터베이스에 저장됩니다. 클라우드 왕복 통신도 없고, 토큰당 청구서도 없습니다.
다음 두 명령어면 1분 이내에 작동하는 임베딩 환경을 구축할 수 있습니다:
ollama pull nomic-embed-text
curl http://localhost:11434/api/embed -d '{
"model": "nomic-embed-text",
"input": "The quick brown fox"
}'이것이 빠른 시작의 전부입니다. 이 튜토리얼의 나머지 부분에서는 모델 선택, 스토어 구성, 그리고 모두가 겪는 두 가지 함정(엔드포인트 혼동과 콜드 스타트 페널티)을 다룹니다.
1단계: Ollama 설치 및 임베딩 모델 다운로드
Ollama를 설치하고 서버가 포트 11434에서 수신 중인지 확인한 후, 임베딩 모델을 다운로드(pull)합니다. Ollama는 백그라운드 서비스로 실되므로, ollama pull nomic-embed-text 명령어는 가중치를 다운로드하며 이후 /api/embed 호출 시 이를 서비스합니다. 임베딩 모델은 채팅 모델에 비해 크기가 매우 작기 때문에 이 과정은 빠릅니다.
# macOS / Linux install
curl -fsSL https://ollama.com/install.sh | sh
# Make sure the server is up (background service on :11434)
ollama serve # only if it isn't already running
# Pull an embedding model and health-check the server
ollama pull nomic-embed-text
curl http://localhost:11434 # should return "Ollama is running"여기서 흥미로운 점은 nomic-embed-text 같은 임베딩 모델의 파라미터 수가 1억 3,700만 개에 불과해 다운로드 크기가 약 274MB라는 것입니다. 이는 수 GB에 달하는 채팅 모델과 대조적입니다. VRAM에 로드되는 데 약 1초가 걸립니다. 임베더와 함께 사용할 채팅 모델용 로컬 LLM 전체 설정이 궁금하다면, 로컬 LLM용 Ollama 설정 가이드를 참고하세요. 명령어(curl)보다 클릭을 선호한다면 로컬 Ollama 모델용 UI도 확인해 보세요.
전문가 팁: 요청 전에 서버가 반드시 실행 중이어야 합니다. :11434에서 연결 거부(refused connection)가 발생한다면 대부분 ollama serve가 실행되지 않았다는 의미입니다.
어떤 로컬 임베딩 모델을 다운로드해야 할까?
대부분의 로컬 RAG(Retrieval-Augmented Generation) 작업에서는 768차원의 nomic-embed-text가 안전한 기본값입니다. 이는 OpenAI의 구형 ada-002 모델보다 성능이 뛰어나며 거의 모든 하드웨어에서 실행 가능합니다. 다국어 지원이나 긴 컨텍스트 검색이 필요하면 bge-m3 또는 qwen3-embedding을, 작은 하드웨어에서 속도가 중요하면 all-minilm을, 최신 Google 옵션으로는 embeddinggemma를 선택하세요. 아래 표는 품질 순위표가 아닌 서비스 결정 기준으로서의 현재 Ollama 임베딩 모델 라이브러리를 요약한 것입니다.
| 모델 (정확한 태그) | 파라미터 수 | 출력 차원 | 컨텍스트 | 비고 |
|---|---|---|---|---|
| nomic-embed-text | 1.37억 | 768 | 기본 2048 (네이티브 8192, num_ctx 증가 필요) | 가장 인기 있는 로컬 임베더; ada-002보다 우수 |
| embeddinggemma | 3억 | 768 (MRL 512/256/128) | ~2K | Google; 현재 Ollama 추천 모델 |
| mxbai-embed-large | 3.35억 | 1024 | 512 | mixedbread.ai; 훨씬 큰 모델과 유사한 성능 |
| bge-m3 | 5.67억 | 1024 | 8192 | BAAI; 밀집(dense), 희소(sparse), 멀티벡터, 다국어 지원 |
| snowflake-arctic-embed | 2,200만~3.35억 | 최대 1024 | 512 | Snowflake; 다양한 크기 제공 |
| granite-embedding | 3,000만 / 2.78억 | 384 / 768 | 512 | IBM; 초소형 및 소형 |
| qwen3-embedding | 0.6b/4b/8b | 1024/2560/4096 (사용자 정의 가능) | 32K | 최고의 오픈 소스 다국어 및 코드 RAG용 |
| all-minilm | 2,200만 / 3,300만 | 384 | 256 | 가장 빠르고 작음 |
"best ollama embedding model reddit" 스레드들을 보면, 일반적인 RAG에는 nomic-embed-text, 다국어 작업에는 bge-m3이라는 합의가 반복적으로 나타나며, 이는 우리가 제공하는 내용과 일치합니다. 점수가 포함된 공급업체 간 순위 비교가 필요하다면 허브를 참조하세요: RAG용 임베딩 모델 선택 가이드. 우리는 여기서 의도적으로 MTEB 점수를 생략했습니다. 리더보드만으로 판단했을 때 발생할 수 있는 오해에 대해서는 RAG에서 MTEB 점수의 의미 관련 companion 글을 참고하세요.
2단계: /api/embed를 통해 임베딩 생성
텍스트를 POST /api/embed로 보내면 Ollama는 L2 정규화된 벡터를 반환합니다. 즉, 각 벡터의 길이가 1이 되어 코사인 유사도를 직접 사용할 수 있습니다. Ollama 임베딩 문서에 따르면, 현재 엔드포인트는 단일 문자열 또는 배치 처리를 위한 배열을 허용하는 input 필드를 받으며, {"embeddings": [[...]]} 형식으로 응답합니다.
raw HTTP 호출 예시:
curl http://localhost:11434/api/embed -d '{
"model": "nomic-embed-text",
"input": ["first chunk", "second chunk", "third chunk"]
}'Python에서는 공식 클라이언트를 사용하여 배치당 한 줄로 처리할 수 있습니다:
import ollama
resp = ollama.embed(
model="nomic-embed-text",
input=["first chunk", "second chunk", "third chunk"],
options={"num_ctx": 8192}, # raise context for long chunks
)
vectors = resp["embeddings"] # list of 768-float lists, L2-normalizedinput 배열을 통한 배치 처리는 처리량(throughput)을 높이는 주요 수단입니다. 64개 청크를 포함한 하나의 요청은 64개의 개별 요청보다 훨씬 효율적인데, 호출당 오버헤드를 한 번만 지불하기 때문입니다. num_ctx 증가에 주의하세요: nomic-embed-text는 네이티브로 8192토큰을 지원하지만 기본적으로 2048토큰 창(window)을 사용하므로, 이를 늘리지 않으면 긴 청크가 자동으로 잘립니다. 임베딩은 전체 RAG 파이프라인의 한 단계일 뿐이며, 청킹(chunking) 및 검색 로직은 해당 파이프라인에서 처리됩니다.
/api/embed vs /api/embeddings vs /v1/embeddings: 차이점은 무엇인가?
/api/embed는 현재 사용되는 엔드포인트이며, 대부분의 "Ollama 임베딩이 작동하지 않는다"는 게시글 뒤에는 폐기된 /api/embeddings가 있습니다. 레거시 경로는 단수 prompt 필드를 사용하고 embedding(s 없음)을 반환하는 반면, 현재 경로는 input을 사용하고 배치를 수용하며 embeddings를 반환합니다. 세 번째 경로인 /v1/embeddings는 OpenAI 호환 모드이며 dimensions 매개변수를 허용합니다.
| 엔드포인트 | 상태 | 입력 필드 | 응답 필드 | 배치 입력 가능? | dimensions 매개변수? |
|---|---|---|---|---|---|
| /api/embed | 현재 사용 중 | input (문자열 또는 배열) | embeddings | 예 | 아니오 |
| /api/embeddings | 레거시 / 폐기됨 | prompt (단일) | embedding | 아니오 | 아니오 |
| /v1/embeddings | OpenAI 호환 | input | data[].embedding | 예 | 예 (Matryoshka) |
404 오류나 이상한 응답 형식을 received하셨나요? 아마도 /api/embeddings(레거시)를 사용하고 있을 가능성이 높습니다. /api/embed로 전환하고 embedding 대신 embeddings 키를 읽으세요. 이 작은 차이가 옛날 튜토리얼을 복사하는 많은 사람들을 혼란스럽게 합니다.
/v1/embeddings 경로는 특정 경우, 즉 OpenAI에서 마이그레이션할 때 중요합니다. dimensions 매개변수를 허용하므로 Matryoshka 지원 모델을 목표 크기로 자를(truncate) 수 있습니다. 이는 다음에 다루게 될 1536차원 불일치 문제를 해결하는 방법입니다.
3단계: 벡터 저장 및 검색 (pgvector, Qdrant 또는 Chroma)
768개의 float 벡터를 최근접 이웃 검색(nearest-neighbor search)을 수행하는 데이터베이스에 저장한 후, 코사인 거리로 쿼리합니다. 우리의 RAG 빌드에서는 이미 Postgres를 사용하는 팀을 위해 **Postgres plus pgvector**를 기본으로 사용합니다. 이렇게 하면 임베딩을 관계형 데이터 옆에 보관할 수 있기 때문입니다. 확장 프로그램을 활성화하고, 모델의 차원과 일치하는 VECTOR(768) 컬럼을 선언한 후, 데이터를 삽입하고 <=> 코사인 연산자로 쿼리합니다.
CREATE EXTENSION IF NOT EXISTS vector;
CREATE TABLE chunks (
id bigserial PRIMARY KEY,
body text,
embedding vector(768) -- must match nomic-embed-text
);
-- Insert a row (embedding comes from ollama.embed)
INSERT INTO chunks (body, embedding) VALUES ('first chunk', '[0.01, -0.02, ...]');
-- Top-5 nearest chunks by cosine distance
SELECT body, 1 - (embedding <=> '[0.01, -0.02, ...]') AS score
FROM chunks
ORDER BY embedding <=> '[0.01, -0.02, ...]'
LIMIT 5;Qdrant과 Chroma도 개념적으로 동일하게 작동합니다. 모델과 일치하는 고정 벡터 크기로 컬렉션을 생성한 후, upsert 및 검색을 수행합니다. 모든 곳에서 통용되는 규칙은 다음과 같습니다: 벡터 데이터베이스 선택은 차원을 올바르게 맞추는 것보다 중요도가 낮습니다. Qdrant, Chroma, pgvector 모두 컬렉션과 크기가 일치하지 않는 벡터를 거부하기 때문입니다. 아직 결정하지 못했다면 Qdrant vs Chroma vs pgvector 비교를 참고하세요.
마이그레이션 시 주의할 점: Ollama 모델 중 네이티브로 1536차원인 것은 없으므로, 기존 VECTOR(1536) pgvector 컬럼은 이를 거부합니다. 해결 방법은 세 가지입니다: (1) 컬럼 차원과 일치하는 모델을 선택하거나, (2) qwen3-embedding 또는 embeddinggemma 같은 Matryoshka 모델에서 /v1/embeddings와 dimensions 매개변수를 사용하여 1536으로 자르거나, (3) 컬럼을 모델의 네이티브 차원(예: VECTOR(768))으로 재선언합니다.
RTX 4090에서 nomic-embed-text 측정: 콜드 스타트 vs 웜 GPU
우리는 직접 측정했습니다. 우리 환경(Ubuntu 22.04, RTX 4090 24GB, Ollama 0.5.x, 768차원 nomic-embed-text)에서 유휴(idle) 상태 후 첫 /api/embed 호출은 가중치가 VRAM으로 로드되는 동안 약 1.3초가 소요되었습니다. 일단 웜(warm) 상태가 되면, 임베딩당 p50은 약 9ms, p95는 약 22ms를 기록했습니다. 64개 배치 처리 시 초당 약 600개의 임베딩을 처리했습니다.
| 지표 | 콜드 (유휴 후 첫 요청) | 웜 (정상 상태) |
|---|---|---|
| 지연 시간 p50 | ~1.3초 | ~9ms |
| 지연 시간 p95 | ~1.3초 | ~22ms |
| 처리량 (배치=64) | 해당 없음 | ~600 임베딩/초 |
| 10,000청크 코퍼스 | 해당 없음 | ~50초 |
여기서 "왜 Ollama 임베딩이 느리거나 타임아웃이 발생하는가"라는 질문에 대한 답이 되는 함정이 있습니다. 기본적으로 Ollama는 약 5분간 유휴 상태이면 VRAM에서 모델을 언로드(unload)합니다. 따라서 다음 요청 시 약 1.3초의 콜드 스타트 비용을 다시 지불하게 되며, 이는 프로덕션 환경에서 무작위 스파이크처럼 느껴집니다. 해결책은 keep_alive 설정입니다:
curl http://localhost:11434/api/embed -d '{
"model": "nomic-embed-text",
"input": "keep me warm",
"keep_alive": -1
}'keep_alive: -1로 설정하면 모델이 VRAM에 무기한 상주하므로 모든 요청이 웜 경로를 통해 처리됩니다. 웜 상태에서 RTX 4090의 nomic-embed-text는 p95가 약 22ms를 유지했습니다. 하지만 5분간 유휴 상태로 두면 다음 요청 시 약 1.3초의 콜드 스타트 비용을 다시 지불하게 됩니다. 지연 시간에 민감한 서비스라면 모델을 상주시키세요.
임베딩 자체 호스팅은 가치가 있는가? API 대비 비용 분석
로컬 임베딩의 한계 비용(marginal cost)은 전기세를 제외하면 백만 토큰당 약 $0입니다. 반면 OpenAI text-embedding-3-small는 백만 토큰당 약 $0.02입니다. 하지만 솔직한 답은 다음과 같습니다: 자체 호스팅은 특정 토큰 볼륨 임계점 이상에서만 이점이 있습니다. 월 수억 토큰 미만이라면 절약되는 달러보다 운영 시간과 유휴 GPU 비용이 더 큽니다. 낮은 볼륨에서는 API의 편의성이 승합니다.
| 요소 | 로컬 Ollama | OpenAI API |
|---|---|---|
| 백만 토큰당 한계 비용 | ~$0 (전기세만) | ~$0.02 |
| 초기 비용 | GPU + 설정 | $0 |
| 데이터 프라이버시 | 외부로 유출되지 않음 | 공급자에게 전송 |
| 운영 부담 | 사용자가 서버 운영 | 없음 |
| 최적 대상 | 고볼륨, 비공개 데이터 | 저볼륨, GPU 없음 |
임베딩 자체 호스팅은 월 수억 토큰 이상일 때만 API보다 우위에 있습니다. 그 이하라면 절약되는 금액보다 운영 시간에 더 많은 비용이 듭니다. 로컬 환경이 적합하지 않은 경우: 쿼리 볼륨이 낮거나, GPU가 없거나, 서버를 건강하게 유지할 운영 역량이 부족한 팀입니다. 이러한 경우 관리형 API가 실용적인 선택이며, Voyage, OpenAI 및 Cohere 임베딩 API 비교를 읽어보는 것이 다음 단계입니다. GPU와 운영 부담을 지고 싶지 않다면? 많은 팀이 프라이버시를 위해 임베딩을 로컬로 유지하지만 설정 및 사후 유지보수는 외부 도움을 받습니다. 이는 우리의 AI 통합 서비스가 처리하는 영역입니다. 런타임을 비교해보고 싶다면 모델을 로컬에서 실행하기 위한 다른 도구들을 참고하세요.
저자 소개
Mert Batur Gurbuz는 Techsy.io의 공동 창업자로, B2B 고객을 위한 AI 에이전트, 자동화 시스템, 음성/SDR 파이프라인을 구축합니다. 그는 버밍엄 대학교에서 공부하며 Techsy 팀이 실제 프로덕션에서 사용하는 LLM 툴링 스택에 대해 집필합니다.
자격 증명: Techsy.io 공동 창업자, 버밍엄 대학교. LinkedIn에서 연결하세요.
자주 묻는 질문 (FAQ)
Ollama로 로컬에서 임베딩을 실행하는 것이 실제로 OpenAI API보다 저렴한가요?
특정 토큰 볼륨 임계점 이상에서만 그렇습니다. 로컬의 한계 비용은 전기세를 제외하면 백만 토큰당 약 $0인 반면, OpenAI text-embedding-3-small는 약 $0.02입니다. 월 수억 토큰 미만이라면 편의성과 운영 부담 제로라는 점에서 API가 우위에 있습니다. 자체 호스팅의 또 다른 이유는 프라이버시입니다. 데이터가 머신 외부로 나가지 않습니다.
/api/embed와 /api/embeddings의 차이점은 무엇인가요?
/api/embed는 현재 사용되는 엔드포인트입니다. input 필드(문자열 또는 배치용 배열)를 받아 embeddings를 반환합니다. /api/embeddings는 레거시이자 폐기된 경로로, 단수 prompt 필드를 사용하여 embedding을 반환합니다. 404 오류나 예상치 못한 응답 형식이 발생한다면 거의 확실히 구버전을 사용하고 있습니다.
Ollama 임베딩은 무료인가요?
네, 토큰당 요금이 없고 API 키가 필요 없다는 의미에서 무료입니다. 하드웨어와 이를 실행하는 전기세는 지불해야 합니다. 클라우드 API처럼 사용량 기반 청구가 없으므로, GPU가 실행 중인 상태에서 추가로 백만 개의 임베딩을 생성하는 데 드는 한계 비용은 사실상 없습니다.
RAG용 기본 또는 최상의 Ollama 임베딩 모델은 무엇인가요?
768차원의 nomic-embed-text는 로컬 RAG용 인기 있는 기본값입니다. OpenAI의 구형 ada-002보다 성능이 뛰어나며 modest한 하드웨어에서도 실행 가능합니다. 다국어 또는 긴 컨텍스트 작업에는 bge-m3 또는 qwen3-embedding이 더 강력합니다. 공급업체 간 순위 및 점수 비교는 임베딩 모델 허브를 참조하세요.
Ollama 임베딩이 느리거나 타임아웃이 발생하는 이유는 무엇인가요?
유휴 상태 후 첫 요청은 모델이 VRAM으로 로드되는 동안 콜드 스타트 비용을 지불합니다. 우리 RTX 4090 환경에서는 약 1.3초가 소요됩니다. 또한 Ollama는 기본적으로 약 5분간 유휴 상태이면 모델을 언로드하므로, 간헐적인 지연은 대부분 반복적인 콜드 스타트 때문입니다. 모델을 VRAM에 상주시키려면 keep_alive: -1로 설정하세요.
Ollama가 OpenAI의 1536차원 임베딩과 일치할 수 있나요?
Ollama 모델 중 네이티브로 1536차원인 것은 없으므로, 기존 VECTOR(1536) 컬럼으로 마이그레이션 시 차원 불일치로 실패합니다. qwen3-embedding 또는 embeddinggemma 같은 Matryoshka 모델에서 dimensions 매개변수와 함께 /v1/embeddings를 호출하여 1536으로 자르거나, 컬럼을 모델의 네이티브 크기(예: VECTOR(768))로 재선언하여 해결하세요.
로컬에서 임베딩 모델을 실행하려면 GPU가 필요한가요?
아니요. nomic-embed-text(1.37억) 및 all-minilm(2,200만) 같은 작은 모델은 저볼륨 작업에서 CPU에서도 잘 실행됩니다. GPU는 임베딩당 지연 시간을 한 자리 수 밀리초(ms) 단위로 줄이고 배치 처리량을 초당 수백 개로 높여줍니다. 이는 수천 개의 청크를 한 번에 인덱싱할 때 중요합니다.
Python 또는 LangChain에서 Ollama 임베딩을 어떻게 사용하나요?
공식 클라이언트 호출은 ollama.embed(model="nomic-embed-text", input=["chunk a", "chunk b"])이며, 이는 embeddings 목록을 반환합니다. LangChain에서는 http://localhost:11434를指向하는 OllamaEmbeddings 클래스를 사용한 후, 다른 임베딩 제공자와 마찬가지로 벡터 스토어의 from_documents 또는 add_texts 메서드에 전달하면 됩니다.
Ollama 임베딩 모델이 처리할 수 있는 컨텍스트 길이는 얼마인가요?
모델에 따라 다릅니다. nomic-embed-text는 네이티브로 8192토큰을 지원하지만 서비스 시 기본적으로 2048토큰 창을 사용하므로, 긴 청크가 자동으로 잘리는 것을 방지하려면 num_ctx를 8192로 늘려야 합니다. bge-m3는 8192를 처리하며 qwen3-embedding은 최대 32K까지 지원합니다. all-minilm은 256토큰으로 제한됩니다.