🚀 Experimente o Zilliz Cloud, o Milvus totalmente gerenciado, gratuitamente—experimente um desempenho 10x mais rápido! Experimente Agora>>

milvus-logo
LFAI
  • Home
  • Blog
  • Otimizar bases de dados de vectores, melhorar a IA generativa orientada por RAG

Otimizar bases de dados de vectores, melhorar a IA generativa orientada por RAG

  • Engineering
May 13, 2024
Cathy Zhang, Dr. Malini Bhandaru

Este post foi originalmente publicado no canal Medium da Intel e é republicado aqui com permissão.


Dois métodos para otimizar a sua base de dados de vectores quando utiliza o RAG

Foto de Ilya Pavlov no Unsplash

Por Cathy Zhang e Dr. Malini Bhandaru Colaboradores: Lin Yang e Changyan Liu

Os modelos de IA generativa (GenAI), que estão a ser adoptados de forma exponencial no nosso quotidiano, estão a ser melhorados através da geração aumentada por recuperação (RAG), uma técnica utilizada para aumentar a precisão e a fiabilidade das respostas, obtendo factos de fontes externas. A RAG ajuda um modelo regular de grande linguagem (LLM) a compreender o contexto e a reduzir as alucinações, tirando partido de uma base de dados gigante de dados não estruturados armazenados como vectores - uma apresentação matemática que ajuda a captar o contexto e as relações entre os dados.

As RAG ajudam a obter mais informações contextuais e, assim, a gerar melhores respostas, mas as bases de dados vectoriais em que se baseiam estão a tornar-se cada vez maiores para fornecerem um conteúdo rico a que recorrer. Tal como os LLM de triliões de parâmetros estão no horizonte, as bases de dados vectoriais de milhares de milhões de vectores não estão muito longe. Como engenheiros de otimização, estávamos curiosos para ver se podíamos tornar as bases de dados vectoriais mais eficientes, carregar dados mais rapidamente e criar índices mais rapidamente para garantir a velocidade de recuperação, mesmo quando são adicionados novos dados. Ao fazê-lo, não só se reduziria o tempo de espera do utilizador, como também se tornariam as soluções de IA baseadas em RAG um pouco mais sustentáveis.

Neste artigo, ficará a saber mais sobre as bases de dados vectoriais e as suas estruturas de avaliação comparativa, conjuntos de dados para abordar diferentes aspectos e as ferramentas utilizadas para a análise de desempenho - tudo o que precisa para começar a otimizar as bases de dados vectoriais. Também partilharemos os nossos resultados de otimização em duas soluções populares de bases de dados vectoriais para o inspirar no seu percurso de otimização do desempenho e do impacto na sustentabilidade.

Compreender as bases de dados vectoriais

Ao contrário das bases de dados relacionais ou não relacionais tradicionais, em que os dados são armazenados de forma estruturada, uma base de dados vetorial contém uma representação matemática de itens de dados individuais, denominada vetor, construída através de uma função de incorporação ou transformação. O vetor representa normalmente caraterísticas ou significados semânticos e pode ser curto ou longo. As bases de dados vectoriais efectuam a recuperação de vectores através da pesquisa de semelhanças utilizando uma métrica de distância (em que mais próximo significa que os resultados são mais semelhantes), como a semelhança euclidiana, o produto escalar ou o cosseno.

Para acelerar o processo de recuperação, os dados vectoriais são organizados utilizando um mecanismo de indexação. Exemplos destes métodos de organização incluem estruturas planas, ficheiro invertido (IVF), Hierarchical Navigable Small Worlds (HNSW) e locality-sensitive hashing (LSH), entre outros. Cada um destes métodos contribui para a eficiência e eficácia da recuperação de vectores semelhantes quando necessário.

Vejamos como utilizar uma base de dados de vectores num sistema GenAI. A Figura 1 ilustra o carregamento de dados numa base de dados vetorial e a sua utilização no contexto de uma aplicação GenAI. Quando o utilizador introduz a sua mensagem, esta é submetida a um processo de transformação idêntico ao utilizado para gerar vectores na base de dados. Este comando vetorial transformado é depois utilizado para recuperar vectores semelhantes da base de dados vetorial. Estes itens recuperados funcionam essencialmente como memória de conversação, fornecendo um historial contextual para as mensagens, à semelhança do funcionamento dos LLM. Esta caraterística revela-se particularmente vantajosa no processamento de linguagem natural, visão por computador, sistemas de recomendação e outros domínios que requerem compreensão semântica e correspondência de dados. O seu pedido inicial é subsequentemente "fundido" com os elementos recuperados, fornecendo contexto e ajudando o LLM a formular respostas com base no contexto fornecido, em vez de se basear apenas nos seus dados de treino originais.

