Überwindung – chatgpz.de – KI-Tools & Praxiswissen http://chatgpz.de Unabhängige Tests, Anleitungen und Hintergründe zu KI-Tools. Thu, 26 Feb 2026 06:47:28 +0000 de-DE hourly 1 https://wordpress.org/?v=6.4.2 234900186 Überwindung des Host-Speicherengpasses: Wie Peer Direct Gaudis Cloud-Leistung veränderte http://chatgpz.de/machine-learning/ueberwindung-des-host-speicherengpasses-wie-peer-direct-gaudis-cloud-leistung-veraenderte/ http://chatgpz.de/machine-learning/ueberwindung-des-host-speicherengpasses-wie-peer-direct-gaudis-cloud-leistung-veraenderte/#respond Thu, 26 Feb 2026 06:47:28 +0000 https://chatgpz.de/machine-learning/ueberwindung-des-host-speicherengpasses-wie-peer-direct-gaudis-cloud-leistung-veraenderte/ [ad_1]

Als wir Gaudi-Beschleuniger in die EC2 DL1-Instanzen von Amazon einführten, standen wir vor einer Herausforderung, die die gesamte Bereitstellung gefährdete. Die Leistungszahlen waren nicht nur enttäuschend; sie waren katastrophal. Bei Modellen, die ein effektives Coaching erforderten, kam es bei der Skalierung über mehrere Knoten zu einem Leistungsabfall von bis zu 50 %. Das Drawback? Eine Netzwerktopologie, die alle Datenbytes durch den Hostspeicher leitete, was zu einem Engpass führte, der alles zunichte machte, wofür Gaudi entwickelt wurde.

Ich leitete die technischen Bemühungen zur Lösung dieses Issues, die letztendlich zur Entwicklung dessen führten, was wir heute Peer Direct nennen. Es handelt sich um eine Funktion, die die Artwork und Weise, wie Gaudi-Beschleuniger in Cloud-Umgebungen kommunizieren, verändert hat, und ihre Geschichte bietet einige nützliche Lehren für verteiltes KI-Coaching im großen Maßstab.

Das Drawback mit Host-NICs

Gaudi wurde so konzipiert, dass die NIC (Community Interface Card) direkt in das Silizium eingebettet ist. Jeder Chip verfügt über zehn Netzwerkschnittstellen, die 100 Gbit/s verarbeiten können und RDMA mit RoCE v2 unterstützen, sodass Geräte direkt auf den Speicher des anderen zugreifen können, ohne dass die CPU benötigt wird. Diese Architektur ist äußerst effizient für KI-Trainings-Workloads, bei denen kollektive Vorgänge wie AllReduce Farbverläufe von Dutzenden oder Hunderten von Geräten professional Trainingsiteration akkumulieren müssen.

Aber Cloud-Bereitstellungen entsprechen nicht immer perfekten Architekturen. Als Amazon Gaudi für DL1-Instanzen testete, mussten sie normale Host-NICs anstelle von Gaudis integriertem Netzwerk verwenden. Die Gründe waren pragmatischer Natur: Kosteneinsparungen und die logistische Umgehung der vorhandenen Rechenzentrumsinfrastruktur zur Anpassung an eine neue Netzwerktopologie. Aus geschäftlicher Sicht warfare die Nutzung einer etablierten Netzwerkinfrastruktur absolut sinnvoll.

Aus leistungstechnischer Sicht warfare es eine Katastrophe. Anstelle von Peer-to-Peer-RDMA-Übertragungen zwischen Gaudi-Karten verlief die gesamte Kommunikation umgekehrt. Daten mussten aus Gaudís Speicher mit hoher Bandbreite in den Host-DRAM dupliziert, von der Host-CPU verarbeitet, über TCP/IP an die Host-NIC gesendet, vom entfernten Host empfangen und zurück in den Speicher des entfernten Gaudí dupliziert werden. Alle zusätzlichen Sprünge verursachten Latenz, stahlen CPU-Zyklen und führten zu zusätzlichen Bandbreitenbeschränkungen, die die Skalierbarkeit des verteilten Trainings völlig zunichte machten.

Der Leistungsmangel warfare so groß, dass man sich fragte, ob sich der Einsatz überhaupt jemals lohnen würde. Hierbei handelte es sich nicht um eine triviale Optimierung; es warfare eine existenzielle Bedrohung für die gesamte Vereinbarung mit AWS.

Warum Leistung so wichtig ist

Es lohnt sich zu wissen, warum ein Leistungsverlust von 50 % für die Lebensdauer von Trainingsmodellen und insbesondere von großen Modellen wie GPT-5 so katastrophal ist. Heutzutage dauert es Wochen oder Monate, selbst auf riesigen Clustern riesige Sprachmodelle zu trainieren. Wenn Sie mit Modellen herumspielen, die Milliarden oder Billionen Parameter haben, schlägt sich jeder Prozentpunkt der Leistung direkt in Zeit und Geld um.

Betrachten Sie die Ökonomie. Wenn die Schulung eines Modells 30 statt 15 Tage dauert, warten Sie nicht nur länger; Sie zahlen für die doppelte Rechenzeit. Im Cloud-Maßstab mit Hunderten oder Tausenden von Beschleunigern im Dauereinsatz summiert sich dies auf Millionen von Greenback. Schlimmer noch, es halbiert Ihre Iterationsgeschwindigkeit. In einer wettbewerbsintensiven KI-Welt, in der Unternehmen um die Entwicklung verbesserter Modelle konkurrieren, kann die Verdoppelung der Anzahl von Exams innerhalb desselben Zeitraums den Unterschied zwischen Vorsprung und Rückstand ausmachen.

Auch die Umweltkosten sind von entscheidender Bedeutung. Große Modelle benötigen zum Anlernen viel Strom. Bessere Leistung bedeutet weniger Rechenzeit, was den Energieverbrauch und die CO2-Emissionen halbiert. Da der Druck auf die KI-Branche, ihren CO2-Fußabdruck zu verringern, zunimmt, sind Effizienzsteigerungen kein Luxus mehr, sondern eine Notwendigkeit.

Die von uns entwickelte Lösung, Peer Direct, lieferte eine RDMA-ähnliche Leistung, als das physische Netzwerklayout nicht für normales RDMA geeignet warfare. Wir benötigten direkten Speicherzugriff zwischen Gaudi-Geräten auf verschiedenen Systemen, ohne den Host-Speicher zu durchlaufen, allerdings auf Host-NICs, die überhaupt nicht dafür ausgelegt waren.

Der Wegbereiter warfare der AWS Elastic Cloth Adapter, eine leistungsstarke Netzwerkschnittstelle für HPC- und KI-Workloads auf EC2. EFA bietet Betriebssystem-Bypass-Kommunikation mit geringer Latenz, typischerweise einer Latenz von weniger als 10 Mikrosekunden. EFA bietet RDMA-ähnliche Semantik mithilfe von libfabric, einer In-Person-House-Kommunikationsbibliothek, die eine gemeinsame Schnittstelle für mehrere Netzwerktechnologien bietet.

Die Aufgabe bestand darin, libfabric mit der Collective Communication Library (HCCL) von Habana zu kombinieren, die alle verteilten Trainingsarbeitslasten abwickelt. HCCL wurde auf der Grundlage von nativem RDMA unter Verwendung von Gaudis On-Chip-NICs erstellt. Wir mussten eine Brücke schaffen, die es HCCL ermöglicht, libfabric clear für die Kommunikation zu nutzen, ohne seine Leistungsgarantien und Kommunikationssemantik zu beeinträchtigen.

Die Lösung erforderte mehrere technische Fortschritte. Zuerst haben wir ein Speicherregistrierungssystem eingeführt, das es libfabric ermöglichte, direkt auf Gaudis Speicher mit hoher Bandbreite zuzugreifen. Wir haben das DMA-BUF-Framework des Linux-Kernels verwendet, das einen gemeinsamen Mechanismus zum Teilen von Gerätetreiberpuffern bietet. Wenn HCCL Daten übertragen muss, stellt der Gaudi-Treiber einen DMA-BUF-Dateideskriptor für die Speicherregion bereit, den libfabric verwenden kann, um RDMA-Übertragungen direkt aus dem Gerätespeicher zu erstellen.

Zweitens haben wir einen LRU-Cache für Speicherregistrierungen integriert. Die Speicherregistrierung ist teuer; Dabei handelt es sich um Kernel-Aufrufe und Setup-Vorgänge, die einen erheblichen Overhead verursachen können. Durch das Zwischenspeichern der Zuordnung von Speicheradressen zu ihren libfabric-Handles konnten wir Registrierungen in Scorching-Entry-Regionen wiederverwenden und so den größten Registrierungsaufwand für das eigentliche Coaching eliminieren.

Bild vom Autor

Das Ergebnis warfare eine Kommunikationspipeline, die etwa so aussah: HCCL ruft den OFI-Wrapper auf, der das zwischengespeicherte libfabric-Deal with aufruft, um eine RDMA-Übertragung direkt vom Gaudi-Quellspeicher zum Gaudi-Zielspeicher durchzuführen, ohne dass jemals eine CPU aufgerufen wird. Der OFI-Wrapper wurde eingeführt, um die Codebasis sauber zu halten und direkte Header-Einbindungen zu vermeiden – es handelt sich um eine schlanke Bibliothek, die dynamisch mit HCCL verknüpft ist und die Verwendung von libfabric ermöglicht, ohne dass eine direkte Integration erforderlich ist

