Artikel

Geringere Latenz und höhere Durchsatzrate mit Multi-Node DeepSeek-Bereitstellung

Leuchtender Wal, Bezug auf DeepSeek

In den meisten Systemen stehen Latenz und Durchsatz oft im Widerspruch zueinander und erfordern während des Designs und der Implementierung Kompromisse. Zum Beispiel können in dichten großen Sprachmodellen eine Erhöhung der Batch-Größe den Durchsatz verbessern, gleichzeitig aber auch die Latenz erhöhen; eine Erhöhung der Tensor-Parallelität innerhalb einer einzelnen Maschine kann die Latenz reduzieren, verringert jedoch die Anzahl der Replikate und führt zu einem niedrigeren Durchsatz.

Modelle mit Mischungen von Experten (MoE) wie DeepSeek-V3/R1 haben kürzlich ausgezeichnete Modellfähigkeiten und operationale Effizienz demonstriert. Beispielsweise hat das DeepSeek-V3/R1-Modell insgesamt 671B Parameter, jedoch verwendet jedes Token während der Inferenz nur 37B Parameter. Diese Modellarchitektur stellt sowohl Herausforderungen als auch Chancen für Inferenzsysteme dar.

Dieser Artikel zeigt, dass MoE-Modelle wie DeepSeek-V3/R1 im Gegensatz zu herkömmlichen Systemen gleichzeitig einen höheren Durchsatz und geringere Latenz erreichen können, wenn mehr GPUs in Multi-Node-Implementierungen genutzt werden in den meisten Szenarien.


Bereitstellungsarchitekturen

Aufgrund der großen Anzahl kleiner Experten im Modell müssen Implementierungen über mehrere Geräte verteilt werden. Wir haben sowohl Single-Node-Implementierungen auf einem einzelnen Knoten mit 8xH200-GPUs als auch Multi-Node-Implementierungen auf 8xH100-GPUs in Betracht gezogen.

Beide Bereitstellungsarchitekturen nutzen Datenparallelität, die durch unseren hausinternen Anfragenscheduler orchestriert wird. Die Implementierung der Datenparallelität umfasst das Starten mehrerer Instanzen der Inferenz-Engine, die jeweils unabhängig arbeiten, um Anfragen zu bedienen und aufrechtzuerhalten. Der Scheduler, der über GRPC mit der Engine interagiert, ist dafür verantwortlich, Anfragen möglichst gleichmäßig zu verteilen und gleichzeitig die Wiederverwendung von KV zu erleichtern, indem er Anfragen mit teilweise übereinstimmenden Präfixen an die Server mit dem Cache sendet. Engine-Instanzen erstrecken sich nicht über mehrere Knoten. Sie können optional Tensor-Parallelität verwenden, um Aufmerksamkeit über mehrere Geräte zu verteilen. Die Instanzen sind im Single-Node-Fall über NVLink oder im Multi-Node-Fall über InfiniBand miteinander verbunden und verteilen und sammeln Experten.

Die Single-Node-Bereitstellungskonfiguration bietet bei kleinen Batch-Größen eine überlegene Latenz; jedoch verschlechtert sich die Leistung bei erhöhten Lastbedingungen schnell.

Um die Serving-Engine zu implementieren, starten wir pro Knoten einen Pod, der mehrere Engine-Instanzen hostet. PyTorch ist dafür verantwortlich, die verteilte Kommunikation einzurichten und die NVSHMEM-Initialisierung auszuhandeln. Für die Kommunikation verlassen wir uns auf benutzerdefinierte CUDA-Kernels, die in einem früheren Blogbeitrag beschrieben wurden. Die Implementierung der beiden Bereitstellungen ist praktisch identisch, wobei das Modell die korrekten Kerne auswählt, die basierend auf dem Stoff implementiert werden, der die Expertenparallelität implementiert.


Parallelisierungstechniken

Bevor wir in unsere Leistungsvergleiche eintauchen, ist es wichtig, die wichtigsten Parallelisierungsstrategien zu verstehen, die die Implementierung massiver MoE-Modelle wie DeepSeek-V3/R1 ermöglichen.

Tensor-Parallelität

In LLM-Inferenz wird die Tensor-Parallelität (TP) typischerweise verwendet, um den Speicherverbrauch und die Berechnung pro GPU zu reduzieren und somit die Latenz zu verringern. Normalerweise können wir lineare Projektionen in Aufmerksamkeits- und MLP-Schichten entlang der Zeilen- oder Spaltenrichtungen aufteilen und Aufmerksamkeitsoperationen entlang der Aufmerksamkeitskopfdimension aufteilen.

Mit TP hat die Llama-3-Architektur keine doppelte Berechnung für lineare Projektionen und Aufmerksamkeitsoperationen über GPUs, was eine ideale Shard-Methode darstellt. Bei DeepSeek-V3/R1-Modellen kann jedoch TP dies nicht erreichen.

DeepSeek-V3/R1-Modelle verwenden Multi-Latent Attention (MLA). Eine MLA-Schicht verwendet zuerst eine lineare Projektion kv_a_proj, um den latenten Vektor zu berechnen, und dann eine weitere lineare Projektion kv_b_proj, um ihn in den Raum jeder Aufmerksamkeitskopf zu transformieren. Da alle Aufmerksamkeitsköpfe denselben latenten Vektor teilen, kann TP den latenten Vektor nicht aufteilen, sodass alle TP-Ränge die Parameter und die Berechnung von kv_a_proj und kv_b_proj replizieren müssen. Da die MLA-Schicht den latenten Vektor im KV-Cache speichert, speichert jeder TP-Rang eine identische Kopie des KV-Caches.

Trotz einer gewissen Duplizierung in MLA bietet die Tensor-Parallelität immer noch eine teilweise Reduzierung des Berechnungsbedarfs, was sie für Szenarien wertvoll macht, die hohe Ausgabegeschwindigkeiten erfordern.

Expertenparallelität

DeepSeek-V3/R1-Modelle ersetzen MLP-Schichten durch MoE-Schichten. Eine MoE-Schicht hat 256 geroutete Experten und einen gemeinsamen Experten. Jedes Token wird zur Berechnung an 8 verschiedene geroutete Experten weitergeleitet, und die Ergebnisse werden gewichtet zusammengefasst. Jedes Token wird auch im gemeinsamen Experten berechnet, und das Ergebnis wird den Ergebnissen der gerouteten Experten hinzugefügt.

Expertenparallelität (EP) dient als typische Sharding-Methoden für MoE-Schichten, wobei jede GPU 256 / EP geroutete Experten verwaltet, während eine Kopie des gemeinsamen Experten beibehalten wird. Im Vergleich zu TP besteht der Vorteil von EP darin, dass es die Berechnung über mehr GPUs verteilen kann, wodurch die Berechnungs- und Speichernutzung pro GPU reduziert wird.

Vor der Durchführung der Expertenberechnung müssen alle GPUs eine AllToAll-Kommunikation durchführen, um Tokens zu den GPUs zu senden, auf denen die entsprechenden Experten sich befinden; nach der Expertenberechnung wird eine weitere AllToAll-Kommunikation benötigt, um Berechnungsergebnisse von verschiedenen GPUs zu sammeln und eine gewichtete Summation zu durchführen. Wir haben eine optimierte Version dieser beiden AllToAll-Kommunikations-Kerne, Dispatch und Combine, mithilfe von NVSHMEM implementiert. In einem vorherigen Blogbeitrag haben wir die Implementierung detaillierter beschrieben, und unsere Kerne wurden auf GitHub open-sourced.

Datenparallelität

Mit EP können wir die MoE-Berechnung über 128 oder noch mehr GPUs verteilen. Die MLA-Berechnung kann jedoch mit EP nicht partitioniert werden. Zu diesem Zeitpunkt können wir Datenparallelität (DP) einführen. Jede DP-Gruppe hat eine vollständige Kopie der MLA-Schicht. Jede DP-Gruppe akzeptiert unterschiedliche Eingaben und führt die MLA-Schichtberechnung unabhängig aus.

Die DP und TP der MLA-Schicht können kombiniert werden, wobei eine DP-Gruppe in mehrere TP-Ränge aufgeteilt wird. Die EP der MoE-Schicht kann mit MLA-Schicht-DP/TP kombiniert werden. EP = DP * TP. Zum Beispiel bedeutet EP128 DP32 TP4 auf 16 Maschinen, dass geroutete Experten über 128 GPUs verteilt werden, wobei alle 4 GPUs eine DP-Gruppe bilden, für insgesamt 32 unabhängige DP-Gruppen.


Single-Node vs Multi-Node

Die 671B-Parameter von DeepSeek übersteigen die Speicherkapazität einer einzelnen 8-GPU H100-Maschine (80 GB * 8), aber eine einzelne 8-GPU H200-Maschine kann das gesamte Modell vollständig aufnehmen (141 GB * 8). Mit der EP8 DP8 TP1-Konfiguration verbraucht das Modell etwa 100 GB Speicher pro GPU, sodass etwa 40 GB für den KV-Cache und andere Zwischenergebnisse übrig bleiben. Ein Token belegt 70.272 Bytes des KV-Caches. Wenn angenommen wird, dass jede Anfrage 5.000 Tokens hat, fasst jede GPU etwa 100 Anfragen.