Figura 1. Arquitetura de uma aplicação RAG.

Os vectores são armazenados e indexados para uma recuperação rápida. As bases de dados vectoriais existem em dois tipos principais: as bases de dados tradicionais que foram alargadas para armazenar vectores e as bases de dados vectoriais criadas para o efeito. Alguns exemplos de bancos de dados tradicionais que oferecem suporte a vetores são Redis, pgvector, Elasticsearch e OpenSearch. Exemplos de bases de dados vectoriais criadas propositadamente incluem as soluções proprietárias Zilliz e Pinecone, e os projectos de código aberto Milvus, Weaviate, Qdrant, Faiss e Chroma. Pode saber mais sobre bases de dados vectoriais no GitHub através do LangChain e do OpenAI Cookbook.

Vamos dar uma olhada mais de perto em um de cada categoria, Milvus e Redis.

Melhorando o desempenho

Antes de mergulhar nas otimizações, vamos rever como os bancos de dados vetoriais são avaliados, alguns frameworks de avaliação e ferramentas de análise de desempenho disponíveis.

Métricas de desempenho

Vejamos as principais métricas que podem ajudar a medir o desempenho do banco de dados vetorial.

  • A latência de carga mede o tempo necessário para carregar dados na memória do banco de dados vetorial e criar um índice. Um índice é uma estrutura de dados usada para organizar e recuperar com eficiência dados vetoriais com base em sua similaridade ou distância. Os tipos de índices na memória incluem o índice plano, IVF_FLAT, IVF_PQ, HNSW, scalable nearest neighbors (ScaNN)e DiskANN.
  • A recuperação é a proporção de correspondências verdadeiras, ou itens relevantes, encontrados nos K principais resultados recuperados pelo algoritmo de pesquisa. Valores de recuperação mais elevados indicam uma melhor recuperação de itens relevantes.
  • Consultas por segundo (QPS) é a taxa a que a base de dados vetorial pode processar as consultas recebidas. Valores mais elevados de QPS implicam uma melhor capacidade de processamento de consultas e um melhor rendimento do sistema.

Quadros de avaliação comparativa

Figura 2. O quadro de avaliação comparativa da base de dados vetorial.

A avaliação comparativa de uma base de dados vetorial requer um servidor de base de dados vetorial e clientes. Nos nossos testes de desempenho, utilizámos duas ferramentas populares de código aberto.

  • VectorDBBench: Desenvolvido e de código aberto por Zilliz, o VectorDBBench ajuda a testar diferentes bases de dados vectoriais com diferentes tipos de índices e fornece uma interface Web conveniente.
  • vetor-db-benchmark: Desenvolvido e de código aberto por Qdrant, o vetor-db-benchmark ajuda a testar várias bases de dados vectoriais típicas para o tipo de índice HNSW. Ele executa testes por meio da linha de comando e fornece um Docker Compose __file para simplificar a inicialização dos componentes do servidor.

Figura 3. Um exemplo de comando vetor-db-benchmark usado para executar o teste de benchmark.

Mas a estrutura de benchmark é apenas parte da equação. Precisamos de dados que exercitem diferentes aspectos da própria solução de banco de dados vetorial, como sua capacidade de lidar com grandes volumes de dados, vários tamanhos de vetor e velocidade de recuperação.

Conjuntos de dados abertos para testar bases de dados vectoriais

Grandes conjuntos de dados são bons candidatos para testar a latência de carga e a alocação de recursos. Alguns conjuntos de dados têm dados de elevada dimensão e são bons para testar a velocidade da similaridade de computação.

Os conjuntos de dados variam de uma dimensão de 25 a uma dimensão de 2048. O conjunto de dados LAION, uma coleção de imagens aberta, tem sido utilizado para treinar modelos neurais profundos visuais e linguísticos muito grandes, como os modelos generativos de difusão estável. O conjunto de dados do OpenAI de 5M vectores, cada um com uma dimensão de 1536, foi criado pelo VectorDBBench executando o OpenAI em dados brutos. Dado que cada elemento do vetor é do tipo FLOAT, para guardar apenas os vectores, são necessários cerca de 29 GB (5M * 1536 * 4) de memória, mais uma quantidade semelhante para guardar índices e outros metadados, num total de 58 GB de memória para testes. Ao usar a ferramenta vetor-db-benchmark, garanta um armazenamento em disco adequado para salvar os resultados.