Nachdem die Übertragung abgeschlossen ist, berichtet libfabric über eine Abschlusswarteschlange und HCCL setzt die Berechnung mit den kürzlich empfangenen Daten fort.

Die Entwicklungserfahrung

Der Aufbau von Peer Direct erforderte den Vorstoß in Neuland unter engen Zeitplänen. Libfabric warfare im Bereich der KI-Beschleuniger noch nicht Mainstream. Es gab nicht viele öffentliche Unterlagen und die Diskussion warfare dürftig. Der Schwerpunkt lag mehr auf dem Eintauchen in den libfabric-Quellcode und dem auf Experimenten basierenden Reverse Engineering.

Die Kommunikation mit den AWS-Ingenieuren warfare von größter Bedeutung, aber aufgrund der Zeitzone eingeschränkt. Die Zusammenarbeit mit einem Crew zwölf Stunden im Voraus bedeutete, dass Debug-Iterationen innerhalb von 24 Stunden bearbeitet werden konnten. Jedes Drawback erforderte eine sorgfältige Dokumentation und eine ordnungsgemäße Kommunikation, da eine Zusammenarbeit in Echtzeit nicht möglich warfare.

Es stand viel auf dem Spiel, da die gesamte DL1-Bereitstellung auf dieser funktionierenden Funktionalität beruhte. Verzögerungen hätten eine große Produkteinführung vereitelt. Niemand in unserem Crew verfügte über umfassende Hintergrundkenntnisse über die Interna von libfabric, daher lernten wir eine komplexe Codebasis und entwarfen gleichzeitig eine wichtige Integration.

Die Ergebnisse

Als wir Peer Direct tatsächlich einsetzten, waren die Geschwindigkeitsverbesserungen der Aufwand wert. Bei kollektiven Vorgängen mit einer Nachrichtengröße von 32 MB konnten wir einen 1,5- bis 2-fachen Durchsatzanstieg feststellen. Bei größeren Nachrichten warfare die Leistung sogar noch erstaunlicher, mit einem bis zu 1,76-fach höheren Durchsatz bei einer Nachrichtengröße von 256 MB. Durch den CPU-Overhead entstand ein Engpass, der vollständig verschwand.

Am wichtigsten ist, dass sich diese Mikrobenchmark-Verbesserungen direkt in der tatsächlichen Trainingsleistung des Modells niederschlugen. Durch das Coaching des DeepSpeed ​​BERT-Modells von Habana mit 5 Milliarden Parametern auf 128 Gaudi-Geräten konnten wir einen erheblichen Durchsatzgewinn feststellen. Modelle, die aggressivere Speicheroptimierungsmethoden verwenden, wie ZeRO-2, die stärker vom kollektiven Betrieb abhängig sind, profitierten überproportional von Peer Direct.

PeerDirect warfare einer der Hauptfaktoren für die Leistung von Gaudi auf AWS DL1-Instanzen und ermöglichte die mühelose Durchführung groß angelegter verteilter Schulungen am Starttag. Über diese anfängliche Wirkung hinaus legte die Initiative den Grundstein für zukünftige leistungsstarke Kommunikationsfunktionen und bewies, dass Cloud-native KI-Beschleuniger trotz der Einschränkungen der Cloud-Infrastruktur wettbewerbsfähig bleiben können.

Die Erfahrung erinnerte mich an eine wichtige Lektion in der Systemtechnik: Oft resultieren die wichtigsten Leistungsverbesserungen nicht aus der Optimierung des Quick Path, sondern aus der vollständigen Umgehung ungerechtfertigter Umwege. Beim verteilten KI-Coaching ist es wichtig, dass die Daten ohne unnötige Kopien und ohne CPU-Eingriff direkt über die Beschleuniger übertragen werden, was ein funktionierendes System im Vergleich zu einem skalierbaren System auszeichnet.

Wichtige Erkenntnisse? Eine wichtige „Erkenntnis“ aus diesem Projekt ist, dass Annahmen über die Netzwerktopologie zum frühestmöglichen Zeitpunkt im verteilten Trainingsprozess getestet werden sollten. Da viele der Beschleunigerstacks auf der Grundlage einer idealisierten Umgebung erstellt wurden, berücksichtigen sie nicht die zusätzlichen Hops, Übersetzungsebenen und/oder kostenbedingten Faktoren, die in den Cloud-Umgebungen vorhanden sind. Daher sollten Ingenieure, bevor sie sich auf die Optimierung auf Modellebene oder Kernelebene konzentrieren, ein einfaches kollektives Mikrobenchmarking für die gewünschte Topologie durchführen. Wenn die Skalierungseffizienz mit zunehmender Knotenanzahl oder Nachrichtengröße dramatisch abnimmt, liegt der wahrscheinliche Grund am Datenpfad und nicht am Kernel. Durch die frühzeitige Erkennung der Umleitung zwischen Host und Speicher können Ingenieure ihre Bemühungen dort konzentrieren, wo sie die größte Wirkung erzielen.

