title_wip

### The data type[​](#the-data-type "Direct link to The data type")

Here's how to create a QBit column:

```
SET allow_experimental_qbit_type = 1;
CREATE TABLE fruit_animal
(
  word String,
  vec QBit(Float64, 5)
)
ENGINE = MergeTree
ORDER BY word;

INSERT INTO fruit_animal VALUES
('apple',  [-0.99105519, 1.28887844, -0.43526649, -0.98520696, 0.66154391]),
('banana', [-0.69372815, 0.25587061, -0.88226235, -2.54593015, 0.05300475]),
('orange', [0.93338752, 2.06571317, -0.54612565, -1.51625717, 0.69775337]),
('dog',    [0.72138876, 1.55757105, 2.10953259, -0.33961248, -0.62217325]),
('horse',  [-0.61435682, 0.48542571, 1.21091247, -0.62530446, -1.33082533]);
```

When data is inserted into a QBit column, it is transposed so that all first bits line up together, all second bits line up together, and so on. We call these **groups**.

Each group is stored in a separate `FixedString(N)` column: fixed-length strings of N bytes stored consecutively in memory with no separators between them. All such groups are then bundled together into a single `Tuple`, which forms the underlying structure of QBit.

**Example:** If we start with a vector of 8×Float64 elements, each group will contain 8 bits. Because a Float64 has 64 bits, we end up with 64 groups (one for each bit). Therefore, the internal layout of `QBit(Float64, 8)` looks like a Tuple of 64×FixedString(1) columns.

Tip

If the original vector length doesn't divide evenly by 8, the structure is padded with invisible elements to make it align to 8. This ensures compatibility with FixedString, which operates strictly on full bytes.

### The distance calculation[​](#the-distance-calculation "Direct link to The distance calculation")

To query with QBit, use the [`L2DistanceTransposed`](/docs/sql-reference/functions/distance-functions#L2DistanceTransposed) function with a precision parameter:

```
SELECT
  word,
  L2DistanceTransposed(vec, [-0.88693672, 1.31532824, -0.51182908, -0.99652702, 0.59907770], 16) AS distance
FROM fruit_animal
ORDER BY distance;
```

```
┌─word───┬────────────distance─┐
│ apple  │ 0.15196434766705247 │
│ banana │   1.966091150410285 │
│ orange │  1.9864477714218596 │
│ horse  │  2.7306267946594005 │
│ dog    │  3.2849989362383165 │
└────────┴─────────────────────┘
```

The third parameter (16) specifies the precision level in bits.

### I/O Optimisation[​](#io-optimisation "Direct link to I/O Optimisation")

Before we can calculate distances, the required data must be read from disk and then untransposed (converted back from the grouped bit representation into full vectors). Because QBit stores values bit-transposed by precision level, ClickHouse can read only the top bit planes needed to reconstruct numbers up to the desired precision.

In the query above, we use a precision level of 16. Since a Float64 has 64 bits, we only read the first 16 bit planes, **skipping 75% of the data**.

After reading, we reconstruct only the top portion of each number from the loaded bit planes, leaving the unread bits zeroed out.

### Calculation optimisation[​](#calculation-optimisation "Direct link to Calculation optimisation")

One might ask whether casting to a smaller type, such as Float32 or BFloat16, could eliminate this unused portion. It does work, but explicit casts are expensive when applied to every row.

Instead, we can downcast only the reference vector and treat the QBit data as if it contained narrower values ("forgetting" the existence of some columns), since its layout often corresponds to a truncated version of those types.

#### BFloat16 Optimization[​](#bfloat16-optimization "Direct link to BFloat16 Optimization")

BFloat16 is a Float32 truncated by half. It keeps the same sign bit and 8-bit exponent, but only the upper 7 bits of the 23-bit mantissa. Because of this, reading the first 16 bit planes from a QBit column effectively reproduces the layout of BFloat16 values. So in this case, we can (and do) safely convert the reference vector to BFloat16.

#### Float64 Complexity[​](#float64-complexity "Direct link to Float64 Complexity")

Float64, however, is a different story. It uses an 11-bit exponent and a 52-bit mantissa, meaning it's not simply a Float32 with twice the bits. Its structure and exponent bias are completely different. Downcasting a Float64 to a smaller format like Float32 requires an actual IEEE-754 conversion, where each value is rounded to the nearest representable Float32. This rounding step is computationally expensive.

Tip

If you're interested in a deep-dive of performance elements of QBit, see ["Let’s vectorize"](https://clickhouse.com/blog/qbit-vector-search#lets-vectorise)

## Example with DBpedia[​](#example "Direct link to Example with DBpedia")

Let's see QBit in action with a real-world example using the DBpedia dataset, which contains 1 million Wikipedia articles represented as Float32 embeddings.

### Setup[​](#setup "Direct link to Setup")

First, create the table

```
CREATE TABLE dbpedia
(
  id      String,
  title   String,
  text    String,
  vector  Array(Float32) CODEC(NONE)
) ENGINE = MergeTree ORDER BY (id);
```

Insert the data from your command line:

```
for i in $(seq 0 25); do
  echo "Processing file ${i}..."
  clickhouse client -q "INSERT INTO dbpedia SELECT _id, title, text, \"text-embedding-3-large-1536-embedding\" FROM url('https://huggingface.co/api/datasets/Qdrant/dbpedia-entities-openai3-text-embedding-3-large-1536-1M/parquet/default/train/${i}.parquet') SETTINGS max_http_get_redirects=5,enable_url_encoding=0;"
  echo "File ${i} complete."
done
```

Tip

Inserting the data could take a while.
Time for a coffee break!

Alternatively, individual SQL statements can be run as shown below to load each of the 25 Parquet files:

```
INSERT INTO dbpedia SELECT _id, title, text, "text-embedding-3-large-1536-embedding" FROM url('https://huggingface.co/api/datasets/Qdrant/dbpedia-entities-openai3-text-embedding-3-large-1536-1M/parquet/default/train/0.parquet') SETTINGS max_http_get_redirects=5,enable_url_encoding=0;
INSERT INTO dbpedia SELECT _id, title, text, "text-embedding-3-large-1536-embedding" FROM url('https://huggingface.co/api/datasets/Qdrant/dbpedia-entities-openai3-text-embedding-3-large-1536-1M/parquet/default/train/1.parquet') SETTINGS max_http_get_redirects=5,enable_url_encoding=0;
...
INSERT INTO dbpedia SELECT _id, title, text, "text-embedding-3-large-1536-embedding" FROM url('https://huggingface.co/api/datasets/Qdrant/dbpedia-entities-openai3-text-embedding-3-large-1536-1M/parquet/default/train/25.parquet') SETTINGS max_http_get_redirects=5,enable_url_encoding=0;
```

Verify that 1 million rows are seen in the dbpedia table:

```
SELECT count(*)
FROM dbpedia

┌─count()─┐
│ 1000000 │
└─────────┘
```

Next, add a QBit column:

```
SET allow_experimental_qbit_type = 1;

-- Assuming you have a table with Float32 embeddings
ALTER TABLE dbpedia ADD COLUMN qbit QBit(Float32, 1536);
ALTER TABLE dbpedia UPDATE qbit = vector WHERE 1;
```

### Search query[​](#search-query "Direct link to Search query")

We'll look for concepts most related to all space-related search terms: Moon, Apollo 11, Space Shuttle, Astronaut, Rocket: