> ## Documentation Index
> Fetch the complete documentation index at: https://clickhouse.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

> 근사 벡터 검색을 위한 세밀한 양자화를 지원하는 ClickHouse의 QBit 데이터 타입 문서

# QBit 데이터 타입

`QBit` 데이터 타입은 더 빠른 근사 벡터 검색을 위해 벡터 저장 방식을 재구성합니다. 각 벡터의 요소를 함께 저장하는 대신, 모든 벡터에서 동일한 비트 위치끼리 묶어 저장합니다.
이 방식은 벡터를 전체 정밀도로 저장하면서도, 검색 시점에 세밀한 양자화 수준을 선택할 수 있게 합니다. 즉, I/O를 줄이고 계산 속도를 높이기 위해 더 적은 비트를 읽거나, 더 높은 정확도를 위해 더 많은 비트를 읽을 수 있습니다. 양자화를 통해 데이터 전송량과 연산량을 줄여 속도상의 이점을 얻으면서도, 필요할 때는 원본 데이터 전체를 그대로 사용할 수 있습니다.

`QBit` 타입의 컬럼을 선언하려면 다음 구문을 사용합니다:

```sql theme={null}
column_name QBit(element_type, dimension[, stride])
```

* `element_type` – 각 벡터 요소의 타입입니다. 허용되는 타입은 `Int8`, `BFloat16`, `Float32`, `Float64`입니다
* `dimension` – 각 벡터를 구성하는 요소의 수입니다
* `stride` – 선택 사항입니다. 하나의 스트림 그룹에 함께 저장되는 차원의 수입니다. 생략하면 기본값은 `dimension`(단일 그룹)입니다. 지정하는 경우 `dimension`은 `stride`의 배수여야 하며, `stride`가 `dimension`보다 작을 때는 `stride`도 8의 배수여야 합니다. `dimension`개의 차원은 `dimension / stride`개의 연속된 그룹으로 나뉘며, 각 그룹의 비트 평면은 별도의 스트림에 저장됩니다. 따라서 처음 `D`개 차원에 대한 검색에서는(`D`는 `stride`의 배수) 해당 차원을 포함하는 그룹의 스트림만 읽으면 되므로 Matryoshka embeddings에 유용합니다.

<div id="creating-qbit">
  ## QBit 생성
</div>

테이블 컬럼 정의에 `QBit` 유형을 사용합니다:

```sql theme={null}
CREATE TABLE test (id UInt32, vec QBit(Float32, 8)) ENGINE = Memory;
INSERT INTO test VALUES (1, [1, 2, 3, 4, 5, 6, 7, 8]), (2, [9, 10, 11, 12, 13, 14, 15, 16]);
SELECT vec FROM test ORDER BY id;
```

```text theme={null}
┌─vec──────────────────────┐
│ [1,2,3,4,5,6,7,8]        │
│ [9,10,11,12,13,14,15,16] │
└──────────────────────────┘
```

<div id="converting-arrays-to-qbit">
  ## 배열을 QBit으로 변환하기
</div>

배열 길이가 `QBit` 차원과 일치하면 배열을 `QBit`으로 변환할 수 있습니다. 배열의 요소 타입이 `QBit` 요소 타입과 같을 필요는 없습니다. 숫자 요소 타입은 모두 자동으로 해당 타입으로 변환됩니다. 따라서 기존 embeddings 컬럼을 바로 `QBit` 컬럼으로 옮길 수 있습니다:

```sql theme={null}
CREATE TABLE embeddings (id UInt32, embedding Array(Float32)) ENGINE = Memory;
INSERT INTO embeddings VALUES (1, [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8]), (2, [0.8, 0.7, 0.6, 0.5, 0.4, 0.3, 0.2, 0.1]);

CREATE TABLE vectors (id UInt32, vec QBit(Float32, 8)) ENGINE = Memory;
INSERT INTO vectors SELECT id, embedding FROM embeddings;

SELECT * FROM vectors ORDER BY id;
```

```text theme={null}
┌─id─┬─vec───────────────────────────────┐
│  1 │ [0.1,0.2,0.3,0.4,0.5,0.6,0.7,0.8] │
│  2 │ [0.8,0.7,0.6,0.5,0.4,0.3,0.2,0.1] │
└────┴───────────────────────────────────┘
```