Wir wollten die Leistungsunterschiede zwischen Single-Node- und Multi-Node-Implementierungen unter verschiedenen Konfigurationen verstehen. Wir verwendeten eine H200-Maschine für die Single-Node-Implementierung und bis zu 16 H100-Maschinen für Multi-Node-Implementierungen. Für jede Implementierungsumgebung verwendeten wir Kombinationen von TP 1, 2, 4, 8 und Batch-Größen pro GPU von 1, 2, 4, 8, 16, 32, 64, 128. Wir nahmen an, dass jede Anfrage eine KV-Cache-Länge von 5.000 Tokens hatte. Wir nahmen auch Multi-Token-Vorhersage (MTP) an, welche 1 zusätzliches Token vorhersagt (d. h., die Abfragelänge jeder Anfrage beträgt 2), und nahmen konservativ eine Akzeptanzrate von 60% an. Die folgende Abbildung zeigt den Durchsatz und die Ausgabegeschwindigkeit für verschiedene Konfigurationen.

Die horizontale Achse stellt die Ausgabegeschwindigkeit pro Anfrage in Tokens/s dar. Die vertikale Achse verwendet eine logarithmische Skala, um den Durchsatz pro Maschine in Tokens/s zu zeigen. Wir markierten die Pareto-Frontier für jede EP-Konfiguration mit unterschiedlich farbigen Linien.

In Szenarien mit extrem hohen Ausgabegeschwindigkeitsanforderungen kann die Verwendung von Single-Node EP8 DP1 TP8 mit einer Batch-Größe von 1 eine Ausgabegeschwindigkeit von über 100 Tokens/s erreichen, aber der Durchsatz ist extrem niedrig, was der Ausgabegeschwindigkeit entspricht. In diesem Szenario hat das gesamte Batch nur 2 Tokens, die maximal an 2*8=16 Experten verteilt werden können, wodurch insgesamt maximal 57B Parameter aktiviert werden.

Im Bereich der Ausgabegeschwindigkeit von 80-40 Tokens/s verringert sich die Ausgabegeschwindigkeit erheblich, während der Durchsatz steigt. Im Gegensatz dazu hat EP128 etwa 5x höheren Durchsatz als die Single-Node-Implementierung bei gleicher Ausgabegeschwindigkeit.

Dieses Phänomen kann durch die Untersuchung des Verhaltens von Single-Node-Implementierungen erklärt werden: Eine Erhöhung der Batch-Größe korreliert direkt mit einer Zunahme der aktivierten Experten. Wenn die Batch-Größe 1 beträgt, liegt die durchschnittliche Anzahl der aktivierten Experten pro GPU bei 2 * 8 / 8 = 2. Bei einer ausreichend großen Batch-Größe sind alle Experten aktiviert, was bedeutet, dass jede GPU 256 / 8 = 32 Experten aktiviert. Mehr Experten zu aktivieren bedeutet, dass die GPU mehr Parameter aus dem Speicher lesen muss, was den Speicher-Bandbreitendruck signifikant erhöht. Da die Decodierungsphase großer Sprachmodelle bereits durch die Speicherbandbreite und nicht durch die Rechenleistung eingeschränkt ist, verringert eine Erhöhung der Batch-Größe bei Single-Node-Implementierungen die Ausgabegeschwindigkeit erheblich.

Der Vergleich der vier Multi-Node-Implementierungskonfigurationen (EP16, EP32, EP64 und EP128) zeigt, dass höhere EP-Werte die Pareto-Frontier zu gleichzeitigen Verbesserungen von Durchsatz und Ausgabegeschwindigkeit verschieben.

Die Verwendung einer höheren EP-Nummer bedeutet, dass jeder GPU weniger Experten zugeordnet werden. Zum Beispiel bedeutet EP128, dass jede GPU für 256 / 128 = 2 Experten verantwortlich ist, sodass der Speicherbandbreitendruck signifikant reduziert wird. Mit anderen Worten, indem wir eine größere EP-Nummer verwenden, gewinnen wir effektiv mehr Speicherbandbreite. Wenn die pro-GPU-Batch-Größe weniger als 64 beträgt, beeinflusst die Erhöhung der Batch-Größe die Geschwindigkeit der Expertenberechnung nicht signifikant, da die Erhöhung der Eingangsanzahl den Speicherbandbreitendruck nicht erheblich erhöht. Daher beobachten wir, dass der Anstieg der Batch-Größe beim Einsatz von EP128 die Ausgabegeschwindigkeit nicht so stark beeinflusst.

Interessanterweise stellten wir bei größerer Batch-Größe (64 Anfragen pro GPU) ein neues Phänomen fest: Der Durchsatz der Single-Node-Implementierung ist leicht höher als der der Multi-Node-Implementierung. Ein Teil des Grundes ist, dass die Intra-Node-NVLink eine höhere Bandbreite hat als das Inter-Node-InfiniBand. Ein weiterer Teil liegt an den Limitationen in unserer Implementierung. Wir werden dieses Phänomen später genauer analysieren.