Para testar a latência de carga, precisávamos de uma grande coleção de vectores, que o deep-image-96-angular oferece. Para testar o desempenho da geração de índices e do cálculo da similaridade, os vectores de elevada dimensão proporcionam mais stress. Para tal, escolhemos o conjunto de dados 500K de 1536 vectores de dimensão.

Ferramentas de desempenho

Abordámos formas de sobrecarregar o sistema para identificar métricas de interesse, mas vamos examinar o que está a acontecer a um nível mais baixo: qual o nível de ocupação da unidade de computação, consumo de memória, esperas em bloqueios, etc.? Estes fornecem pistas sobre o comportamento da base de dados, particularmente úteis na identificação de áreas problemáticas.

O utilitário top do Linux fornece informações sobre o desempenho do sistema. No entanto, a ferramenta perf no Linux fornece um conjunto mais profundo de informações. Para saber mais, recomendamos também a leitura dos exemplos do Linux perf e do método de análise de microarquitetura top-down da Intel. Ainda outra ferramenta é o Intel® vTune™ Profiler, que é útil ao otimizar não apenas o aplicativo, mas também o desempenho e a configuração do sistema para uma variedade de cargas de trabalho que abrangem HPC, nuvem, IoT, mídia, armazenamento e muito mais.

Otimizações do banco de dados do Milvus Vetor

Vamos analisar alguns exemplos de como tentamos melhorar o desempenho do banco de dados vetorial Milvus.

Reduzindo a sobrecarga de movimentação de memória na gravação do buffer de nó de dados

Os proxies do caminho de escrita do Milvus escrevem dados num broker de registo através de MsgStream. Os nós de dados então consomem os dados, convertendo-os e armazenando-os em segmentos. Os segmentos irão fundir os dados recém-inseridos. A lógica de mesclagem aloca um novo buffer para manter/mover os dados antigos e os novos dados a serem inseridos e, em seguida, retorna o novo buffer como dados antigos para a próxima mesclagem de dados. Isso faz com que os dados antigos fiquem sucessivamente maiores, o que, por sua vez, torna a movimentação de dados mais lenta. Os perfis de desempenho mostraram uma sobrecarga elevada para esta lógica.

Figura 4. Mesclar e mover dados no banco de dados vetorial gera uma sobrecarga de alto desempenho.

Alterámos a lógica do buffer de fusão para anexar diretamente os novos dados a inserir nos dados antigos, evitando a atribuição de um novo buffer e a movimentação dos grandes dados antigos. Os perfis de desempenho confirmam que não há sobrecarga para esta lógica. As métricas de microcódigo metric_CPU operating frequency e metric_CPU utilization indicam uma melhoria que é consistente com o facto de o sistema já não ter de esperar pelo longo movimento da memória. A latência de carga melhorou em mais de 60 por cento. A melhoria é capturada no GitHub.

Figura 5. Com menos cópias, vemos uma melhoria de desempenho de mais de 50 por cento na latência de carga.

Construção de índice invertido com sobrecarga de alocação de memória reduzida

O motor de busca Milvus, Knowhere, utiliza o algoritmo Elkan k-means para treinar dados de cluster para criar índices de ficheiros invertidos (IVF). Cada rodada de treinamento de dados define uma contagem de iteração. Quanto maior for a contagem, melhores serão os resultados do treino. No entanto, isso também implica que o algoritmo Elkan será chamado com mais frequência.

O algoritmo Elkan trata da atribuição e desalocação de memória de cada vez que é executado. Especificamente, ele aloca memória para armazenar metade do tamanho dos dados da matriz simétrica, excluindo os elementos diagonais. No Knowhere, a dimensão da matriz simétrica usada pelo algoritmo Elkan é definida como 1024, resultando em um tamanho de memória de aproximadamente 2 MB. Isto significa que, para cada ronda de treino, o Elkan aloca e desaloca repetidamente 2 MB de memória.

Os dados de perfil de desempenho indicaram uma atividade frequente de atribuição de memória de grande dimensão. De facto, desencadeou a alocação da área de memória virtual (VMA), a alocação da página física, a configuração do mapa de páginas e a atualização das estatísticas do cgroup de memória no kernel. Este padrão de grande atividade de alocação/desalocação de memória pode, em algumas situações, também agravar a fragmentação da memória. Trata-se de um imposto significativo.

