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

milvus-logo
LFAI

Antecedentes

  • Engineering
March 03, 2020
milvus

este artigo, discutiremos como o Milvus agenda as tarefas de consulta. Também falaremos sobre problemas, soluções e orientações futuras para a implementação do agendamento do Milvus.

Antecedentes

Sabemos, a partir de Managing Data in Massive-Scale Vetor Search Engine, que a pesquisa por semelhança de vectores é implementada pela distância entre dois vectores num espaço de elevada dimensão. O objetivo da pesquisa vetorial é encontrar K vectores que estejam mais próximos do vetor alvo.

Há muitas formas de medir a distância vetorial, como a distância euclidiana:

1-euclidean-distance.png 1-euclidean-distance.png

onde x e y são dois vectores. n é a dimensão dos vectores.

Para encontrar os K vectores mais próximos num conjunto de dados, é necessário calcular a distância euclidiana entre o vetor alvo e todos os vectores do conjunto de dados a pesquisar. Em seguida, os vectores são ordenados por distância para obter os K vectores mais próximos. O trabalho computacional é diretamente proporcional à dimensão do conjunto de dados. Quanto maior for o conjunto de dados, mais trabalho computacional é necessário para uma consulta. Uma GPU, especializada no processamento de grafos, tem muitos núcleos para fornecer a potência de computação necessária. Assim, o suporte multi-GPU também é tido em consideração durante a implementação do Milvus.

Conceitos básicos

Bloco de dados(TableFile)

Para melhorar o suporte à pesquisa de dados em grande escala, optimizámos o armazenamento de dados do Milvus. O Milvus divide os dados de uma tabela por tamanho em vários blocos de dados. Durante a pesquisa vetorial, o Milvus pesquisa vectores em cada bloco de dados e junta os resultados. Uma operação de pesquisa vetorial consiste em N operações de pesquisa vetorial independentes (N é o número de blocos de dados) e N-1 operações de fusão de resultados.

Fila de tarefas(TaskTable)

Cada recurso tem uma matriz de tarefas, que regista as tarefas pertencentes ao recurso. Cada tarefa tem diferentes estados, incluindo Início, Carregamento, Carregado, Executando e Executado. O Carregador e o Executor num dispositivo informático partilham a mesma fila de tarefas.

Programação de consultas

2-query-scheduling.png 2-query-scheduling.png

  1. Quando o servidor Milvus inicia, o Milvus lança o GpuResource correspondente através dos parâmetros gpu_resource_config no ficheiro de configuração server_config.yaml. DiskResource e CpuResource ainda não podem ser editados em server_config.yaml. GpuResource é a combinação de search_resources e build_index_resources e referido como {gpu0, gpu1} no exemplo a seguir:

3-sample-code.png 3-sample-code.png

3-example.png 3-exemplo.png

  1. O Milvus recebe um pedido. Os metadados da tabela são armazenados numa base de dados externa, que é o SQLite ou MySQl para um único anfitrião e o MySQL para um anfitrião distribuído. Depois de receber um pedido de pesquisa, o Milvus valida se a tabela existe e se a dimensão é consistente. De seguida, o Milvus lê a lista TableFile da tabela.

4-milvus-reads-tablefile-list.png 4-milvus-reads-tablefile-list.png

  1. O Milvus cria uma SearchTask. Como o cálculo de cada TableFile é realizado de forma independente, o Milvus cria uma SearchTask para cada TableFile. Como unidade básica do agendamento de tarefas, uma SearchTask contém os vectores alvo, os parâmetros de pesquisa e os nomes dos ficheiros TableFile.

5-table-file-list-task-creator.png 5-table-file-list-task-creator.png

  1. Milvus escolhe um dispositivo de computação. O dispositivo em que uma SearchTask efectua o cálculo depende do tempo de conclusão estimado para cada dispositivo. O tempo estimado de conclusão especifica o intervalo estimado entre a hora atual e a hora estimada para a conclusão do cálculo.

Por exemplo, quando um bloco de dados de uma SearchTask é carregado na 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 de conclusão estimado para a CPU é igual à soma do custo de tempo estimado da SearchTask anterior e da SearchTask atual. O tempo de conclusão estimado para uma GPU é igual à soma do tempo para os blocos de dados serem carregados na GPU e o custo de tempo estimado da SearchTask atual. O tempo de conclusão estimado para uma SearchTask num recurso é igual ao tempo médio de execução de todas as SearchTasks no recurso. O Milvus escolhe então um dispositivo com o menor tempo de conclusão estimado e atribui a SearchTask ao dispositivo.

Aqui, assumimos que o tempo de conclusão estimado para a GPU1 é mais curto.