Aufgrund von Speicherkapazitätsbeschränkungen kann die EP8 DP8 TP1-Konfiguration keine Batch-Größe von 128 pro GPU erreichen, sodass die Multi-Node-Implementierung in Szenarien, die einen höheren Durchsatz anstreben, immer noch die bessere Wahl ist.


Überlappung von Berechnungen und Kommunikation

Wie in der kurzen Einführung zur Expertenparallelität erwähnt, sind GPUs während der MoE-Schichtkommunikation untätig. Um Verschwendung zu reduzieren und die Latenz zu verringern, müssen wir unabhängige Berechnungsaufgaben finden, um diese Leerlaufzeiten zu füllen.

Der obere Teil der obigen Abbildung zeigt den Berechnungsablauf einer Schicht. MoE-Berechnung hängt von Dispatch ab, und die nächste Schichtberechnung hängt von dem Ergebnis des Combine ab.

Wir platzieren den gemeinsamen Experten auf jeder GPU. Auf diese Weise erfordert die Berechnung des gemeinsamen Experten keine AllToAll-Kommunikation. Daher können wir die Berechnung des gemeinsamen Experten sofort nach dem Dispatch-Senden durchführen und dann auf den Abschluss des Dispatch-Empfangs warten. Wir nennen dieses Überlappungsschema "Dispatch Overlap".

Dispatch Overlap bietet eine unkomplizierte Implementierung und breite Anwendbarkeit. Diese Technik verbirgt den Berechnungszeitraum des gemeinsamen Experten über alle EP-Größen und Batch-Größen hinweg.

Um die Überlappung von Berechnungen und Kommunikation weiter zu erhöhen, nutzten wir das im technischen Bericht von DeepSeek erwähnte Micro-Batching, um die Datenabhängigkeit zu durchbrechen. Wie im unteren Teil der Abbildung gezeigt, haben wir die Berechnung einer Transformator-Schicht in 5 Stufen unterteilt:

  • Stufe 1: InputNorm, QKVProj, AppendKV, BMM

  • Stufe 2: BMM, Attn, OProj, PostNorm, Gate

  • Stufe 3: Dispatch Send, Shared Expert

  • Stufe 4: Dispatch Recv, MoE, Combine Send

  • Stufe 5: Combine Recv

In den ersten 3 dichten Transformator-Schichten verwenden wir das gesamte Batch. In den folgenden 58 MoE-Transformator-Schichten teilen wir das Batch gleichmäßig in zwei Mikro-Batches. Die beiden Mikro-Batches werden abwechselnd ausgeführt, um 3 Stufen versetzt. Da zwischen diesen beiden Mikro-Batches keine Datenabhängigkeit besteht, können wir nach dem Dispatch-Senden und nach dem Combine-Senden zur Berechnung des anderen Mikro-Batches wechseln.


Latenzzerlegung

Als nächstes vergleichen wir die Auswirkungen der Überlappung durch ein Experiment sowie die Leistungsunterschiede zwischen Single-Node-Implementierungen EP8 und Multi-Node-Implementierungen EP128. Zur Erleichterung des Vergleichs verwendeten wir H100-GPUs für das folgende Experiment. Wir verwendeten TP1, eine Batch-Größe von 128 pro GPU, eine Abfragelänge von 2 pro Anfrage und eine KV-Cache-Länge von 5000.

Die obige Abbildung zeigt die Gesamtzeit, die für eine MoE-Transformator-Schicht aufgewendet wurde, und die Latenzproportion verschiedener Kerneltypen. Abgesehen von Dispatch, Combine und GroupGEMM sollte die Ausführungszeit anderer Kernel in den EP8-, EP128-NoOverlap- und EP128-DispatchOverlap-Serien gleich sein, da die Batch-Größe gleich ist.

Überlappung

Lasst uns zunächst die Auswirkungen der drei Überlappungsmethoden vergleichen. NoOverlap benötigte insgesamt 2667µs, DispatchOverlap benötigte 2651µs, spart 16µs oder nur 0,6%. MicroBatch zeigte eine sehr signifikante Verbesserung und benötigte 1896µs, was einer Geschwindigkeitsverbesserung von 29% entspricht. Sowohl die Dispatch- als auch die Combine-Zeit wurden erheblich reduziert. Dispatch verringerte sich von 593µs auf 367µs und Combine von 1012µs auf 237µs.

Beachten Sie, dass durch das Teilen einer Batch-Größe von 128 in zwei Batches von 64 die Gesamtausführungszeit für Berechnungskerne verlängert wird. Daher verringerte sich die Zeit für die Kommunikation um 1001µs, aber die Gesamtzeit verringerte sich nur um 771µs. Wir werden den Grund im Folgenden Abschnitt mit dem Roofline-Modell erklären.

Aus diesem Grund verbessert das Micro-Batching nicht immer die Leistung.