A estrutura IndexFlatElkan foi especificamente concebida e construída para suportar o algoritmo Elkan. Cada processo de treinamento de dados terá uma instância IndexFlatElkan inicializada. Para atenuar o impacto no desempenho resultante da frequente atribuição e desalocação de memória no algoritmo Elkan, refactorámos a lógica do código, deslocando a gestão da memória para fora da função do algoritmo Elkan, para o processo de construção do IndexFlatElkan. Isso permite que a alocação de memória ocorra apenas uma vez durante a fase de inicialização, enquanto atende a todas as chamadas de função do algoritmo Elkan subseqüentes do processo de treinamento de dados atual e ajuda a melhorar a latência de carga em cerca de 3%. Encontre o patch do Knowhere aqui.

Aceleração da pesquisa vetorial do Redis por meio da pré-busca de software

O Redis, um popular armazenamento tradicional de dados de valor-chave na memória, começou recentemente a oferecer suporte à pesquisa vetorial. Para ir além de um típico armazenamento de valores-chave, ele oferece módulos de extensibilidade; o módulo RediSearch facilita o armazenamento e a pesquisa de vetores diretamente no Redis.

Para a pesquisa de similaridade de vectores, o Redis suporta dois algoritmos, nomeadamente força bruta e HNSW. O algoritmo HNSW é especificamente criado para localizar eficientemente os vizinhos mais próximos aproximados em espaços de alta dimensão. Utiliza uma fila de prioridades denominada candidate_set para gerir todos os candidatos a vectores para cálculo da distância.

Cada candidato a vetor inclui metadados substanciais para além dos dados do vetor. Como resultado, ao carregar um candidato da memória, ele pode causar falhas no cache de dados, o que gera atrasos no processamento. Nossa otimização introduz a pré-busca de software para carregar proativamente o próximo candidato enquanto processa o atual. Esse aprimoramento resultou em uma melhoria de 2 a 3% na taxa de transferência para pesquisas de similaridade de vetores em uma configuração Redis de instância única. O patch está em processo de upstreaming.

Mudança de comportamento padrão do GCC para evitar penalidades de código de montagem misto

Para obter o máximo desempenho, as secções de código utilizadas frequentemente são escritas à mão em assembly. No entanto, quando diferentes segmentos de código são escritos por pessoas diferentes ou em momentos diferentes, as instruções usadas podem vir de conjuntos de instruções de assembly incompatíveis, como Intel® Advanced Vetor Extensions 512 (Intel® AVX-512) e Streaming SIMD Extensions (SSE). Se não for compilado adequadamente, o código misto resulta em uma penalidade de desempenho. Saiba mais sobre a mistura de instruções Intel AVX e SSE aqui.

Pode determinar facilmente se está a utilizar código assembly de modo misto e se não compilou o código com VZEROUPPER, incorrendo na penalização do desempenho. Isso pode ser observado através de um comando perf como sudo perf stat -e 'assists.sse_avx_mix/event/event=0xc1,umask=0x10/' <workload>. Se o seu sistema operacional não tiver suporte para o evento, use cpu/event=0xc1,umask=0x10,name=assists_sse_avx_mix/.

O compilador Clang, por padrão, insere VZEROUPPER, evitando qualquer penalidade de modo misto. Mas o compilador GCC só inseriu o VZEROUPPER quando os sinalizadores do compilador -O2 ou -O3 foram especificados. Contactámos a equipa do GCC e explicámos o problema, e agora, por predefinição, eles tratam corretamente o código de montagem de modo misto.

Comece a otimizar as suas bases de dados vectoriais

As bases de dados de vectores estão a desempenhar um papel integral na GenAI e estão a crescer cada vez mais para gerar respostas de maior qualidade. No que diz respeito à otimização, as aplicações de IA não são diferentes de outras aplicações de software, na medida em que revelam os seus segredos quando se utilizam ferramentas de análise de desempenho padrão, juntamente com estruturas de benchmark e entradas de stress.

Utilizando estas ferramentas, descobrimos armadilhas de desempenho relacionadas com a atribuição desnecessária de memória, a não pré-busca de instruções e a utilização de opções incorrectas do compilador. Com base em nossas descobertas, fizemos melhorias no Milvus, Knowhere, Redis e no compilador GCC para ajudar a tornar a IA um pouco mais eficiente e sustentável. Os bancos de dados vetoriais são uma classe importante de aplicativos que merecem seus esforços de otimização. Esperamos que este artigo o ajude a começar.

Like the article? Spread the word

Continue Lendo