제품 양자화
제품 양자화 (Product Quantization for Vector Search)
벡터 검색에서 메모리는 생각보다 빨리 차오르기 마련이에요. 이 글에서는 Qdrant 1.2.0에 새로 들어온 **제품 양자화(Product Quantization, PQ)**가 어떻게 메모리를 크게 줄이면서도 거리를 계산하는지, 그리고 어느 정도의 정확도·성능 트레이드오프가 따라오는지 하나씩 살펴볼게요. 특히 벤치마크 표에 있는 숫자들은 그대로 기억해 두시면 좋아요.
출처: 공식문서
제품 양자화란?
제품 양자화는 다른 모든 양자화 방법처럼 부동소수점 숫자를 정수로 바꿔요. 다만 그 과정이 스칼라 양자화(Scalar Quantization)보다 조금 더 복잡하고, 그만큼 더 유연하게 조정할 수 있어요. 덕분에 메모리 사용량과 검색 정확도 사이에서 여러분이 원하는 균형점(sweet spot)을 직접 찾을 수 있죠. 이 글은 제품 양자화를 수행하는 데 필요한 모든 단계와, Qdrant에서 그것이 어떻게 구현되어 있는지를 다룹니다.
Qdrant 1.1.0은 스칼라 양자화를 지원하면서 float32로 표현하던 값을 int8로 바꿔 메모리 사용량을 최대 4배까지 줄여줬어요. 그런데 벡터 검색에서 메모리 사용량은 더 줄일 수 있습니다! 바로 **제품 양자화(Product Quantization)**를 소개합니다, Qdrant 1.2.0의 새로운 기능이에요.
제품 양자화는 어떻게 동작하나요?
컬렉션에 몇 개의 벡터가 추가되고, 옵티마이저가 새 세그먼트 생성을 시작하기로 했다고 가정해 볼게요.
벡터를 조각으로 자르기
먼저 우리 벡터들은 청크(chunk) 즉 **서브벡터(subvector)**로 나뉘어요. 청크 개수는 설정할 수 있는데, 대략적으로 말하면 개수가 적을수록 압축률은 높아져요. 물론 그 대가로 검색 정확도는 떨어지고요. 어떤 경우에는 메모리 사용량을 최대한 낮게 유지하는 게 더 나을 수도 있으니까요.
Qdrant API는 4배에서 64배까지 압축 비율을 고를 수 있어요. 이 예시에서는 16배를 골랐다고 생각해 볼게요. 그러면 각 서브벡터는 4개의 float(16바이트)로 이루어지고, 최종적으로는 한 개의 바이트로 표현됩니다.
클러스터링
벡터의 청크들은 이제 클러스터링의 입력으로 사용돼요. Qdrant는 K-means 알고리즘을 쓰는데, 여기서 K=256이에요. 이 값은 사전에 정해진 것인데, 한 바이트가 표현할 수 있는 값의 최댓값이 256이기 때문이에요. 그 결과 각 청크 그룹별로 256개의 센트로이드(centroid) 리스트를 얻고, 각각에 고유한 id를 부여합니다. 클러스터링은 각 청크 그룹마다 별도로 수행돼요.
이제 벡터의 각 청크는 가장 가까운 센트로이드에 매핑될 수 있어요. 바로 여기서 정밀도가 손실되는데, 하나의 점이 오직 하나의 부분공간(subspace)만을 대표하게 되기 때문이에요. 서브벡터를 통째로 저장하는 대신, 가장 가까운 센트로이드의 id만 저장하면 됩니다. 이걸 각 청크마다 반복하면, 원래 임베딩을 센트로이드 id들의 연속된 벡터로 근사할 수 있어요. 이렇게 만들어진 벡터의 차원은 청크 개수와 같아지는데, 우리 예시에서는 2가 됩니다.
전체 과정
이 모든 단계들이 모여 제품 양자화의 다음 파이프라인을 만듭니다.
거리 측정
벡터 검색은 점들 사이의 거리에 의존해요. 제품 양자화를 켜면 거리를 계산하는 방식이 조금 달라져요. 질의(query) 벡터를 청크로 나눈 다음, 전체 거리를 비교 대상 벡터의 특정 id에 할당된 센트로이드와 서브벡터 사이의 거리들의 합으로 구합니다. 센트로이드의 좌표는 우리가 알고 있으니 계산은 어렵지 않아요.
Qdrant에서의 구현
검색 연산은 여러 점에 대한 거리 계산을 필요로 해요. 그런데 우리는 유한한 센트로이드 집합에 대해서만 거리를 계산하므로, 이 값들을 미리 계산해 두고 재사용할 수 있어요. Qdrant는 질의마다 **룩업 테이블(lookup table)**을 만들어서, 질의와 모든 센트로이드 사이의 거리를 측정하기 위해 여러 항을 그냥 더하기만 하면 됩니다.
| Centroid 0 | Centroid 1 | … | |
|---|---|---|---|
| Chunk 0 | 0.14213 | 0.51242 | |
| Chunk 1 | 0.08421 | 0.00142 | |
| … | … | … | … |
제품 양자화 벤치마크
제품 양자화에는 비용이 따릅니다. 수행해야 할 추가 연산이 생기므로 성능은 떨어질 수 있어요. 하지만 메모리 사용량도 크게 줄일 수 있고요. 늘 그렇듯 여러분이 무엇을 기대할 수 있는지 감을 잡을 수 있도록 벤치마크를 수행했습니다.
공개한 다른 벤치마크와 같은 파이프라인을 다시 사용했어요. 제품 양자화가 정밀도와 시간에 미치는 영향을 측정하기 위해 Arxiv-titles-384-angular-no-filters와 Glove-100 데이터셋을 골랐습니다. 두 실험 모두 EF=128로 실행했고, 결과는 다음 표에 요약되어 있어요.
Glove-100
| Original | 1D clusters | 2D clusters | 3D clusters | |
|---|---|---|---|---|
| Mean precision | 0.7158 | 0.7143 | 0.6731 | 0.5854 |
| Mean search time | 2336 µs | 2750 µs | 2597 µs | 2534 µs |
| Compression | x1 | x4 | x8 | x12 |
| Upload & indexing time | 147 s | 339 s | 217 s | 178 s |
제품 양자화는 인덱싱과 검색 시간을 모두 늘립니다. 압축 비율이 높을수록 검색 정밀도는 낮아지고요. 가장 큰 이점은 의심할 여지 없이 메모리 사용량이 줄어드는 것이에요.
Arxiv-titles-384-angular-no-filters
| Original | 1D clusters | 2D clusters | 4D clusters | 8D clusters | |
|---|---|---|---|---|---|
| Mean precision | 0.9837 | 0.9677 | 0.9143 | 0.8068 | 0.6618 |
| Mean search time | 2719 µs | 4134 µs | 2947 µs | 2175 µs | 2053 µs |
| Compression | x1 | x4 | x8 | x16 | x32 |
| Upload & indexing time | 332 s | 921 s | 597 s | 481 s | 474 s |
어떤 경우에는 제품 양자화가 메모리 사용량만 줄이는 게 아니라 검색 시간까지 줄여주기도 한다는 걸 알 수 있어요.
제품 양자화 vs 스칼라 양자화
스칼라 양자화에 비해 제품 양자화는 더 높은 압축률을 제공해요. 하지만 그 대가로 정확도가 상당히 떨어지고, 때로는 인메모리 검색 속도도 저하될 수 있어요.
제품 양자화는 특정 시나리오에서 선호되는 경향이 있습니다.
- 메모리가 낮은 환경에 배포하는데, 병목이 벡터 비교 자체보다는 디스크 읽기 횟수인 경우
- 원래 벡터의 차원이 충분히 높은 상황
- 인덱싱 속도가 핵심 요소가 아닌 경우
위에 해당하지 않는 상황이라면 스칼라 양자화가 더 나은 선택이에요.
Qdrant에서 제품 양자화 사용하기
이미 Qdrant 사용자라면 제품 양자화 문서가 데이터에 새 양자화를 설정·구성하는 방법을 도와줄 거예요. 최대 64배까지 메모리를 줄일 수 있습니다.
제품 양자화의 힘을 직접 경험해 보고 싶으신가요? 지금 가입해서 무료 Qdrant 데모로 데이터 관리를 최적화해 보세요!