Die obige Abbildung zeigt die Leistungsverbesserung von Microbatch im Vergleich zu DispatchOverlap für Batch-Größen 4-128. Wenn die Batch-Größe kleiner als 32 ist, verringert Microbatch die Leistung um 5%-40%. Wenn die Batch-Größe größer oder gleich 32 ist, kann Microbatch die Leistung um 10%-35% verbessern.

EP8 vs EP128

Kehren wir zur vorherigen Abbildung zurück und vergleichen EP8 und EP128 Microbatch. EP8 benötigte insgesamt 1802µs, etwas weniger als die 1896µs von EP128. Neben der durch Microbatching verursachten erhöhten Kernel-Ausführungszeit, die oben erwähnt wurde, liegen die Hauptunterschiede in GroupGEMM, das für die MoE-Berechnung verwendet wird, und den beiden Kommunikations-Kernen, Dispatch und Combine.

EP8's GroupGEMM benötigte 555µs, während EP128's GroupGEMM 270µs benötigte, was einer Reduzierung um die Hälfte entspricht. Dies ist der Kernvorteil der Multi-Node-Bereitstellung.

Leider erhöhte sich die Zeit für die Kommunikation um 213µs, was den Vorteil von GroupGEMM erheblich ausglich. In separaten Leistungstests unserer Kommunikationskerne stellten wir fest, dass sie nur die Hälfte der InfiniBand-Bandbreite erreichen. Wir werden unsere Kommunikationskerne weiterhin optimieren.

Ein weiterer Kernel, der erheblich hinterherhinkt, ist GEMM. Das Microbatching erhöhte GEMM um 95µs. Wir werden GEMM im Roofline-Abschnitt weiter unten ausführlicher analysieren. Wir glauben, dass die aktuelle GEMM-Implementierung noch nicht die optimale Leistung erreicht hat.

Dachmodell (Roofline)

Das Roofline-Modell ist ein gutes Instrument zur Analyse der Kernel-Leistung. Die horizontale Achse ist die Arithmetische Intensität, das Verhältnis von FLOP zu Speicher-E/A-Bytes. Der Wert auf der horizontalen Achse kann direkt aus der Semantik des Kerns berechnet werden. Die vertikale Achse repräsentiert die erreichte Leistung, berechnet durch Teilen von FLOP durch die Benchmark-Latenz.

Die theoretische Obergrenze der Kernleistung wird direkt durch die Spezifikationen der GPU bestimmt. Die FP8-Spitzenleistung der H100 beträgt 1979 TFLOP/s, dargestellt als horizontale Linie im Roofline-Modell. Die Speicherbandbreite der H100 beträgt 3,35 TB/s, dargestellt als die Neigung einer Linie, die durch den Ursprung geht. Die beiden Linien geben die Leistungsgrenzen für berechnungsgebundene bzw. speichergebundene Kerne an.

Im Folgenden diskutieren wir die Leistung der GroupGEMM- und GEMM-Kerne.

GroupGEMM

Der GroupGEMM-Kern im MoE führt die folgende Berechnung aus: Es gibt insgesamt g Gruppen, die i-te Gruppe hat m_i Tokens, die eine Matrixmultiplikation von [m_i, k] x [k, n] -> [m_i, n] durchführen. In Leistungstests gehen wir davon aus, dass die Anzahl der Tokens in jeder Gruppe gleich ist, bezeichnet als m_i = m. Dann ist die FLOP-Anzahl für GroupGEMM 2 * g * m * k * n, und die Speicher-E/A-Bytes ist g * (m * k + n * k + m * n).

Im DeepSeek-V3/R1-Modell gibt es 256 Experten, und jedes Token wird zur Berechnung an 8 Experten weitergeleitet. Angenommen, eine Batch-Größe von 128, Abfragelänge von 2, verwendet die EP128 DP128-Konfiguration, die durchschnittliche Anzahl von Tokens, die jeder Experte erhält (d. h. m), beträgt 128 * 2 * 8 * 128 / 256 = 1024. Ebenso können wir m für andere Konfigurationen und Batch-Größen berechnen.

Wir haben für Leistungstests die GroupGEMM-Implementierung von DeepGEMM verwendet. Die Testpunkte deckten Kombinationen der EP8-, EP16-, EP32-, EP64-, EP128-Konfigurationen mit TP1 und Batch-Größen 1-128 ab.

Die obige Abbildung zeigt das Roofline-Modell für GroupGEMM unter verschiedenen EP-Konfigurationen. Unterschiedliche EP entspricht unterschiedlichen Gruppenzahlen. Die Abbildung zeigt beinahe überlappende Leistungslinien, was darauf hinweist, dass die GroupGEMM-Leistung hauptsächlich durch die Gesamtzahl der Tokens (dargestellt als g * m) bestimmt wird.

