Il contesto
n questo articolo parleremo di come Milvus pianifica le attività di interrogazione. Parleremo anche di problemi, soluzioni e orientamenti futuri per l'implementazione dello scheduling di Milvus.
Il contesto
Dalla Gestione dei dati nel motore di ricerca vettoriale su larga scala sappiamo che la ricerca di similarità vettoriale è implementata dalla distanza tra due vettori nello spazio ad alta dimensione. L'obiettivo della ricerca vettoriale è trovare i K vettori più vicini al vettore di destinazione.
Esistono molti modi per misurare la distanza vettoriale, come la distanza euclidea:
1-euclidea-distanza.png
dove x e y sono due vettori. n è la dimensione dei vettori.
Per trovare i K vettori più vicini in un set di dati, è necessario calcolare la distanza euclidea tra il vettore di destinazione e tutti i vettori del set di dati da cercare. Quindi, i vettori vengono ordinati in base alla distanza per acquisire i K vettori più vicini. Il lavoro di calcolo è direttamente proporzionale alla dimensione del set di dati. Più grande è il set di dati, più lavoro di calcolo richiede una query. Una GPU, specializzata nell'elaborazione dei grafi, dispone di molti core per fornire la potenza di calcolo richiesta. Pertanto, il supporto multi-GPU viene preso in considerazione durante l'implementazione di Milvus.
Concetti di base
Blocco dati (file tabellare)
Per migliorare il supporto alla ricerca di dati su larga scala, abbiamo ottimizzato l'archiviazione dei dati di Milvus. Milvus divide i dati di una tabella per dimensione in più blocchi di dati. Durante la ricerca vettoriale, Milvus cerca i vettori in ogni blocco di dati e unisce i risultati. Un'operazione di ricerca vettoriale consiste in N operazioni indipendenti di ricerca vettoriale (N è il numero di blocchi di dati) e N-1 operazioni di unione dei risultati.
Coda dei task (tabella dei task)
Ogni risorsa ha un array di task, che registra i task appartenenti alla risorsa. Ogni task ha diversi stati, tra cui Start, Loading, Loaded, Executing ed Executed. Il Loader e l'Executor di un dispositivo informatico condividono la stessa coda di task.
Pianificazione delle query
2-query-scheduling.png
- Quando il server Milvus si avvia, Milvus lancia la GpuResource corrispondente tramite i parametri
gpu_resource_config
nel file di configurazioneserver_config.yaml
. DiskResource e CpuResource non possono ancora essere modificati inserver_config.yaml
. GpuResource è la combinazione disearch_resources
ebuild_index_resources
ed è indicata come{gpu0, gpu1}
nell'esempio seguente:
3-codice-esempio.png
3-esempio.png
- Milvus riceve una richiesta. I metadati delle tabelle sono memorizzati in un database esterno, che è SQLite o MySQl per gli host singoli e MySQL per quelli distribuiti. Dopo aver ricevuto una richiesta di ricerca, Milvus convalida se la tabella esiste e se la dimensione è coerente. Quindi, Milvus legge l'elenco TableFile della tabella.
4-milvus-reads-tablefile-list.png
- Milvus crea un task di ricerca. Poiché il calcolo di ogni TableFile viene eseguito in modo indipendente, Milvus crea un task di ricerca per ogni TableFile. Come unità di base della programmazione dei task, un task di ricerca contiene i vettori di destinazione, i parametri di ricerca e i nomi dei file di TableFile.
5-tabella-file-elenco-task-creator.png
- Milvus sceglie un dispositivo di elaborazione. Il dispositivo con cui un task di ricerca esegue i calcoli dipende dal tempo di completamento stimato per ogni dispositivo. Il tempo di completamento stimato specifica l'intervallo stimato tra l'ora corrente e l'ora prevista per il completamento del calcolo.
Ad esempio, quando un blocco di dati di un task di ricerca viene caricato nella memoria della CPU, il task di ricerca successivo è in attesa nella coda dei task di calcolo della CPU e la coda dei task di calcolo della GPU è inattiva. Il tempo di completamento stimato per la CPU è pari alla somma del costo temporale stimato del task di ricerca precedente e del task di ricerca corrente. Il tempo di completamento stimato per una GPU è uguale alla somma del tempo di caricamento dei blocchi di dati sulla GPU e del costo temporale stimato del task di ricerca corrente. Il tempo di completamento stimato per un task di ricerca in una risorsa è uguale al tempo medio di esecuzione di tutti i task di ricerca nella risorsa. Milvus sceglie quindi un dispositivo con il minor tempo di completamento stimato e assegna il SearchTask al dispositivo.
In questo caso si assume che il tempo di completamento stimato per la GPU1 sia più breve.
6-GPU1-tempo di completamento stimato più breve.png
Milvus aggiunge SearchTask alla coda di task di DiskResource.
Milvus sposta SearchTask nella coda dei task di CpuResource. Il thread di caricamento in CpuResource carica ogni task dalla coda dei task in modo sequenziale. CpuResource legge i blocchi di dati corrispondenti nella memoria della CPU.
Milvus sposta SearchTask in GpuResource. Il thread di caricamento in GpuResource copia i dati dalla memoria della CPU alla memoria della GPU. GpuResource legge i blocchi di dati corrispondenti nella memoria della GPU.
Milvus esegue SearchTask in GpuResource. Poiché il risultato di un SearchTask è relativamente piccolo, viene restituito direttamente alla memoria della CPU.
7-scheduler.png
- Milvus unisce il risultato di SearchTask all'intero risultato della ricerca.
8-milvus-merge-il-risultato-di-ricerca.png
Dopo che tutti i SearchTask sono stati completati, Milvus restituisce al client l'intero risultato della ricerca.
Costruzione dell'indice
La costruzione dell'indice è sostanzialmente uguale al processo di ricerca, senza il processo di fusione. Non ne parleremo in dettaglio.
Ottimizzazione delle prestazioni
Cache
Come già detto, i blocchi di dati devono essere caricati sui dispositivi di memorizzazione corrispondenti, come la memoria della CPU o la memoria della GPU, prima di essere calcolati. Per evitare il caricamento ripetitivo dei dati, Milvus introduce la cache LRU (Least Recently Used). Quando la cache è piena, i nuovi blocchi di dati allontanano quelli vecchi. La dimensione della cache può essere personalizzata dal file di configurazione in base alle dimensioni della memoria corrente. Si consiglia di utilizzare una cache di grandi dimensioni per memorizzare i dati di ricerca, in modo da risparmiare tempo di caricamento dei dati e migliorare le prestazioni di ricerca.
Sovrapposizione di caricamento e calcolo dei dati
La cache non può soddisfare le esigenze di migliori prestazioni di ricerca. I dati devono essere ricaricati quando la memoria è insufficiente o le dimensioni del set di dati sono troppo grandi. È necessario ridurre l'effetto del caricamento dei dati sulle prestazioni di ricerca. Il caricamento dei dati, sia che avvenga dal disco alla memoria della CPU, sia che avvenga dalla memoria della CPU alla memoria della GPU, rientra nelle operazioni di IO e non richiede quasi alcun lavoro di calcolo da parte dei processori. Pertanto, consideriamo di eseguire il caricamento dei dati e la computazione in parallelo per un migliore utilizzo delle risorse.
Dividiamo il calcolo su un blocco di dati in 3 fasi (caricamento dal disco alla memoria della CPU, calcolo della CPU, fusione dei risultati) o 4 fasi (caricamento dal disco alla memoria della CPU, caricamento dalla memoria della CPU alla memoria della GPU, calcolo e recupero dei risultati da parte della GPU e fusione dei risultati). Prendendo come esempio la computazione in 3 fasi, possiamo lanciare 3 thread responsabili delle 3 fasi per funzionare come pipelining di istruzioni. Poiché gli insiemi di risultati sono per lo più piccoli, l'unione dei risultati non richiede molto tempo. In alcuni casi, la sovrapposizione del caricamento dei dati e del calcolo può ridurre di 1/2 il tempo di ricerca.
9-sequenziale-sovrapposizione-carico-milvus.png
Problemi e soluzioni
Velocità di trasmissione diverse
In precedenza, Milvus utilizzava la strategia Round Robin per la programmazione dei task multi-GPU. Questa strategia ha funzionato perfettamente nel nostro server a 4-GPU e le prestazioni di ricerca sono state 4 volte migliori. Tuttavia, per i nostri host a 2-GPU, le prestazioni non erano 2 volte migliori. Abbiamo fatto alcuni esperimenti e abbiamo scoperto che la velocità di copia dei dati per una GPU era di 11 GB/s. Per un'altra GPU, invece, era di 3 GB/s. Dopo aver consultato la documentazione della scheda madre, abbiamo confermato che la scheda madre era collegata a una GPU tramite PCIe x16 e a un'altra GPU tramite PCIe x4. Ciò significa che queste GPU hanno velocità di copia diverse. In seguito, abbiamo aggiunto il tempo di copia per misurare il dispositivo ottimale per ogni SearchTask.
Lavoro futuro
Ambiente hardware con maggiore complessitÃ
In condizioni reali, l'ambiente hardware può essere più complicato. Per gli ambienti hardware con più CPU, memoria con architettura NUMA, NVLink e NVSwitch, la comunicazione tra CPU/GPU offre molte opportunità di ottimizzazione.
Ottimizzazione delle query
Durante la sperimentazione, abbiamo scoperto alcune opportunità di miglioramento delle prestazioni. Ad esempio, quando il server riceve più query per la stessa tabella, in alcune condizioni le query possono essere unite. Utilizzando la localizzazione dei dati, possiamo migliorare le prestazioni. Queste ottimizzazioni saranno implementate nello sviluppo futuro. Ora sappiamo già come vengono programmate ed eseguite le query per lo scenario single-host e multi-GPU. Nei prossimi articoli continueremo a introdurre altri meccanismi interni a Milvus.
- Concetti di base
- Costruzione dell'indice
- Ottimizzazione delle prestazioni
- Problemi e soluzioni
- Lavoro futuro
On This Page
Try Managed Milvus for Free
Zilliz Cloud is hassle-free, powered by Milvus and 10x faster.
Get StartedLike the article? Spread the word