Hintergrund
In diesem Artikel wird erörtert, wie Milvus die Abfrageaufgaben plant. Wir werden auch über Probleme, Lösungen und zukünftige Orientierungen für die Implementierung von Milvus Scheduling sprechen.
Hintergrund
Wir wissen aus der Verwaltung von Daten in einer massiven Vektorsuchmaschine, dass die Ähnlichkeitssuche in Vektoren durch den Abstand zwischen zwei Vektoren im hochdimensionalen Raum implementiert wird. Das Ziel der Vektorsuche ist es, K Vektoren zu finden, die dem Zielvektor am nächsten sind.
Es gibt viele Möglichkeiten, den Vektorabstand zu messen, z. B. den euklidischen Abstand:
1-euklidischer-abstand.png
wobei x und y zwei Vektoren sind. n ist die Dimension der Vektoren.
Um K nächstgelegene Vektoren in einem Datensatz zu finden, muss der euklidische Abstand zwischen dem Zielvektor und allen Vektoren im zu durchsuchenden Datensatz berechnet werden. Anschließend werden die Vektoren nach ihrem Abstand sortiert, um die K nächstgelegenen Vektoren zu ermitteln. Der Rechenaufwand steht in direktem Verhältnis zur Größe des Datensatzes. Je größer der Datensatz ist, desto mehr Rechenarbeit erfordert eine Abfrage. Ein Grafikprozessor (GPU), der auf die Verarbeitung von Graphen spezialisiert ist, verfügt zufällig über viele Kerne, um die erforderliche Rechenleistung zu erbringen. Daher wird bei der Implementierung von Milvus auch die Unterstützung mehrerer GPUs in Betracht gezogen.
Grundlegende Konzepte
Datenblock(TableFile)
Um die Unterstützung für die Suche in großen Datenmengen zu verbessern, haben wir die Datenspeicherung von Milvus optimiert. Milvus unterteilt die Daten in einer Tabelle nach Größe in mehrere Datenblöcke. Bei der Vektorsuche durchsucht Milvus die Vektoren in jedem Datenblock und führt die Ergebnisse zusammen. Ein Vektorsuchvorgang besteht aus N unabhängigen Vektorsuchvorgängen (N ist die Anzahl der Datenblöcke) und N-1 Ergebnis-Zusammenführungsvorgängen.
Aufgaben-Warteschlange(TaskTable)
Jede Ressource hat ein Aufgaben-Array, das die zur Ressource gehörenden Aufgaben aufzeichnet. Jede Aufgabe hat verschiedene Zustände, darunter Start, Laden, Geladen, Ausführen und Ausgeführt. Der Loader und der Executor in einer Recheneinheit teilen sich dieselbe Aufgabenwarteschlange.
Abfrage-Planung
2-abfrage-planung.png
- Wenn der Milvus-Server startet, startet Milvus die entsprechende GpuResource über die
gpu_resource_config
Parameter in derserver_config.yaml
Konfigurationsdatei. DiskResource und CpuResource können inserver_config.yaml
noch nicht bearbeitet werden. GpuResource ist die Kombination aussearch_resources
undbuild_index_resources
und wird im folgenden Beispiel als{gpu0, gpu1}
bezeichnet:
3-Beispiel-code.png
3-beispiel.png
- Milvus empfängt eine Anfrage. Die Tabellen-Metadaten werden in einer externen Datenbank gespeichert, die SQLite oder MySQl für Single-Host und MySQL für verteilte Systeme ist. Nachdem Milvus eine Suchanfrage erhalten hat, prüft es, ob die Tabelle existiert und die Dimension konsistent ist. Dann liest Milvus die TableFile-Liste der Tabelle.
4-milvus-liest-tabelle-datei-liste.png
- Milvus erstellt eine SearchTask. Da die Berechnung jeder TableFile unabhängig voneinander durchgeführt wird, erstellt Milvus für jede TableFile eine SearchTask. Als Grundeinheit der Aufgabenplanung enthält eine SearchTask die Zielvektoren, Suchparameter und die Dateinamen der TableFile.
5-table-file-list-task-creator.png
- Milvus wählt ein Rechengerät aus. Das Gerät, auf dem eine SearchTask Berechnungen durchführt, hängt von der geschätzten Fertigstellungszeit für jedes Gerät ab. Die geschätzte Fertigstellungszeit gibt das geschätzte Intervall zwischen dem aktuellen Zeitpunkt und dem geschätzten Zeitpunkt des Abschlusses der Berechnung an.
Wenn beispielsweise ein Datenblock einer SearchTask in den CPU-Speicher geladen wird, wartet die nächste SearchTask in der Warteschlange für CPU-Berechnungsaufgaben und die Warteschlange für GPU-Berechnungsaufgaben ist inaktiv. Die geschätzte Fertigstellungszeit für die CPU ist gleich der Summe der geschätzten Zeitkosten der vorherigen SearchTask und der aktuellen SearchTask. Die geschätzte Fertigstellungszeit für einen Grafikprozessor ist gleich der Summe aus der Zeit für das Laden von Datenblöcken in den Grafikprozessor und den geschätzten Zeitkosten der aktuellen SearchTask. Die geschätzte Fertigstellungszeit für eine SearchTask in einer Ressource ist gleich der durchschnittlichen Ausführungszeit aller SearchTasks in der Ressource. Milvus wählt dann ein Gerät mit der geringsten geschätzten Fertigstellungszeit und weist die Suchaufgabe dem Gerät zu.
Hier nehmen wir an, dass die geschätzte Fertigstellungszeit für GPU1 kürzer ist.
6-GPU1-kürzere-geschätzte-Fertigstellungszeit.png
Milvus fügt SearchTask in die Task-Warteschlange von DiskResource ein.
Milvus verschiebt SearchTask in die Task-Warteschlange von CpuResource. Der Lade-Thread in CpuResource lädt jede Aufgabe der Reihe nach aus der Aufgaben-Warteschlange. CpuResource liest die entsprechenden Datenblöcke in den CPU-Speicher.
Milvus verschiebt SearchTask nach GpuResource. Der Lade-Thread in GpuResource kopiert Daten aus dem CPU-Speicher in den GPU-Speicher. GpuResource liest die entsprechenden Datenblöcke in den GPU-Speicher.
Milvus führt SearchTask in GpuResource aus. Da das Ergebnis einer SearchTask relativ klein ist, wird das Ergebnis direkt in den CPU-Speicher zurückgegeben.
7-scheduler.png
- Milvus fügt das Ergebnis von SearchTask zum gesamten Suchergebnis zusammen.
8-milvus-merges-searchtast-result.png
Nachdem alle SearchTasks abgeschlossen sind, gibt Milvus das gesamte Suchergebnis an den Client zurück.
Indexerstellung
Der Indexaufbau ist im Grunde dasselbe wie der Suchprozess ohne den Zusammenführungsprozess. Wir werden hier nicht im Detail darauf eingehen.
Optimierung der Leistung
Cache
Wie bereits erwähnt, müssen Datenblöcke vor der Berechnung in die entsprechenden Speichergeräte wie den CPU-Speicher oder den GPU-Speicher geladen werden. Um das wiederholte Laden von Daten zu vermeiden, führt Milvus den LRU-Cache (Least Recently Used) ein. Wenn der Cache voll ist, verdrängen neue Datenblöcke alte Datenblöcke. Sie können die Cache-Größe in der Konfigurationsdatei auf der Grundlage der aktuellen Speichergröße anpassen. Ein großer Cache zum Speichern von Suchdaten wird empfohlen, um effektiv Datenladezeit zu sparen und die Suchleistung zu verbessern.
Überschneidungen beim Laden und Berechnen von Daten
Der Cache kann unsere Anforderungen an eine bessere Suchleistung nicht erfüllen. Die Daten müssen neu geladen werden, wenn der Speicher nicht ausreicht oder der Datensatz zu groß ist. Wir müssen die Auswirkungen des Datenladens auf die Suchleistung verringern. Das Laden von Daten, sei es von der Festplatte in den CPU-Speicher oder vom CPU-Speicher in den GPU-Speicher, gehört zu den IO-Operationen und erfordert kaum Rechenarbeit von den Prozessoren. Daher ziehen wir in Betracht, das Laden von Daten und die Berechnung parallel durchzuführen, um die Ressourcen besser zu nutzen.
Wir unterteilen die Berechnung eines Datenblocks in drei Stufen (Laden von der Festplatte in den CPU-Speicher, CPU-Berechnung, Zusammenführung der Ergebnisse) oder in vier Stufen (Laden von der Festplatte in den CPU-Speicher, Laden vom CPU-Speicher in den GPU-Speicher, GPU-Berechnung und Ergebnisabruf sowie Zusammenführung der Ergebnisse). Nehmen wir als Beispiel eine 3-stufige Berechnung, so können wir 3 Threads starten, die für die 3 Stufen verantwortlich sind, um als Befehlspipelining zu funktionieren. Da die Ergebnismengen meist klein sind, nimmt die Zusammenführung der Ergebnisse nicht viel Zeit in Anspruch. In einigen Fällen kann die Überlappung von Datenladen und Berechnung die Suchzeit um die Hälfte reduzieren.
9-sequenzielles-überlappendes-laden-milvus.png
Probleme und Lösungen
Unterschiedliche Übertragungsgeschwindigkeiten
Bisher verwendet Milvus die Round-Robin-Strategie für die Planung von Multi-GPU-Aufgaben. Diese Strategie funktionierte auf unserem 4-GPU-Server perfekt und die Suchleistung war viermal besser. Bei unseren 2-GPU-Hosts war die Leistung jedoch nicht um den Faktor 2 besser. Wir haben einige Experimente durchgeführt und festgestellt, dass die Datenkopiergeschwindigkeit für eine GPU 11 GB/s betrug. Bei einer anderen GPU waren es jedoch nur 3 GB/s. Nach einem Blick in die Mainboard-Dokumentation bestätigten wir, dass das Mainboard mit einer GPU über PCIe x16 und einer anderen GPU über PCIe x4 verbunden war. Das bedeutet, dass diese GPUs unterschiedliche Kopiergeschwindigkeiten haben. Später fügten wir die Kopierzeit hinzu, um das optimale Gerät für jede SearchTask zu messen.
Zukünftige Arbeiten
Hardwareumgebung mit erhöhter Komplexität
Unter realen Bedingungen kann die Hardwareumgebung komplizierter sein. Bei Hardwareumgebungen mit mehreren CPUs, Speicher mit NUMA-Architektur, NVLink und NVSwitch bietet die Kommunikation zwischen CPUs/GPUs viele Möglichkeiten zur Optimierung.
Abfrageoptimierung
Bei unseren Experimenten haben wir einige Möglichkeiten zur Leistungsverbesserung entdeckt. Wenn der Server zum Beispiel mehrere Abfragen für dieselbe Tabelle erhält, können die Abfragen unter bestimmten Bedingungen zusammengeführt werden. Durch die Nutzung der Datenlokalität können wir die Leistung verbessern. Diese Optimierungen werden in unserer zukünftigen Entwicklung implementiert. Jetzt wissen wir bereits, wie Abfragen für das Single-Host- und Multi-GPU-Szenario geplant und durchgeführt werden. Wir werden in den kommenden Artikeln weitere innere Mechanismen für Milvus vorstellen.
- Grundlegende Konzepte
- Indexerstellung
- Optimierung der Leistung
- Probleme und Lösungen
- Zukünftige Arbeiten
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