Die Sterne markieren die Datenpunkte, die einer Batch-Größe von 128 pro GPU für jede EP-Konfiguration entsprechen. Durch den Vergleich dieser mit Stern markierten Datenpunkte sehen wir, dass, wenn EP steigt (und DP synchron zunimmt), die Anzahl der Tokens pro Experte m ebenfalls zunimmt. Bei EP8, m=128, während bei EP128, m=2048.

Mit steigendem m steigt auch die Arithmetische Intensität. In den meisten Konfigurationen wird die Leistung von GroupGEMM durch die Speicherbandbreite begrenzt, daher verbessert die Erhöhung von m die Leistung.

GEMM

Der GEMM-Kern entspricht linearen Projektionen im Modell, wie Q/K/V/O-Projektionen. Für eine Matrixmultiplikation von [m, k] x [k, n] -> [m, n] beträgt die FLOP-Anzahl 2 * m * k * n, und die Speicher-E/A-Bytes beträgt m * k + n * k + m * n. Wir können auch die Latenz für Batch-Größen 1-128 testen.

Die abgebildete Figur zeigt das Roofline-Modell für GEMM unter verschiedenen EP-Konfigurationen. Wir können sehen, dass die GEMM-Leistung durch die Speicherbandbreite begrenzt wird. Mit zunehmender Batch-Größe steigt auch die Arithmetische Intensität, was die Leistung verbessert.

Microbatch

Bei Verwendung des Microbatchings teilen wir die Batch gleichmäßig in zwei Teile. Aus den beiden oben gezeigten Abbildungen geht hervor, dass, wenn m zu m/2 werden, die Effizienz der Matrix-Multiplikation abnimmt. Daher dauert die Ausführung von zwei Matrix-Multiplikationen der Größe m/2 länger als die Ausführung einer Matrix-Multiplikation der Größe m.

Multi-Token Predictions

In diesem Artikel haben wir angenommen, dass Multi-Token-Predictions (MTP) für spekulatives Decodieren verwendet werden. MTP ändert die Abfragelänge pro Anfrage von 1 auf 2. Für Matrix-Multiplikationen entspricht dies der Änderung von m auf m * 2, wodurch die Effizienz der Matrix-Multiplikation erhöht wird. Andererseits, wenn wir das Roofline-Modell für MLA zeichnen würden, würden wir feststellen, dass die Erhöhung der Abfragelänge die Effizienz des MLA-Kerns erheblich erhöht.

Das Verwenden von MTP spielt daher eine wichtige Rolle bei der Modelleffizienz.

Implementierung & Optimierungen

In diesem Abschnitt werden wir einige Implementierungs- und Optimierungsdetails unseres DeepSeek-V3/R1-Modells einführen.

Quantifizierung

DeepSeek-V3/R1 wurde nativ auf FP8 unter Verwendung eines Quantisierungsschemas pro Block trainiert, wobei Gewichte statisch und Aktivierungen dynamisch quantisiert werden. Statt eines statischen Skalierungsfaktors pro Kanal oder Matrix werden Skalierungsfaktoren über 128-Element-Vektoren für Aktivierungen und 128x128-Element-Kacheln für Matrizen berechnet, wodurch der Genauigkeitsverlust durch die Quantisierung reduziert wird.

Bei Perplexity verlassen wir uns auf eine Mischung aus CUDA- und Triton-Kernen, um die Inferenz zu unterstützen, wobei CUDA für die leistungsintensivsten und selten modifizierten Kerne verwendet wird (wie Aufmerksamkeit und GEMM), während Triton eine breite Palette von Aktivierungs-, Normalisierungs- und Dienstkerne implementiert. Triton ermöglichte es uns, die Kerne schnell an das Block-Quantisierungsschema anzupassen.

Für lineare und MoE-Schichten mischen wir die Deep GEMM-Kerne mit unseren eigenen Triton-GEMM-Kernen, da wir bemerkt haben, dass Split-K für bestimmte Matrixdimensionen und niedrige Batchgrößen eine geringere Latenz bietet. Wenn die unquantisierte Schicht eine (M, K) x (K, N)-Multiplikation ausführt, benötigt sie (M x ceil_div(K, 128)) x (ceil_div(K, 128), ceil_div(N, 128)) einen Skalierungsfaktor zur Block-Quantisierung. Für die Block-Quantisierung werden die Skalierungsfaktoren für Aktivierungen dynamisch berechnet, anstatt vorkalibriert zu werden. Da Aktivierungsskalierungsfaktoren nur entlang der K- und nicht entlang der M-Dimension aggregiert werden, erfordern die Kerne nur geringfügige Änderungen, um das Schema zu unterstützen.