Eine weitere wichtige Lektion warfare die Notwendigkeit, sowohl die Speicherregistrierung als auch die Datenübertragung als erstklassige Leistungsaspekte zu behandeln. Der Aufwand für die Speicherregistrierung kann den Zeitaufwand für die Kommunikation erheblich übersteigen, wenn für jede Datenübertragung eine neue Registrierung erforderlich ist. Der LRU-Cache für registrierte Erinnerungen warfare eine nicht glamouröse Ergänzung zu HCCL; Es beseitigte jedoch effektiv eine systemische Latenzquelle und machte den RDMA-Pfad für reale Arbeitslasten nutzbar. Bei der Entwicklung verteilter Systeme sollten Ingenieure nicht nur ein Profil der verfügbaren Netzwerkbandbreite erstellen, sondern auch die Lebenszykluskosten, die mit der Zuweisung von Puffern, deren Registrierung und dem Abbau dieser Registrierungen verbunden sind. Kleine Änderungen an diesen Kontrollpfaden können zu großen Steigerungen des Finish-to-Finish-Durchsatzes führen.

Schließlich liefert die in diesem Projekt verwendete Integrationsmethode ein Muster für die Integration. Anstatt HCCL so umzuschreiben, dass libfabric direkt verwendet wird, haben wir eine dünne Abstraktionsschicht erstellt, die die vorhandene Semantik beibehält und gleichzeitig die zugrunde liegende Transportschicht ersetzt. Dies brachte mehrere Vorteile mit sich, darunter die Minimierung des Risikos, die Reduzierung der Code-Abwanderung und die Möglichkeit inkrementeller Exams. Groups, die vor einer ähnlichen Herausforderung stehen (z. B. die Anpassung von Beschleuniger-nativen Kommunikationsbibliotheken an Cloud-native Materials), sollten versuchen, die Transportschicht zu isolieren, die kollektive Semantik beizubehalten und kleine, testbare Schnittstellen zwischen beiden zu schaffen. Dies ermöglicht nicht nur eine schnellere Entwicklung, sondern auch eine einfachere Unterstützung zukünftiger Transport-Backends.

Offenlegung: Ich arbeite als AI Runtime Crew Supervisor bei Intel. Die in diesem Artikel geteilten Perspektiven sind meine eigenen.

[ad_2]

]]>
http://chatgpz.de/machine-learning/ueberwindung-des-host-speicherengpasses-wie-peer-direct-gaudis-cloud-leistung-veraenderte/feed/ 0 16622
Überwindung der versteckten Leistungsfallen variabel geformter Tensoren: Effiziente Datenerfassung in PyTorch http://chatgpz.de/machine-learning/ueberwindung-der-versteckten-leistungsfallen-variabel-geformter-tensoren-effiziente-datenerfassung-in-pytorch/ http://chatgpz.de/machine-learning/ueberwindung-der-versteckten-leistungsfallen-variabel-geformter-tensoren-effiziente-datenerfassung-in-pytorch/#respond Wed, 03 Dec 2025 17:49:33 +0000 http://chatgpz.de/machine-learning/ueberwindung-der-versteckten-leistungsfallen-variabel-geformter-tensoren-effiziente-datenerfassung-in-pytorch/ [ad_1]

ist der Teil von a Reihe von Beiträgen zum Thema Analyse und Optimierung von PyTorch-Modellen. Während der gesamten Serie haben wir uns für die Verwendung von ausgesprochen PyTorch-Profiler in der Entwicklung von KI-Modellen und demonstrierte die potenziellen Auswirkungen der Leistungsoptimierung auf die Geschwindigkeit und die Kosten der Ausführung von KI/ML-Workloads. Ein häufiges Phänomen, das wir beobachtet haben, ist, wie scheinbar harmloser Code die Laufzeitleistung beeinträchtigen kann. In diesem Beitrag untersuchen wir einige der Nachteile, die mit der naiven Verwendung von Tensoren mit variabler Kind verbunden sind – Tensoren, deren Kind von vorherigen Berechnungen und/oder Eingaben abhängt. Obwohl dies nicht auf alle Situationen anwendbar ist, gibt es Zeiten, in denen die Verwendung von Tensoren mit variabler Kind vermieden werden kann – obwohl dies möglicherweise auf Kosten zusätzlicher Rechenleistung und/oder Speicher geht. Wir werden die Kompromisse dieser Alternativen anhand einer Spielzeugimplementierung der Datenerfassung in PyTorch demonstrieren.

Drei Nachteile variabel geformter Tensoren

Wir motivieren die Diskussion, indem wir drei Nachteile der Verwendung von Tensoren mit variabler Kind aufzeigen:

Host-Gerät-Synchronisierungsereignisse

