Como o Milvus agenda tarefas de consulta
Neste artigo, discutiremos como o Milvus agenda tarefas de consulta. Também falaremos sobre problemas, soluções e orientações futuras para a implementação do agendamento do Milvus.
Contexto
Sabemos, a partir de Managing Data in Massive-Scale Vector Search Engine, que a busca por similaridade vetorial é implementada pela distância entre dois vetores em um espaço de alta dimensão. O objetivo da busca vetorial é encontrar K vetores que estejam mais próximos do vetor-alvo.
Há muitas maneiras de medir a distância vetorial, como a distância euclidiana:
Distância euclidiana.
onde x e y são dois vetores. n é a dimensão dos vetores.
Para encontrar os K vetores mais próximos em um conjunto de dados, a distância euclidiana precisa ser calculada entre o vetor-alvo e todos os vetores no conjunto de dados a ser pesquisado. Em seguida, os vetores são ordenados por distância para obter os K vetores mais próximos. O trabalho computacional é diretamente proporcional ao tamanho do conjunto de dados. Quanto maior o conjunto de dados, mais trabalho computacional uma consulta exige. Uma GPU, especializada em processamento gráfico, por acaso tem muitos núcleos para fornecer a potência computacional necessária. Assim, o suporte a múltiplas GPUs também é levado em consideração durante a implementação do Milvus.
Conceitos básicos
Bloco de dados(TableFile)
Para melhorar o suporte à busca de dados em escala massiva, otimizamos o armazenamento de dados do Milvus. O Milvus divide os dados de uma tabela por tamanho em vários blocos de dados. Durante a busca vetorial, o Milvus pesquisa vetores em cada bloco de dados e mescla os resultados. Uma operação de busca vetorial consiste em N operações independentes de busca vetorial (N é o número de blocos de dados) e N-1 operações de mesclagem de resultados.
Fila de tarefas(TaskTable)
Cada Resource tem um array de tarefas, que registra tarefas pertencentes ao Resource. Cada tarefa tem estados diferentes, incluindo Start, Loading, Loaded, Executing e Executed. O Loader e o Executor em um dispositivo de computação compartilham a mesma fila de tarefas.
Agendamento de consultas
Agendamento de consultas.
- Quando o servidor Milvus inicia, o Milvus lança o GpuResource correspondente por meio dos parâmetros
gpu_resource_configno arquivo de configuraçãoserver_config.yaml. DiskResource e CpuResource ainda não podem ser editados emserver_config.yaml. GpuResource é a combinação desearch_resourcesebuild_index_resourcese é referido como{gpu0, gpu1}no exemplo a seguir:
Código de exemplo.
Exemplo.
- O Milvus recebe uma solicitação. Os metadados da tabela são armazenados em um banco de dados externo, que é SQLite ou MySQl para host único e MySQL para distribuído. Após receber uma solicitação de busca, o Milvus valida se a tabela existe e se a dimensão é consistente. Em seguida, o Milvus lê a lista TableFile da tabela.
Milvus lê lista tablefile.
- O Milvus cria uma SearchTask. Como a computação de cada TableFile é realizada independentemente, o Milvus cria uma SearchTask para cada TableFile. Como a unidade básica de agendamento de tarefas, uma SearchTask contém os vetores-alvo, parâmetros de busca e os nomes de arquivo do TableFile.
Criador de tarefas da lista de arquivos de tabela.
- O Milvus escolhe um dispositivo de computação. O dispositivo no qual uma SearchTask realiza a computação depende do tempo estimado de conclusão de cada dispositivo. O tempo estimado de conclusão especifica o intervalo estimado entre o horário atual e o horário estimado em que a computação é concluída.
Por exemplo, quando um bloco de dados de uma SearchTask é carregado para a memória da CPU, a próxima SearchTask está aguardando na fila de tarefas de computação da CPU e a fila de tarefas de computação da GPU está ociosa. O tempo estimado de conclusão para a CPU é igual à soma do custo de tempo estimado da SearchTask anterior e da SearchTask atual. O tempo estimado de conclusão para uma GPU é igual à soma do tempo para os blocos de dados serem carregados para a GPU e do custo de tempo estimado da SearchTask atual. O tempo estimado de conclusão para uma SearchTask em um Resource é igual ao tempo médio de execução de todas as SearchTasks no Resource. O Milvus então escolhe um dispositivo com o menor tempo estimado de conclusão e atribui a SearchTask ao dispositivo.
Aqui assumimos que o tempo estimado de conclusão para a GPU1 é menor.
Tempo estimado de conclusão menor da GPU1.
O Milvus adiciona a SearchTask à fila de tarefas do DiskResource.
O Milvus move a SearchTask para a fila de tarefas do CpuResource. A thread de carregamento no CpuResource carrega cada tarefa da fila de tarefas sequencialmente. O CpuResource lê os blocos de dados correspondentes para a memória da CPU.
O Milvus move a SearchTask para o GpuResource. A thread de carregamento no GpuResource copia os dados da memória da CPU para a memória da GPU. O GpuResource lê os blocos de dados correspondentes para a memória da GPU.
O Milvus executa a SearchTask no GpuResource. Como o resultado de uma SearchTask é relativamente pequeno, o resultado é retornado diretamente para a memória da CPU.
Agendador.
- O Milvus mescla o resultado da SearchTask ao resultado geral da busca.
O Milvus mescla os resultados da tarefa de busca.
Depois que todas as SearchTasks são concluídas, o Milvus retorna o resultado geral da busca ao cliente.
Construção de índice
A construção de índice é basicamente igual ao processo de busca, sem o processo de mesclagem. Não falaremos sobre isso em detalhes.
Otimização de desempenho
Cache
Como mencionado antes, os blocos de dados precisam ser carregados para dispositivos de armazenamento correspondentes, como memória da CPU ou memória da GPU, antes da computação. Para evitar o carregamento repetitivo de dados, o Milvus introduz o cache LRU (Least Recently Used). Quando o cache está cheio, novos blocos de dados removem blocos de dados antigos. Você pode personalizar o tamanho do cache pelo arquivo de configuração com base no tamanho atual da memória. Recomenda-se um cache grande para armazenar dados de busca a fim de economizar efetivamente o tempo de carregamento de dados e melhorar o desempenho da busca.
Sobreposição de carregamento de dados e computação
O cache não consegue atender às nossas necessidades de melhor desempenho de busca. Os dados precisam ser recarregados quando a memória é insuficiente ou o tamanho do conjunto de dados é grande demais. Precisamos diminuir o efeito do carregamento de dados no desempenho da busca. O carregamento de dados, seja do disco para a memória da CPU ou da memória da CPU para a memória da GPU, pertence a operações de IO e quase não exige trabalho computacional dos processadores. Portanto, consideramos realizar o carregamento de dados e a computação em paralelo para melhor uso dos recursos.
Dividimos a computação em um bloco de dados em 3 estágios (carregamento do disco para a memória da CPU, computação da CPU, mesclagem de resultados) ou 4 estágios (carregamento do disco para a memória da CPU, carregamento da memória da CPU para a memória da GPU, computação da GPU e recuperação de resultados, e mesclagem de resultados). Tomando a computação em 3 estágios como exemplo, podemos iniciar 3 threads responsáveis pelos 3 estágios para funcionar como pipelining de instruções. Como os conjuntos de resultados são, em sua maioria, pequenos, a mesclagem de resultados não leva muito tempo. Em alguns casos, a sobreposição do carregamento de dados e da computação pode reduzir o tempo de busca pela metade.
Carregamento sobreposto sequencial do Milvus.
Problemas e soluções
Diferentes velocidades de transmissão
Anteriormente, o Milvus usa a estratégia Round Robin para o agendamento de tarefas em múltiplas GPUs. Essa estratégia funcionou perfeitamente em nosso servidor com 4 GPUs e o desempenho de busca foi 4 vezes melhor. No entanto, para nossos hosts com 2 GPUs, o desempenho não foi 2 vezes melhor. Fizemos alguns experimentos e descobrimos que a velocidade de cópia de dados para uma GPU era de 11 GB/s. No entanto, para outra GPU, era de 3 GB/s. Após consultar a documentação da placa-mãe, confirmamos que a placa-mãe estava conectada a uma GPU via PCIe x16 e a outra GPU via PCIe x4. Ou seja, essas GPUs têm velocidades de cópia diferentes. Posteriormente, adicionamos o tempo de cópia para medir o dispositivo ideal para cada SearchTask.
Trabalho futuro
Ambiente de hardware com maior complexidade
Em condições reais, o ambiente de hardware pode ser mais complicado. Para ambientes de hardware com múltiplas CPUs, memória com arquitetura NUMA, NVLink e NVSwitch, a comunicação entre CPUs/GPUs traz muitas oportunidades de otimização.
Otimização de consultas
Durante os experimentos, descobrimos algumas oportunidades de melhoria de desempenho. Por exemplo, quando o servidor recebe múltiplas consultas para a mesma tabela, as consultas podem ser mescladas sob algumas condições. Ao usar a localidade dos dados, podemos melhorar o desempenho. Essas otimizações serão implementadas em nosso desenvolvimento futuro. Agora já sabemos como as consultas são agendadas e executadas para o cenário de host único com múltiplas GPUs. Continuaremos a apresentar mais mecanismos internos do Milvus nos próximos artigos.
Continue lendo

Migrating from S3 Vectors to Zilliz Cloud: Unlocking the Power of Tiered Storage
Learn how Zilliz Cloud bridges cost and performance with tiered storage and enterprise-grade features, and how to migrate data from AWS S3 Vectors to Zilliz Cloud.

Our Journey to 35K+ GitHub Stars: The Real Story of Building Milvus from Scratch
Join us in celebrating Milvus, the vector database that hit 35.5K stars on GitHub. Discover our story and how we’re making AI solutions easier for developers.

How to Use Anthropic MCP Server with Milvus
MCP + Milvus: Streamline AI agent development with standardized data access, eliminating integration hassles while enhancing context and flexibility.