Die von DeepSeek-V3/R1 verwendete SiLU-Aktivierungsfunktion erforderte erhebliche Änderungen zur Unterstützung von CUDA-Grafiken, Block-Quantisierung und dynamisch gerouteten Token-Zahlen. Block-Quantisierung kann problematisch sein, da sie horizontale Reduktionen einführt, jedoch wurden die Aktivierungen im Kernel bereits entlang ihrer verborgenen Dimension in Blöcke von 1024 Elementen unterteilt. Innerhalb eines Blocks wurde der zu quantisierende Tensor weiter in Blöcke von 128 unterteilt, um den größten absoluten Wert zu berechnen, wobei Triton effiziente Max-Reduktionen zwischen den Warps generierte und nur minimalen Overhead hinzufügte.

Um das MoE-Routing unter CUDA-Grafiken zu unterstützen, müssen die Kerne sich der Routing-Informationen bewusst sein, die die Anzahl der Tokens pro Experte angeben, anstatt Arbeit basierend auf der Größe der Puffer zu planen, die zum Halten der oberen Grenze der Token-Zahlen zugewiesen wurden. Das Problem kann nicht basierend auf den Dimensionen des Eingangstensors aufgeteilt werden, sodass wir eine feste Anzahl an persistenten Kernen starten, die die Routing-Informationen lesen, um zu ermitteln, wie viele Tokens belegt sind, und die Arbeit der Verarbeitung der Aktivierungen unter ihnen dynamisch aufteilen.

Wir haben bereits einige unserer Kerne in das FlashInfer-Projekt integriert und in Zukunft werden wir mehr von unserem Code als Open Source zur Verfügung stellen.

MLA-Schicht

Wir verwenden FlashInfer für die MLA-Berechnung. FlashInfer unterstützt flexible Seitentabelle-Einstellungen und extrem hohe Leistung.

Wir haben q_a_proj und kv_a_proj zu einem einzigen qkv_a_proj fusioniert. Die Latenz verringerte sich von 15,4 µs + 14,8 µs = 30,2 µs auf 16,7 µs.

Wir haben kv_b_proj in zwei Matrizen zerlegt, k_b_proj und v_b_proj. Wir haben einen FP8-Block-quantisierten BMM-Kern für Berechnungen im Zusammenhang mit diesen beiden Matrizen geschrieben.

Cuda Graph

Cuda Graph kann die Kernel-Startüberkopfkosten erheblich reduzieren, was entscheidend für die Leistung ist. Wir erstellen ein Cuda Graph für jede Batch-Größe.

Bevor wir unseren AllToAll-Kern entwickelten, verwendeten wir torch.all_to_all_single() für die AllToAll-Kommunikation. Diese Operation erfordert, dass alle GPUs dieselbe Batch-Größe verwenden. Allerdings können verschiedene DP-Gruppen unterschiedliche Batch-Größen verwenden.

Um sicherzustellen, dass all_to_all_single() mit verschiedenen DP-Gruppen kompatibel ist, die unterschiedliche Batch-Größen verwenden, haben wir vor jedem Modelllauf eine allreduce()-Operation verwendet, um die maximale Batch-Größe unter allen DP-Gruppen zu ermitteln. Dann haben wir alle DP-Gruppen diese Batch-Größe verwenden lassen, um auszuführen.

Obwohl dieser Ansatz sicherstellt, dass wir Cuda Graph verwenden können, hat er drei Nachteile. Erstens erfordert es eine zusätzliche allreduce()-Operation. Zweitens werden DP-Gruppen mit kleineren Batch-Größen gezwungen, aufzufüllen. Drittens macht es unseren Implementierungscode komplex.

Nachdem wir unseren eigenen AllToAll-Kern implementiert haben, müssen nicht mehr alle GPUs dieselbe Batch-Größe verwenden. Daher müssen wir keine zusätzlichen allreduce()-Operationen durchführen oder Batch-Größen auffüllen.

MoE Router

Der MoE-Router wird in Triton implementiert und verlässt sich auf eine modifizierte Sortierung, die aus der Standardbibliothek abgeleitet wurde und auch die Indizes der sortierten Elemente verfolgt. Die Implementierung ist für alle MoE-Modelle gemeinsam nutzbar, da das Mixtral-Routing ein Sonderfall der DeepSeek-Routen ist, bei dem die Top-K-Gruppe identisch mit der Gruppe aller Experten ist. Die sparsamen Kerne verbrauchen die Top-K-Indizes und -Punktzahlen direkt, während dichte Dispatch/Combine-Schemata, die auf All-To-All beruhen, Routinginformationen auf Expertenbasis und nicht auf Tokenbasis erfordern.

Zukünftige Arbeiten

In zukünftigen Arbeiten planen wir, die Leistung des DeepSeek-Modells weiter zu optimieren.

