🤖 AI/ML

FAISS

Facebook AI Similarity Search

Meta의 벡터 유사도 검색 라이브러리. 빠른 근사 최근접 이웃 검색.

📖 상세 설명

FAISS(Facebook AI Similarity Search)는 Meta AI Research에서 개발한 고성능 벡터 유사도 검색 라이브러리입니다. 수십억 개의 벡터를 밀리초 단위로 검색할 수 있어 대규모 임베딩 기반 검색 시스템의 핵심 인프라로 사용됩니다. C++로 작성되어 Python 바인딩을 제공하며, CPU와 GPU 모두 지원합니다.

FAISS는 2017년 오픈소스로 공개되었으며, 당시 Facebook의 10억 장 이상 이미지 검색 문제를 해결하기 위해 개발되었습니다. 기존 ANN(Approximate Nearest Neighbor) 라이브러리들보다 빠르고 메모리 효율적인 구현으로 빠르게 업계 표준이 되었습니다. 현재는 RAG(Retrieval-Augmented Generation) 시스템의 벡터 저장소로 널리 활용됩니다.

핵심 원리는 벡터 양자화(Quantization)와 인덱싱입니다. IVF(Inverted File Index)로 벡터를 클러스터링하고, PQ(Product Quantization)로 벡터를 압축하여 메모리 사용량을 줄입니다. HNSW(Hierarchical Navigable Small World) 그래프 기반 인덱스도 지원하여 검색 속도와 정확도 사이의 트레이드오프를 조절할 수 있습니다.

실무에서 FAISS는 추천 시스템, 시맨틱 검색, 중복 탐지, 이미지 검색 등에 활용됩니다. LangChain, LlamaIndex 등 LLM 프레임워크들이 기본 벡터 저장소로 FAISS를 지원합니다. 10만 개 이하 벡터는 Flat Index로 정확한 검색을, 그 이상은 IVF나 HNSW로 근사 검색을 권장합니다.

💻 코드 예제

import faiss
import numpy as np

# 1. 기본 사용법 - Flat Index (정확한 검색)
d = 128  # 벡터 차원
nb = 10000  # 데이터베이스 벡터 수
nq = 5  # 쿼리 수

# 랜덤 벡터 생성 (실제로는 임베딩 모델 출력 사용)
np.random.seed(42)
xb = np.random.random((nb, d)).astype('float32')
xq = np.random.random((nq, d)).astype('float32')

# L2 거리 기반 Flat Index
index = faiss.IndexFlatL2(d)
index.add(xb)  # 벡터 추가
print(f"인덱스 벡터 수: {index.ntotal}")

# 검색 (k=4 최근접 이웃)
k = 4
distances, indices = index.search(xq, k)
print(f"쿼리 0의 최근접 이웃: {indices[0]}")
print(f"거리: {distances[0]}")

# 2. IVF Index (대규모 데이터용 - 근사 검색)
nlist = 100  # 클러스터 수
quantizer = faiss.IndexFlatL2(d)
index_ivf = faiss.IndexIVFFlat(quantizer, d, nlist)

# IVF는 학습이 필요
index_ivf.train(xb)
index_ivf.add(xb)

# nprobe: 검색할 클러스터 수 (높을수록 정확, 느림)
index_ivf.nprobe = 10
distances, indices = index_ivf.search(xq, k)

# 3. GPU 사용 (CUDA 필요)
# res = faiss.StandardGpuResources()
# index_gpu = faiss.index_cpu_to_gpu(res, 0, index)

# 4. 인덱스 저장/로드
faiss.write_index(index, "my_index.faiss")
loaded_index = faiss.read_index("my_index.faiss")

# 5. LangChain과 함께 사용
from langchain_community.vectorstores import FAISS
from langchain_openai import OpenAIEmbeddings

# texts = ["문서1 내용", "문서2 내용", ...]
# embeddings = OpenAIEmbeddings()
# vectorstore = FAISS.from_texts(texts, embeddings)
# results = vectorstore.similarity_search("검색 쿼리", k=3)

📊 성능 & 비용

2025년 1월 기준 FAISS 인덱스 타입별 성능 비교입니다. (오픈소스, 완전 무료)

인덱스 타입 메모리 사용량 쿼리 속도 정확도
Flat (L2/IP) d x 4 bytes/벡터 느림 (전수 검색) 100% (정확)
IVF d x 4 bytes/벡터 빠름 (nprobe 조절) 95%+ (근사)
HNSW (d x 4 + M x 8) bytes 매우 빠름 98%+ (근사)
IVF-PQ Flat 대비 1/15 빠름 90%+ (근사)

권장: 10만 이하는 Flat, 100만 이하는 IVF+HNSW, 1억 이상은 IVF-PQ. HNSW 정확도 0.984, QPS 351. FAISS L2는 QPS 803 달성.

🗣️ 실무에서 이렇게 말하세요

RAG 시스템 설계 회의에서

"벡터 저장소로 FAISS를 쓸 건데, 500만 건이면 IVF-PQ 조합이 좋을 것 같습니다. nlist는 sqrt(n)인 2,000 정도로 잡고, PQ는 64 subquantizer로 시작해서 recall 보면서 튜닝하죠. 메모리 4GB 정도면 충분할 겁니다."

인프라 엔지니어와 논의 중

"FAISS 인덱스가 2GB인데 서버 메모리가 8GB라 괜찮긴 한데, 콜드 스타트 시 인덱스 로딩에 30초 걸립니다. 서비스 무중단을 위해 인덱스를 미리 로드해두는 워밍업 스크립트를 추가하고, 인덱스 업데이트는 블루-그린 배포로 하는 게 좋겠습니다."

ML 엔지니어 기술 면접에서

"FAISS의 IVF는 k-means로 벡터를 클러스터링해서 검색 범위를 줄입니다. nprobe 파라미터로 검색할 클러스터 수를 조절하는데, nprobe를 nlist와 같게 하면 정확한 검색이 되지만 느려집니다. 보통 recall 95% 이상 나올 때까지 nprobe를 올려가며 튜닝합니다."

⚠️ 흔한 실수 & 주의사항

1
인덱스 타입 선택 시 데이터 규모 고려

10만 개 이하는 Flat Index로 정확한 검색을, 그 이상은 IVF나 HNSW를 사용하세요. 1억 개 이상이면 IVF-PQ 조합으로 메모리를 절약해야 합니다. 잘못된 인덱스 선택은 검색 속도를 1000배 이상 느리게 만들 수 있습니다.

2
벡터 정규화 여부 확인

코사인 유사도를 사용하려면 벡터를 L2 정규화한 후 Inner Product(IP) 인덱스를 사용하세요. IndexFlatIP는 정규화된 벡터에서 코사인 유사도와 동일한 결과를 줍니다. 정규화 없이 IP를 사용하면 예상과 다른 결과가 나옵니다.

3
IVF 인덱스는 반드시 train() 호출

IVF 계열 인덱스는 add() 전에 train()으로 클러스터 중심을 학습해야 합니다. 학습 데이터는 최소 nlist * 30개 이상 권장합니다. train() 없이 add()하면 에러가 발생하고, 학습 데이터가 부족하면 검색 품질이 떨어집니다.

🔗 관련 용어

📚 더 배우기