Skip to content

Commit d97a9f7

Browse files
committed
RDBC-999 VectorQuantizer should return list[int] instead of bytes for proper serialization
1 parent e434a5c commit d97a9f7

1 file changed

Lines changed: 58 additions & 13 deletions

File tree

ravendb/documents/queries/vector.py

Lines changed: 58 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,25 @@
1+
from __future__ import annotations
12
import struct
2-
from typing import List, Tuple
3+
from typing import List
34

45

56
class VectorQuantizer:
67
@staticmethod
7-
def to_int8(raw_embedding: List[float]) -> bytes:
8+
def to_int8(raw_embedding: List[float]) -> List[int]:
89
"""
9-
Converts a list of floats to a packed byte array of signed 8-bit integers (int8).
10-
The maximum absolute value is appended as a 4-byte float at the end.
10+
Converts a list of floats to a list of signed 8-bit integers (int8).
11+
The returned list contains the quantized values followed by the
12+
max_component float encoded as 4 signed bytes.
1113
1214
Args:
1315
raw_embedding (List[float]): List of floating-point numbers to be quantized.
1416
1517
Returns:
16-
bytes: Packed byte array containing the quantized int8 values and the max component.
18+
List[int]: List of signed integers (-128 to 127) representing the quantized vector.
19+
Includes both the quantized values and the 4 bytes of the max_component float.
1720
"""
1821
if not raw_embedding:
19-
return b""
22+
return []
2023

2124
# Find the maximum absolute value in the input array
2225
max_component: float = max(abs(x) for x in raw_embedding)
@@ -35,21 +38,24 @@ def to_int8(raw_embedding: List[float]) -> bytes:
3538
# Append the max_component as a little-endian float
3639
packed += struct.pack("<f", max_component)
3740

38-
return packed
41+
# Convert to list of signed integers
42+
return VectorQuantizer._bytes_to_int8_list(packed)
3943

4044
@staticmethod
41-
def to_int1(raw_embedding: List[float]) -> bytes:
45+
def to_int1(raw_embedding: List[float]) -> List[int]:
4246
"""
43-
Converts a list of floats to a packed byte array of binary values (int1).
44-
Each byte represents 8 consecutive float values, where each bit corresponds to
45-
whether the float is non-negative (1) or negative (0).
47+
Converts a list of floats to a list of binary values (0 or 1).
48+
Each value in the input is converted to 1 if non-negative, 0 if negative.
4649
4750
Args:
4851
raw_embedding (List[float]): List of floating-point numbers to be quantized.
4952
5053
Returns:
51-
bytes: Packed byte array containing the binary-packed values.
54+
List[int]: List of 0s and 1s representing the binary-quantized vector.
5255
"""
56+
if not raw_embedding:
57+
return []
58+
5359
# Calculate the number of bytes needed to store the binary-packed values
5460
output_length: int = (len(raw_embedding) + 7) // 8
5561

@@ -63,4 +69,43 @@ def to_int1(raw_embedding: List[float]) -> bytes:
6369
bit_pos: int = 7 - (i % 8) # Determine the bit position within the byte
6470
bytes_list[byte_index] |= 1 << bit_pos # Set the bit to 1 if the value is non-negative
6571

66-
return bytes(bytes_list)
72+
# Convert to list of 0s and 1s
73+
return VectorQuantizer._bytes_to_int1_list(bytes(bytes_list), len(raw_embedding))
74+
75+
@staticmethod
76+
def _bytes_to_int8_list(packed_bytes: bytes) -> List[int]:
77+
"""
78+
Protected method to convert packed bytes to a list of signed int8 values.
79+
Includes all bytes (quantized values + max_component float bytes).
80+
81+
Args:
82+
packed_bytes (bytes): Packed byte array from to_int8().
83+
84+
Returns:
85+
List[int]: List of signed integers (-128 to 127).
86+
"""
87+
if not packed_bytes:
88+
return []
89+
90+
# Unpack ALL bytes as signed int8 (including the 4-byte max_component)
91+
return list(struct.unpack("b" * len(packed_bytes), packed_bytes))
92+
93+
@staticmethod
94+
def _bytes_to_int1_list(packed_bytes: bytes, original_length: int) -> List[int]:
95+
"""
96+
Protected method to convert packed binary bytes to a list of 0s and 1s.
97+
98+
Args:
99+
packed_bytes (bytes): Packed byte array from to_int1().
100+
original_length (int): Original number of float values (to trim padding).
101+
102+
Returns:
103+
List[int]: List of 0s and 1s.
104+
"""
105+
result = []
106+
for byte_val in packed_bytes:
107+
for bit_pos in range(7, -1, -1):
108+
result.append(1 if (byte_val & (1 << bit_pos)) else 0)
109+
110+
# Trim to original length (remove padding bits)
111+
return result[:original_length]

0 commit comments

Comments
 (0)