In einem idealen Szenario können CPU und GPU asynchron parallel laufen, wobei die CPU die GPU kontinuierlich mit Eingabebeispielen versorgt, den erforderlichen GPU-Speicher zuweist und GPU-Rechenkerne lädt und die GPU die geladenen Kernel auf den bereitgestellten Eingaben unter Verwendung des zugewiesenen Speichers ausführt. Das Vorhandensein dynamisch geformter Tensoren macht dieser Parallelität einen Strich durch die Rechnung. Um die entsprechende Speichermenge zuzuweisen, muss die CPU darauf warten, dass die GPU die Kind des Tensors meldet, und dann muss die GPU darauf warten, dass die CPU den Speicher zuweist und mit dem Laden des Kernels fortfährt. Der Overhead dieses Synchronisierungsereignisses kann zu einem Rückgang der GPU-Auslastung und einer langsameren Laufzeitleistung führen.

Ein Beispiel dafür haben wir gesehen Teil drei Teil dieser Serie, als wir eine naive Umsetzung des Gemeinsamen untersuchten Kreuzentropieverlust Dazu gehörten Anrufe an Torch.nonzero Und Taschenlampe.einzigartig. Beide APIs geben Tensoren mit Formen zurück, die dynamisch und vom Inhalt der Eingabe abhängig sind. Wenn diese Funktionen auf der GPU ausgeführt werden, tritt ein Host-Gerät-Synchronisierungsereignis auf. Im Fall der Kreuzentropieverlusthaben wir die Ineffizienz durch die Verwendung von entdeckt PyTorch-Profiler und konnten es leicht mit einer alternativen Implementierung überwinden, die die Verwendung von Tensoren mit variabler Kind vermeidet und eine viel bessere Laufzeitleistung zeigte.

Diagrammerstellung

In einem letzter Beitrag Wir haben die Leistungsvorteile der Bewerbung untersucht just-in-time (JIT)-Kompilierung mit der Torch.compile Operator. Eine unserer Beobachtungen warfare, dass die Diagrammerstellung viel bessere Ergebnisse lieferte, wenn das Diagramm statisch warfare. Das Vorhandensein dynamischer Formen im Diagramm schränkt den Umfang der Optimierung durch Kompilierung ein: In einigen Fällen schlägt sie vollständig fehl; in anderen führt es zu geringeren Leistungssteigerungen. Die gleichen Implikationen gelten auch für andere Formen der Diagrammerstellung, wie z XLA, ONNX, OpenVINOUnd TensorRT.

Datenstapelung

Eine weitere Optimierung, die uns in mehreren unserer Beiträge begegnet ist (z. B. Hier) ist Probenbatchierung. Die Stapelverarbeitung verbessert die Leistung hauptsächlich auf zwei Arten:

  1. Reduzierung des Overheads beim Laden des Kernels: Anstatt die für die Berechnungspipeline erforderlichen GPU-Kernel einmal professional Eingabebeispiel zu laden, kann die CPU die Kernel einmal professional Stapel laden.
  2. Maximierung der Parallelisierung über Recheneinheiten hinweg: GPUs sind hochparallele Rechenmaschinen. Je mehr wir die Berechnungen parallelisieren können, desto stärker können wir die GPU auslasten und ihre Auslastung steigern. Durch die Stapelverarbeitung können wir den Grad der Parallelisierung potenziell um den Faktor der Stapelgröße erhöhen.

Trotz ihrer Nachteile ist die Verwendung von Tensoren mit variabler Kind oft unvermeidbar. Aber manchmal können wir unsere Modellimplementierung ändern, um sie zu umgehen. Manchmal sind diese Änderungen unkompliziert (wie im Beispiel des Kreuzentropieverlusts). In anderen Fällen ist möglicherweise etwas Kreativität erforderlich, um eine andere Sequenz von PyTorch-APIs mit fester Kind zu entwickeln, die das gleiche numerische Ergebnis liefern. Oftmals kann dieser Aufwand zu erheblichen Vorteilen bei Laufzeit und Kosten führen.

In den nächsten Abschnitten werden wir die Verwendung von variablen Tensoren im Kontext der Datenabtastoperation untersuchen. Wir beginnen mit einer trivialen Implementierung und analysieren deren Leistung. Wir werden dann eine GPU-freundliche Different vorschlagen, die die Verwendung von Tensoren mit variabler Kind vermeidet.

Um unsere Implementierungen zu vergleichen, verwenden wir eine Amazon EC2 g6e.xlarge mit einem NVIDIA L40S Ausführen eines AWS Deep Studying AMI (DLAMI) mit PyTorch (2.8). Der Code, den wir teilen, ist für Demonstrationszwecke gedacht. Bitte verlassen Sie sich nicht auf die Genauigkeit oder Optimalität. Bitte interpretieren Sie unsere Erwähnung eines Frameworks, einer Bibliothek oder einer Plattform nicht als Befürwortung ihrer Verwendung.