이 변환은 `CAST`를 사용해 명시적으로 수행할 수도 있습니다. 예를 들어 `CAST(embedding AS QBit(Float32, 8))`와 같습니다.

<div id="converting-qbit-to-arrays">
  ## QBit를 배열로 변환하기
</div>

역변환은 비트 전치 표현에서 원래 벡터를 복원하므로, `QBit`를 `Array`로 캐스팅하면 저장된 값이 반환됩니다. 이는 [배열을 `QBit`로 변환하기](#converting-arrays-to-qbit)의 역과정입니다:

```sql theme={null}
SELECT [1, 2, 3, 4]::QBit(Float32, 4)::Array(Float32) AS vec;
```

```text theme={null}
┌─vec───────┐
│ [1,2,3,4] │
└───────────┘
```

재구성된 배열은 `QBit`의 타입을 사용한 뒤, 각 요소를 요청된 배열 타입으로 변환합니다. 따라서 `QBit(Float32, N)`에서 `Array(Float64)`로처럼 타입까지 변경하는 cast도 동작합니다.

`Array` -> `QBit` -> `Array` 왕복 변환은 `Int8`, `Float32`, `Float64`에서는 손실이 없습니다. `BFloat16`의 경우 `BFloat16`로 직접 변환한 결과와 일치하며, 손실되는 정밀도는 `BFloat16` 자체의 정밀도뿐입니다.

`차원`이 8의 배수가 아니면 내부 표현에 포함된 후행 패딩 요소가 삭제되므로, 결과에는 항상 정확히 `차원`개의 요소만 포함됩니다.

<div id="converting-between-qbit-types">
  ## QBit 타입 간 변환
</div>

`QBit`은 `dimension`(벡터 원소 수)이 동일하게 유지되는 한 다른 `QBit`으로 캐스팅할 수 있습니다. `element_type`과 `stride`는 모두 변경할 수 있지만, `dimension`이 다른 `QBit`으로 캐스팅하면 벡터 자체가 바뀌므로 예외가 발생합니다.

`element_type`을 변경하면 벡터를 다시 구성하고 각 원소를 새 타입으로 변환하는데, 이는 해당 `Array` 변환과 정확히 같습니다. 확장 변환(예: `QBit(Float32, N)`에서 `QBit(Float64, N)`으로)은 정확하게 수행되지만, 축소 변환은 축소 `Array` 캐스트와 마찬가지로 정밀도가 손실됩니다.

```sql theme={null}
SELECT [1, 2, 3, 4]::QBit(Float32, 4)::QBit(Float64, 4) AS vec;
```

```text theme={null}
┌─vec───────┐
│ [1,2,3,4] │
└───────────┘
```

[`stride`](#strides)만 변경하고 `element_type`은 그대로 유지하면, 값 자체는 건드리지 않은 채 저장된 비트 평면만 다시 그룹화하므로 항상 무손실입니다:

```sql theme={null}
SELECT range(16)::Array(Float32)::QBit(Float32, 16)::QBit(Float32, 16, 8)::Array(Float32)
     = range(16)::Array(Float32) AS is_lossless;
```

```text theme={null}
┌─is_lossless─┐
│           1 │
└─────────────┘
```

<div id="qbit-subcolumns">
  ## QBit 서브컬럼
</div>

`QBit`는 저장된 벡터의 개별 비트 평면에 접근할 수 있는 서브컬럼 접근 패턴을 구현합니다. 각 비트 위치는 `.N` 구문으로 접근할 수 있으며, 여기서 `N`은 해당 비트 위치를 나타냅니다:

```sql theme={null}
CREATE TABLE test (id UInt32, vec QBit(Float32, 8)) ENGINE = Memory;
INSERT INTO test VALUES (1, [0, 0, 0, 0, 0, 0, 0, 0]);
INSERT INTO test VALUES (1, [-0, -0, -0, -0, -0, -0, -0, -0]);
SELECT bin(vec.1) FROM test;
```

```text theme={null}
┌─bin(tupleElement(vec, 1))─┐
│ 00000000                  │
│ 11111111                  │
└───────────────────────────┘
```

접근 가능한 서브컬럼 수는 요소 타입에 따라 달라지며, stride가 적용된 경우 stride 그룹 수에도 따라 달라집니다:

* `Int8`: stride 그룹당 8개 서브컬럼 (1-8)
* `BFloat16`: stride 그룹당 16개 서브컬럼 (1-16)
* `Float32`: stride 그룹당 32개 서브컬럼 (1-32)
* `Float64`: stride 그룹당 64개 서브컬럼 (1-64)

서브컬럼은 그룹 우선 순서를 따릅니다. 일반적으로 `vec.N`은 stride 그룹 `(N-1) / element_size`의 비트 평면 `(N-1) % element_size`를 읽습니다. 예를 들어 `QBit(BFloat16, 4096, 1024)`에서는 4096개 차원이 1024개씩 4개의 그룹으로 나뉘므로 서브컬럼은 총 64개입니다. `vec.1` … `vec.16`은 첫 번째 stride 그룹(차원 1–1024)의 비트 평면이고, `vec.17` … `vec.32`는 두 번째 그룹(차원 1025–2048)에 속하며, 이후도 같은 방식으로 이어집니다.

<div id="strides">
  ## 스트라이드
</div>

기본적으로 `QBit`는 각 비트 평면을 모든 `dimension` 차원에 걸친 단일 스트림으로 저장하므로, 검색 시 항상 전체 벡터의 비트 평면을 모두 읽게 됩니다. 선택적 `stride` 매개변수는 `dimension` 차원을 `dimension / stride`개의 연속된 그룹으로 나누고, 각 그룹의 비트 평면을 별도의 스트림에 저장합니다. 그러면 처음 `D`개 차원만 대상으로 검색할 때(`D`는 `stride`의 배수), 해당 차원을 포함하는 그룹의 스트림만 읽으면 됩니다. 이는 앞부분 차원만으로도 더 낮은 차원의 유효한 임베딩을 구성하는 [Matryoshka embeddings](https://arxiv.org/abs/2205.13147)에 특히 유용합니다.

```sql theme={null}
CREATE TABLE test (id UInt32, vec QBit(BFloat16, 4096, 1024)) ENGINE = MergeTree ORDER BY id;
```

여기서 4096개 차원은 1024개씩 4개의 그룹으로 나뉩니다. 서브컬럼은 그룹 우선 순서를 따릅니다. 즉, `BFloat16`(16개의 비트 평면)에서는 `vec.1` … `vec.16`이 첫 번째 stride 그룹(차원 1–1024)의 16개 비트 평면이고, `vec.17` … `vec.32`는 두 번째 그룹(차원 1025–2048)에 속하며, 이후에도 같은 방식으로 이어집니다. 일반적으로 `vec.N`은 stride 그룹 `(N-1) / element_size`의 비트 평면 `(N-1) % element_size`를 읽습니다.

축소 차원 검색을 실행하려면 전치 거리 함수의 네 번째 인수로 읽을 차원 수를 전달하십시오(아래 참조). 참조 벡터는 최소한 그 개수만큼의 원소를 가져야 하며(이를 초과하는 뒤쪽 원소는 무시됨), 이 값은 `stride`의 배수여야 합니다.

<div id="vector-search-functions">
  ## 벡터 검색 함수
</div>

다음은 `QBit` 데이터 타입을 사용하는 벡터 유사도 검색용 거리 함수입니다:

* [`L2DistanceTransposed`](/docs/ko/reference/functions/regular-functions/distance-functions#L2DistanceTransposed)
* [`cosineDistanceTransposed`](/docs/ko/reference/functions/regular-functions/distance-functions#cosineDistanceTransposed)
* [`dotProductTransposed`](/docs/ko/reference/functions/regular-functions/distance-functions#dotProductTransposed)

stride가 적용된 `QBit`의 경우, 이러한 함수는 선택적인 네 번째 인수 `used_dims`(읽을 선행 차원의 수)를 받을 수 있으며, 이 경우 해당 차원을 포함하는 stride 그룹만 읽습니다. 참조 벡터는 최소 `used_dims`개의 원소를 가져야 하며(뒤에 추가된 원소는 무시되므로, 전체 크기의 쿼리 벡터를 먼저 잘라내지 않고도 축소 차원 검색에 재사용할 수 있습니다), `used_dims`는 `stride`의 배수여야 합니다.
