QBit reorganiza o armazenamento de vetores para tornar as buscas aproximadas mais rápidas. Em vez de armazenar juntos os elementos de cada vetor, ele agrupa as mesmas posições de dígitos binários em todos os vetores.
Isso armazena os vetores com precisão total e permite escolher o nível de quantização de granularidade fina no momento da busca: leia menos bits para reduzir a E/S e acelerar os cálculos, ou mais bits para obter maior precisão. Você aproveita os ganhos de velocidade da redução da transferência de dados e do processamento proporcionada pela quantização, mas todos os dados originais continuam disponíveis quando necessário.
Para declarar uma coluna do tipo QBit, use a seguinte sintaxe:
element_type– o tipo de cada elemento do vetor. Os tipos permitidos sãoInt8,BFloat16,Float32eFloat64dimension– o número de elementos de cada vetorstride– opcional. O número de dimensões armazenadas juntas em um grupo de fluxos. Quando omitido, o padrão édimension(um único grupo). Quando fornecido,dimensiondeve ser um múltiplo destridee, quandostridefor menor quedimension,stridedeve ser um múltiplo de 8. Asdimensiondimensões são divididas emdimension / stridegrupos contíguos, e os planos de bits de cada grupo são armazenados em fluxos separados. Isso permite que uma busca nas primeirasDdimensões (comDsendo um múltiplo destride) leia apenas os fluxos dos grupos que abrangem essas dimensões, o que é útil para embeddings Matryoshka.
Criando QBit
QBit na definição de coluna da tabela:
Convertendo arrays em QBit
QBit quando o comprimento do array corresponde à dimensão do QBit. O tipo de elemento do array não precisa corresponder ao tipo de elemento do QBit. Qualquer tipo numérico de elemento é convertido automaticamente. Isso permite mover uma coluna existente de embeddings diretamente para uma coluna QBit:
CAST, por exemplo CAST(embedding AS QBit(Float32, 8)).
Convertendo QBit para arrays
QBit em um Array retorna os valores armazenados. Isso é o inverso de converter arrays para QBit:
QBit, e seus elementos são então convertidos para o tipo de elemento do array solicitado. Portanto, um cast que também altera o tipo de elemento, como de QBit(Float32, N) para Array(Float64), também funciona.
Uma conversão de ida e volta Array -> QBit -> Array não perde informação para Int8, Float32 e Float64. Para BFloat16, ela corresponde a uma conversão direta para BFloat16 — a única precisão perdida é a do próprio BFloat16.
Quando a dimension não é um múltiplo de 8, os elementos de preenchimento no final presentes na representação interna são descartados, de modo que o resultado sempre tenha exatamente dimension elementos.
Conversão entre tipos QBit
QBit pode ser convertido em outro QBit desde que a dimension (o número de elementos do vetor) permaneça a mesma. Tanto element_type quanto stride podem mudar; converter para um QBit com uma dimension diferente gera uma exceção, porque isso alteraria o próprio vetor.
Alterar o element_type reconstrói o vetor e converte cada elemento para o novo tipo, exatamente como na conversão correspondente de Array: a ampliação (por exemplo, de QBit(Float32, N) para QBit(Float64, N)) é exata, enquanto a redução perde precisão da mesma forma que uma conversão redutora de Array.
stride (mantendo o mesmo element_type) reagrupa os planos de bits armazenados sem alterar os valores, portanto não há perda de dados:
Subcolunas do QBit
QBit implementa um padrão de acesso a subcolunas que permite acessar planos de bits individuais dos vetores armazenados. Cada posição de bit pode ser acessada usando a sintaxe .N, em que N é a posição do bit:
stride, do número de grupos de stride):
Int8: 8 subcolunas por grupo de stride (1-8)BFloat16: 16 subcolunas por grupo de stride (1-16)Float32: 32 subcolunas por grupo de stride (1-32)Float64: 64 subcolunas por grupo de stride (1-64)
vec.N lê o plano de bits (N-1) % element_size do grupo de stride (N-1) / element_size. Por exemplo, com QBit(BFloat16, 4096, 1024), as 4096 dimensões são divididas em 4 grupos de 1024, portanto há 64 subcolunas: vec.1 … vec.16 são os planos de bits do primeiro grupo de stride (dimensões 1–1024), vec.17 … vec.32 pertencem ao segundo grupo (dimensões 1025–2048), e assim por diante.
Strides
QBit armazena cada plano de bits como um único fluxo que abrange todas as dimension dimensões, de modo que uma busca sempre lê os planos de bits completos ao longo de todo o vetor. O parâmetro opcional stride particiona as dimension dimensões em dimension / stride grupos contíguos e armazena os planos de bits de cada grupo em fluxos separados. Isso permite que uma busca restrita às primeiras D dimensões (com D sendo um múltiplo de stride) leia apenas os fluxos dos grupos que cobrem essas dimensões — algo útil para embeddings Matryoshka, em que as dimensões iniciais formam um embedding utilizável de menor dimensionalidade.
BFloat16 (16 planos de bits), vec.1 … vec.16 são os 16 planos de bits do primeiro grupo de stride (dimensões 1–1024), vec.17 … vec.32 pertencem ao segundo grupo (dimensões 1025–2048), e assim por diante. Em geral, vec.N lê o plano de bits (N-1) % element_size do grupo de stride (N-1) / element_size.
Para executar uma busca em dimensão reduzida, passe o número de dimensões a serem lidas como o quarto argumento das funções de distância transpostas (veja abaixo). O vetor de referência deve ter pelo menos essa quantidade de elementos (quaisquer elementos extras ao final são ignorados), e o valor deve ser um múltiplo de stride.
Acesso a elementos e fatiamento
arrayElement (e o operador vec[n]) retorna o enésimo elemento do vetor, reconstruído com a precisão total do tipo de elemento. Apenas os planos de bits do grupo de stride que contém o elemento são lidos. arraySlice retorna um QBit com as dimensões selecionadas (uma projeção para um subconjunto de dimensões); o deslocamento e o comprimento devem ser constantes, pois a dimensão de um QBit faz parte do seu tipo. Um fatiamento alinhado aos limites dos grupos de stride preserva o stride e reutiliza os fluxos de planos de bits armazenados sem copiá-los:
Funções de busca vetorial
QBit:
Para um QBit com stride, essas funções aceitam um quarto argumento opcional, used_dims — o número de dimensões iniciais a serem lidas — e leem apenas os grupos de stride que abrangem essas dimensões. O vetor de referência deve ter pelo menos used_dims elementos (quaisquer elementos extras no final são ignorados, portanto um vetor de consulta de tamanho completo pode ser reutilizado para uma busca com dimensão reduzida sem precisar fatiá-lo antes), e used_dims deve ser um múltiplo de stride.