Stichprobenerhebung in KI-Modell-Workloads

Im Kontext dieses Beitrags bezieht sich Stichprobe auf die Auswahl einer Teilmenge von Elementen aus einer großen Menge von Kandidaten zum Zwecke der Recheneffizienz, des Ausgleichs von Datentypen oder der Regularisierung. Stichproben sind in vielen KI/ML-Modellen üblich, beispielsweise in Erkennungs-, Rating- und kontrastiven Lernsystemen.

Wir definieren eine einfache Variation des Stichprobenproblems: Gegeben sei eine Liste von N Tensoren mit jeweils einer binären Bezeichnung werden wir gebeten, eine Teilmenge davon zurückzugeben Ok Tensoren, die sowohl constructive als auch adverse Beispiele in zufälliger Reihenfolge enthalten. Wenn die Eingabeliste genügend Beispiele für jedes Etikett enthält (Ok/2), sollte die zurückgegebene Teilmenge gleichmäßig aufgeteilt sein. Fehlen Stichproben einer Artwork, sollten diese durch Stichproben der zweiten Artwork aufgefüllt werden.

Der folgende Codeblock enthält eine PyTorch-Implementierung unserer Sampling-Funktion. Die Umsetzung orientiert sich am Populären Detectron2 Bibliothek (z. B. siehe Hier Und Hier). Für die Experimente in diesem Beitrag legen wir das Stichprobenverhältnis fest 1:10.

import torch

INPUT_SAMPLES = 10000
SUB_SAMPLE = INPUT_SAMPLES // 10
FEATURE_DIM = 16