Die wichtigste nächste Optimierung ist die Prefill-Desaggregation. Die Prefill-Phase und die Dekodierungsphase des DeepSeek-V3/R1-Modells haben sehr unterschiedliche Berechnungseigenschaften. Beide können unterschiedliche Optimierungsstrategien und Implementierungsschemata verwenden.

Für die MLA-Schicht verwenden wir in der Dekodierungsphase die Matrixabsorption, um die FLOP-Anzahl der MLA-Berechnung zu reduzieren. In der Phrafill-Phase führt das erste Projektieren des latenten Vektors in den K/V-Raum und dann die Berechnung in der Mehrkopf-Attention (MHA) durch besser.

Wenn Prefill und Decode auf derselben GPU ausgeführt werden, verwenden wir typischerweise chunked prefill, um die Auswirkungen des Prefill auf die Decode-Ausgabegeschwindigkeit zu reduzieren und zu reduzieren, die Anfrage in mehrere Größen zu unterteilen.

Für die MoE-Schicht verwenden wir in der Dekodierungsphase so große EP und DP wie möglich, um die Anzahl der Eingabetokens pro Experte zu erhöhen und so die Leistung des GroupGEMM zu verbessern. In der Phrafill-Phase, da die Anzahl der Tokens bereits groß genug ist, ist das GroupGEMM bereits von der Berechnung betont. Daher können wir für Phrafill kleinere EP und DP verwenden.

Wenn Prefill und Decode auf derselben GPU ausgeführt werden, solange jede DP-Gruppe Prefill durchführt, wird die Latenz der MoE-Schichten auf alle GPUs erhöhen, was die Decode-Ausgabegeschwindigkeit erheblich beeinflusst.

Neben der Prefill-Desaggregation planen wir auch die Optimierung der folgenden Aspekte:

  • AllToAll-Leistung: Unser AllToAll-Kern erreicht derzeit nur 1/3 der InfiniBand-Bandbreite. Wir werden diesen Kern weiterhin optimieren.

  • EAGLE-Style spekulatives Decodieren: In den oben genannten Daten gehen wir davon aus, dass spekulatives Decodieren verwendet wird, um 1 Token vorherzusagen. EAGLE kann eine Baumstruktur verwenden, um mehrere Tokens vorherzusagen und damit die Annahmelänge zu verbessern, was die Ausgabegeschwindigkeit erheblich erhöht.

  • GEMM-Kern: Im oben gezeigten Roofline-Modell können wir sehen, dass die Effizienz des GEMM-Kerns noch weit vom theoretischen Limit entfernt ist. Wir werden diesen Kern weiterhin optimieren.

  • GB200 NVL72: In NVIDIAs neuester GB200 NVL72-Lösung sind 72 Blackwell-GPUs über schnelle NVLink verbunden. Für MoE-Architektur-Modelle ist dies eine sehr große Chance und Herausforderung.

Schlussfolgerung

Die Multi-Node-Bereitstellung von DeepSeek MoE-Modellen erreicht das, was normalerweise bei dichten LLMs unmöglich ist: gleichzeitige Verbesserung von Durchsatz und Latenz. Indem Experten auf mehr GPUs verteilt werden, reduzieren wir den Speicherbandbreitendruck pro Gerät, was eine schnellere Verarbeitung und höheren Systemdurchsatz ermöglicht. Unsere Experimente zeigen EP128-Konfigurationen mit bis zu 5-fach höherem Durchsatz bei gleichwertigen Ausgabegeschwindigkeiten im Vergleich zu Single-Node-Bereitstellungen.

Techniken zur Überlappung von Berechnung und Kommunikation wie Mikro-Batching reduzieren die Multi-Node-Kommunikations-Latenzzeit erheblich, wobei unsere Implementierung eine bis zu 40%ige Geschwindigkeitssteigerung zeigt. Unsere benutzerdefinierten AllToAll-Kommunikationskerne und optimierten Kernel-Implementierungen ermöglichen eine effiziente Implementierung des 671B-Parameter-Modells.

Da MoE-Architekturen wegen ihrer Leistungsfähigkeit an Popularität gewinnen, bieten diese Implementierungsstrategien wertvolle Einblicke in die effiziente Skalierung solcher Modelle.

Referenzen

Interessiert daran, die Zukunft unserer API-Plattform mitzugestalten? Wir stellen ein.

Treten Sie unserer Entwickler-Community bei, um über neue Releases, Funktionen und Updates stets auf dem Laufenden zu bleiben.

Interessiert daran, die Zukunft unserer API-Plattform mitzugestalten? Wir stellen ein.

Treten Sie unserer Entwickler-Community bei, um über neue Releases, Funktionen und Updates stets auf dem Laufenden zu bleiben.

Interessiert daran, die Zukunft unserer API-Plattform mitzugestalten? Wir stellen ein.

Treten Sie unserer Entwickler-Community bei, um über neue Releases, Funktionen und Updates stets auf dem Laufenden zu bleiben.