6-GPU1-shorter-estimated-completion-time.png 6-GPU1-tempo-de-conclusão-mais-curto-estimado.png

  1. Milvus adiciona SearchTask à fila de tarefas de DiskResource.

  2. Milvus move SearchTask para a fila de tarefas de CpuResource. A thread de carregamento no CpuResource carrega cada tarefa da fila de tarefas sequencialmente. O CpuResource lê os blocos de dados correspondentes na memória da CPU.

  3. Milvus move SearchTask para GpuResource. O thread de carregamento no GpuResource copia os dados da memória da CPU para a memória da GPU. GpuResource lê os blocos de dados correspondentes na memória da GPU.

  4. O Milvus executa a SearchTask no GpuResource. Como o resultado de uma SearchTask é relativamente pequeno, o resultado é devolvido diretamente à memória da CPU.

7-scheduler.png 7-scheduler.png

  1. Milvus junta o resultado da SearchTask com o resultado total da pesquisa.

8-milvus-merges-searchtast-result.png 8-milvus-merge-searchtast-result.png

Depois de todas as SearchTasks estarem completas, o Milvus devolve o resultado completo da pesquisa ao cliente.

Construção de índices

A construção do índice é basicamente o mesmo que o processo de pesquisa, sem o processo de fusão. Não falaremos sobre isso em pormenor.

Otimização do desempenho

Cache

Como mencionado anteriormente, os blocos de dados têm de ser carregados para os dispositivos de armazenamento correspondentes, como a memória da CPU ou a memória da GPU, antes do cálculo. Para evitar o carregamento repetitivo de dados, o Milvus introduz a cache LRU (Least Recently Used). Quando a cache está cheia, os novos blocos de dados afastam os blocos de dados antigos. É possível personalizar o tamanho da cache através do ficheiro de configuração com base no tamanho atual da memória. Recomenda-se a utilização de uma cache grande para armazenar dados de pesquisa, de modo a poupar tempo de carregamento de dados e melhorar o desempenho da pesquisa.

Sobreposição de carregamento de dados e computação

A cache não pode satisfazer as nossas necessidades de melhor desempenho de pesquisa. Os dados têm de ser recarregados quando a memória é insuficiente ou o tamanho do conjunto de dados é demasiado grande. Precisamos de diminuir o efeito do carregamento de dados no desempenho da pesquisa. O carregamento de dados, quer seja do disco para a memória da CPU ou da memória da CPU para a memória da GPU, pertence às operações de IO e quase não necessita de qualquer trabalho computacional dos processadores. Assim, consideramos a possibilidade de efetuar o carregamento de dados e a computação em paralelo para uma melhor utilização dos recursos.

Dividimos a computação num bloco de dados em 3 fases (carregamento do disco para a memória da CPU, computação da CPU, fusão de resultados) ou 4 fases (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 fusão de resultados). Tomando como exemplo a computação em 3 fases, podemos lançar 3 threads responsáveis pelas 3 fases para funcionar como pipelining de instruções. Como os conjuntos de resultados são, na sua maioria, pequenos, a fusão de resultados não demora muito tempo. Em alguns casos, a sobreposição do carregamento de dados e do cálculo pode reduzir o tempo de pesquisa em 1/2.

9-sequential-overlapping-load-milvus.png 9-sequential-overlapping-load-milvus.png

Problemas e soluções

Diferentes velocidades de transmissão

Anteriormente, o Milvus utilizava a estratégia Round Robin para o agendamento de tarefas multi-GPU. Esta estratégia funcionou perfeitamente no nosso servidor de 4-GPU e o desempenho da pesquisa foi 4 vezes melhor. No entanto, para nossos hosts de 2 GPUs, o desempenho não foi 2 vezes melhor. Fizemos algumas experiências 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. Depois de consultarmos a documentação da placa principal, confirmámos que a placa principal estava ligada a uma GPU através de PCIe x16 e a outra GPU através de PCIe x4. Ou seja, essas GPUs têm velocidades de cópia diferentes. Mais tarde, adicionámos 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 várias 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 a experimentação, descobrimos algumas oportunidades de melhoria de desempenho. Por exemplo, quando o servidor recebe várias consultas para a mesma tabela, as consultas podem ser mescladas sob algumas condições. Ao utilizar a localidade dos dados, podemos melhorar o desempenho. Estas optimizações serão implementadas no nosso desenvolvimento futuro. Agora já sabemos como as consultas são agendadas e executadas para o cenário de um único anfitrião e várias GPUs. Continuaremos a introduzir mais mecanismos internos para o Milvus nos próximos artigos.

Try Managed Milvus for Free

Zilliz Cloud is hassle-free, powered by Milvus and 10x faster.

Get Started

Like the article? Spread the word

Continue Lendo