def sample_data(input_array, labels):
    gadget = labels.gadget
    constructive = torch.nonzero(labels == 1, as_tuple=True)(0)
    adverse = torch.nonzero(labels == 0, as_tuple=True)(0)
    num_pos = min(constructive.numel(), SUB_SAMPLE//2)
    num_neg = min(adverse.numel(), SUB_SAMPLE//2)
    if num_neg < SUB_SAMPLE//2:
        num_pos = SUB_SAMPLE - num_neg
    elif num_pos < SUB_SAMPLE//2:
        num_neg = SUB_SAMPLE - num_pos

    # randomly choose constructive and adverse examples
    perm1 = torch.randperm(constructive.numel(), gadget=gadget)(:num_pos)
    perm2 = torch.randperm(adverse.numel(), gadget=gadget)(:num_neg)

    pos_idxs = constructive(perm1)
    neg_idxs = adverse(perm2)

    sampled_idxs = torch.cat((pos_idxs, neg_idxs), dim=0)
    rand_perm = torch.randperm(SUB_SAMPLE, gadget=labels.gadget)
    sampled_idxs = sampled_idxs(rand_perm)
    return input_array(sampled_idxs), labels(sampled_idxs)

Leistungsanalyse mit PyTorch Profiler

Auch wenn die Verwendung dynamischer Formen nicht sofort offensichtlich ist, ist sie in der PyTorch Profiler Hint-Ansicht leicht zu erkennen. Wir verwenden die folgende Funktion, um PyTorch Profiler zu aktivieren:

def profile(fn, enter, labels):
    
    def export_trace(p):
        p.export_chrome_trace(f"{fn.__name__}.json")
        
    with torch.profiler.profile(
            actions=(torch.profiler.ProfilerActivity.CPU,
                        torch.profiler.ProfilerActivity.CUDA),
            with_stack=True,
            schedule=torch.profiler.schedule(wait=0, warmup=10, lively=5),
            on_trace_ready=export_trace
    ) as prof:
        for _ in vary(20):
            fn(enter, labels)
            torch.cuda.synchronize()  # specific sync for hint readability
            prof.step()

# create random enter
input_samples = torch.randn((INPUT_SAMPLES, FEATURE_DIM), gadget='cuda')
labels = torch.randint(0, 2, (INPUT_SAMPLES,), 
                       gadget='cuda', dtype=torch.int64)

# run with profiler
profile(sample_data, input_samples, labels)

Das Bild unten wurde für den Wert von zehn Millionen Eingabeproben aufgenommen. Es zeigt deutlich das Vorhandensein von Synchronisierungsereignissen, die vom Torch.nonzero-Aufruf ausgehen, sowie die entsprechenden Rückgänge bei der GPU-Auslastung:

Profiler Hint of Sampler (vom Autor)

Die Verwendung von Torch.nonzero in unserer Implementierung ist nicht supreme, aber kann sie vermieden werden?

Ein GPU-freundlicher Datensampler

Wir schlagen eine various Implementierung unserer Sampling-Funktion vor, die die dynamische Funktion Torch.nonzero durch eine kreative Kombination der Statik ersetzt Torch.count_nonzero, Torch.topkund andere APIs:

def opt_sample_data(enter, labels):
    pos_mask = labels == 1
    neg_mask = labels == 0
    num_pos_idxs = torch.count_nonzero(pos_mask, dim=-1)
    num_neg_idxs = torch.count_nonzero(neg_mask, dim=-1)
    half_samples = labels.new_full((), SUB_SAMPLE // 2)
    num_pos = torch.minimal(num_pos_idxs, half_samples)
    num_neg = torch.minimal(num_neg_idxs, half_samples)
    num_pos = torch.the place(
        num_neg < SUB_SAMPLE // 2,
        SUB_SAMPLE - num_neg,
        num_pos
    )
    num_neg = SUB_SAMPLE - num_pos

    # create random ordering on pos and neg entries
    rand = torch.rand_like(labels, dtype=torch.float32)
    pos_rand = torch.the place(pos_mask, rand, -1)
    neg_rand = torch.the place(neg_mask, rand, -1)

    # choose prime pos entries and invalidate others
    # since CPU would not know num_pos, we assume most to keep away from sync
    top_pos_rand, top_pos_idx = torch.topk(pos_rand, okay=SUB_SAMPLE)
    arange = torch.arange(SUB_SAMPLE, gadget=labels.gadget)
    if num_pos.numel() > 1:
        # unsqueeze to help batched enter
        arange = arange.unsqueeze(0)
        num_pos = num_pos.unsqueeze(-1)
        num_neg = num_neg.unsqueeze(-1)
    top_pos_rand = torch.the place(arange >= num_pos, -1, top_pos_rand)

    # repeat for neg entries
    top_neg_rand, top_neg_idx = torch.topk(neg_rand, okay=SUB_SAMPLE)
    top_neg_rand = torch.the place(arange >= num_neg, -1, top_neg_rand)

    # mix and blend collectively constructive and adverse idxs
    cat_rand = torch.cat((top_pos_rand, top_neg_rand), dim=-1)
    cat_idx = torch.cat((top_pos_idx, top_neg_idx), dim=-1)
    topk_rand_idx = torch.topk(cat_rand, okay=SUB_SAMPLE)(1)
    sampled_idxs = torch.collect(cat_idx, dim=-1, index=topk_rand_idx)
    sampled_input = torch.collect(enter, dim=-2, 
                                 index=sampled_idxs.unsqueeze(-1))
    sampled_labels = torch.collect(labels, dim=-1, index=sampled_idxs)
    return sampled_input, sampled_labels

Offensichtlich erfordert diese Funktion mehr Speicher und mehr Operationen als unsere erste Implementierung. Die Frage ist: Überwiegen die Leistungsvorteile einer statischen, synchronisierungsfreien Implementierung die zusätzlichen Kosten für Speicher und Rechenleistung?

Um die Kompromisse zwischen den beiden Implementierungen zu bewerten, führen wir das folgende Benchmarking-Dienstprogramm ein:

def benchmark(fn, enter, labels):
    # warm-up
    for _ in vary(20):
        _ = fn(enter, labels)

    iters = 100
    begin = torch.cuda.Occasion(enable_timing=True)
    finish = torch.cuda.Occasion(enable_timing=True)
    torch.cuda.synchronize()
    begin.document()
    for _ in vary(iters):
        _ = fn(enter, labels)
    finish.document()
    torch.cuda.synchronize()
    avg_time = begin.elapsed_time(finish) / iters
    
    print(f"{fn.__name__} common step time: {(avg_time):.4f} ms")

benchmark(sample_data, input_samples, labels)
benchmark(opt_sample_data, input_samples, labels)

Die folgende Tabelle vergleicht die durchschnittliche Laufzeit jeder Implementierung für verschiedene Eingabestichprobengrößen:

Vergleichende Schrittzeitleistung – Je niedriger, desto besser (vom Autor)

Bei den meisten Eingabestichprobengrößen ist der Overhead des Host-Gerät-Synchronisierungsereignisses entweder vergleichbar oder geringer als der zusätzliche Rechenaufwand der statischen Implementierung. Enttäuschenderweise sehen wir erst dann einen großen Nutzen aus der synchronisierungsfreien Different, wenn die Eingabestichprobengröße zehn Millionen erreicht. Derart große Stichprobengrößen sind in KI/ML-Umgebungen ungewöhnlich. Aber es ist nicht unsere Tendenz, so schnell aufzugeben. Wie oben erwähnt, ermöglicht die statische Implementierung andere Optimierungen wie die Kompilierung von Diagrammen und die Stapelverarbeitung von Eingaben.

Diagrammerstellung

Im Gegensatz zur ursprünglichen Funktion – die nicht kompiliert werden kann – ist unsere statische Implementierung vollständig kompatibel mit Torch.compile:

benchmark(torch.compile(opt_sample_data), input_samples, labels)

Die folgende Tabelle enthält die Laufzeiten unserer kompilierten Funktion:

Vergleichende Schrittzeitleistung – Je niedriger, desto besser (vom Autor)

Die Ergebnisse sind deutlich besser und bieten eine Steigerung von 70 bis 75 Prozent gegenüber der ursprünglichen Sampler-Implementierung im Bereich von 1 bis 10.000. Aber wir haben noch eine weitere Optimierung in petto.

Maximierung der Leistung mit Batch-Enter

Da die ursprüngliche Implementierung variablenförmige Operationen enthält, kann sie Batch-Eingaben nicht direkt verarbeiten. Um einen Stapel zu verarbeiten, haben wir keine andere Wahl, als ihn in einer Python-Schleife auf jede Eingabe einzeln anzuwenden:

BATCH_SIZE = 32

def batched_sample_data(inputs, labels):
    sampled_inputs = ()
    sampled_labels = ()
    for i in vary(inputs.dimension(0)):
        inp, lab = sample_data(inputs(i), labels(i))
        sampled_inputs.append(inp)
        sampled_labels.append(lab)
    return torch.stack(sampled_inputs), torch.stack(sampled_labels)

Im Gegensatz dazu unterstützt unsere optimierte Funktion Batch-Eingaben unverändert – es sind keine Änderungen erforderlich.

input_batch = torch.randn((BATCH_SIZE, INPUT_SAMPLES, FEATURE_DIM),
                          gadget='cuda')
labels = torch.randint(0, 2, (BATCH_SIZE, INPUT_SAMPLES),
                       gadget='cuda', dtype=torch.int64)

benchmark(batched_sample_data, input_batch, labels)
benchmark(opt_sample_data, input_batch, labels)
benchmark(torch.compile(opt_sample_data), input_batch, labels)

Die folgende Tabelle vergleicht die Schrittzeiten unserer Stichprobenfunktionen bei einer Chargengröße von 32:

Schrittzeitleistung bei Batch-Enter – Je niedriger, desto besser (vom Autor)

Jetzt sind die Ergebnisse endgültig: Durch die Verwendung einer statischen Implementierung des Datensamplers können wir die Leistung je nach Eingabestichprobengröße um das 2- bis 52-fache (!!) der variablenförmigen Choice steigern.

Beachten Sie, dass unsere Experimente zwar auf einem GPU-Gerät ausgeführt wurden, die Modellkompilierungs- und Eingabe-Batching-Optimierungen jedoch auch für eine CPU-Umgebung gelten. Daher könnte die Vermeidung variabler Formen auch Auswirkungen auf die Leistung des AI/ML-Modells auf der CPU haben.

Zusammenfassung

Der Optimierungsprozess, den wir in diesem Beitrag demonstriert haben, verallgemeinert sich über den speziellen Fall der Datenstichprobe hinaus:

  • Entdeckung über Leistungsprofilierung: Mit der PyTorch-Profiler Wir konnten einen Rückgang der GPU-Auslastung identifizieren und ihre Ursache ermitteln: das Vorhandensein von Tensoren mit variabler Kind, die aus der Operation „torch.nonzero“ resultieren.
  • Eine various Implementierung: Unsere Profilierungsergebnisse ermöglichten es uns, eine various Implementierung zu entwickeln, die das gleiche Ziel erreichte und gleichzeitig die Verwendung von Tensoren mit variabler Kind vermeidete. Dieser Schritt ging jedoch mit einem zusätzlichen Rechen- und Speicheraufwand einher. Wie in unseren ersten Benchmarks zu sehen warfare, zeigte die synchronisierungsfreie Different bei gängigen Eingabegrößen eine schlechtere Leistung.
  • Weiteres Optimierungspotenzial erschließen: Der wahre Durchbruch gelang, weil die statisch geformte Implementierung kompilierungsfreundlich warfare und Batchverarbeitung unterstützte. Diese Optimierungen sorgten für Leistungssteigerungen, die den anfänglichen Overhead in den Schatten stellten und zu einer 2- bis 52-fachen Beschleunigung gegenüber der ursprünglichen Implementierung führten.

Natürlich enden nicht alle Geschichten so glücklich wie unsere. In vielen Fällen stoßen wir möglicherweise auf PyTorch-Code, der auf der GPU eine schlechte Leistung erbringt, aber über keine various Implementierung verfügt, oder über eine, die deutlich mehr Rechenressourcen erfordert. Angesichts des Potenzials für erhebliche Leistungssteigerungen und Kostensenkungen ist der Prozess der Identifizierung von Laufzeitineffizienzen und der Erforschung alternativer Implementierungen jedoch ein wesentlicher Bestandteil der KI/ML-Entwicklung.

[ad_2]

]]>
http://chatgpz.de/machine-learning/ueberwindung-der-versteckten-leistungsfallen-variabel-geformter-tensoren-effiziente-datenerfassung-in-pytorch/feed/ 0 14503