Optimierung von SWE-Bench: Wie Logicstar die Größe um 50x reduzierte
Die Evaluierung von Codierungsagenten sollte nicht wie das Trocknen von Farbe erscheinen. Doch mit SWE-Bench Verified war dies oft der Fall – Hunderte von Docker-Images, die insgesamt 240 GiB ausmachten, wurden durch Ratenlimits ausgebremst und verwandelten die erste Einrichtung auf einem neuen Rechner in eine 30-stündige Odyssee. Um über ein breiteres, weniger überangepasstes und repräsentativeres Set von Repositories zu testen, war es notwendig, die Umgebung zu optimieren. Daher entschlossen wir uns, das Problem zu beheben. Durch die Umstrukturierung von Schichten, das Entfernen unnötiger Dateien und die Anwendung von Kompressionstechniken reduzierten wir SWE-Bench Verified von 240 GiB auf nur 5 GiB. Jetzt kann es in weniger als einer Minute heruntergeladen werden, was großangelegte Evaluierungen und die Generierung von Trace-Daten auf Cloud-Maschinen schnell und schmerzlos macht.
Hintergrund
Die Evaluierung von SWE-Bench Verified erfordert 500 containerisierte Umgebungen, eine für jedes Problem über zwölf Repositories. Ihre Optionen sind entweder, alle von Grund auf neu zu erstellen (und zu hoffen, dass alle Abhängigkeiten festgelegt sind) oder die vorgefertigten Images von Docker Hub herunterzuladen. Keine dieser Optionen ist ideal. Der Aufbau dauert Stunden und kann Inkonsistenzen einführen. Das Herunterladen erfordert mehr als 100 GiB komprimierter Schichten und die Entpackung in 240 GiB lokalen Speicher. Selbst mit einem Docker Hub Pro-Abonnement und einer schnellen Verbindung kann dieser Prozess zwischen einer halben Stunde und mehreren Stunden in Anspruch nehmen. Ohne ein Pro-Konto machen die Ratenlimits die Situation noch schlimmer – man kann 30 Stunden damit verbringen, nur auf die Fertigstellung der Downloads zu warten.
Das Schichtproblem
Im Kern jedes Docker-Images liegt ein Stapel von Schichten, die die Änderungen im Dateisystem darstellen. Wenn ein Container ausgeführt wird, sucht Docker (über OverlayFS) nach der obersten Schicht, die eine angeforderte Datei enthält, und liest sie von dort. Der Container selbst fügt eine dünne beschreibbare Schicht oben drauf hinzu: Wenn Sie eine Datei ändern, kopiert Docker sie in diese beschreibbare Schicht, sodass Änderungen die zugrunde liegenden Bildschichten nie beeinflussen. Dieses Design ist clever, da es die Speicherung und Verteilung von Images effizient macht. Wenn zwei Images eine Basis wie ubuntu:latest teilen, können sie beide die gleiche Basisschicht verwenden und nur ihre eigenen Unterschiede oben drauf hinzufügen. Allerdings wird jede modifizierte Datei vollständig dupliziert.
Das Problem lösen
Um das Problem zu lösen, führen wir eine Technik ein, die wir Delta-Layering nennen. Anstatt für jeden Checkout eine einzelne Schicht zu erstellen, die eine vollständige Kopie des Repositories enthält, verarbeiten wir die Images nach, sodass jede Instanzschicht nur die Unterschiede – das Delta – zum vorherigen Commit hinzufügt. Die Idee ist einfach: Zwei Schnappschüsse desselben Repositories, die nur wenige Wochen auseinander liegen, sind nahezu identisch. Doch im Standard-Layering-Schema werden beide Schnappschüsse als vollständige Kopien verpackt; Delta-Layering entfernt diese Duplikation.
Git-Historie und Packfiles
Delta-Layering löst einen Großteil des Duplikationsproblems, aber es gibt eine versteckte Komplikation: die Git-Historie. Jedes SWE-Bench-Image enthält die vollständige Git-Historie des Repositories bis zu dem Punkt, an dem das Problem erstellt wurde. In der Theorie sollte das kein großes Problem sein. Git speichert seine Daten als eine Schlüssel-Wert-Datenbank von Objekten: Commits, Bäume und Blobs. Das Hinzufügen eines neuen Commits erzeugt nur einige neue Objekte – die geänderten Dateien, die geänderten Verzeichnisse und das Commit-Objekt selbst. Wenn alles als lose zlib-komprimierte Dateien in .git/objects gespeichert wäre, könnte Delta-Layering einfach die Handvoll neuer Objekte erfassen.
Entfernen von Build-Artefakten
Viele der Images enthielten Überreste des Build-Prozesses, die zur Laufzeit nie benötigt wurden – Installer, Caches usw. Beispielsweise fügte der Miniconda-Installer allein 136 MB zu jedem Image hinzu. Pip- und Conda-Caches verbrauchten noch mehr. Das Entfernen dieser Artefakte spart Gigabytes ohne nennenswerte Kosten.
Abschließende Kompression
Zusätzlich zur Minimierung jeder Schicht wenden wir auch eine Kreuzschichtkompression an. Während das Schichtmodell von Docker die gesamte Datei kopiert, wenn sich eine einzige Zeile ändert, sind Kompressionsalgorithmen sehr gut darin, wiederholte Daten zu erkennen. Wir wählen zstd, da es schnell, hochgradig parallel und in der Lage ist, sehr große Kompressionsfenster zu unterstützen. Um dem Kompressor die besten Chancen zu geben, sortierten wir die Schichten nach ihrer chronologischen Kettenreihenfolge. So sitzen nahezu identische Schichten nebeneinander im Eingabestrom. Das Ergebnis ist, dass die gesamte Benchmark, 240 GiB roher Images, jetzt in einem einzigen 5 GiB Archiv passt.
Zusammenfassung
Insgesamt bringen unsere Optimierungen SWE-Bench Verified von 240 GiB rohen Schichten auf nur 31 GiB unkomprimiert und mit der richtigen Kompression auf ein einzelnes Archiv von nur 5 GiB. Dieses Archiv ist klein genug, um in etwa fünf Minuten auf jedem modernen Rechner heruntergeladen und entpackt zu werden. Und das Beste ist, dass der Kern unserer Optimierung – Delta-Layering – nicht spezifisch für SWE-Bench ist und leicht auf jede andere Serie von Ausführungsumgebungen angewendet werden kann. Da Docker und Podman nativ keine komprimierten Bundles laden können, haben wir Hilfsskripte auf GitHub bereitgestellt. Das finale Archiv selbst wird auf Hugging Face gehostet, um schnelle Downloads zu unterstützen.
Was kommt als Nächstes?
Ausführungsumgebungen sind nicht nur für die Evaluierung von Code-Agenten unerlässlich, sondern auch für das Training von Code-Modellen. Unabhängig davon, ob Sie Reinforcement Learning oder Supervised Fine-Tuning durchführen, erfordert die Generierung hochwertiger Trainingsdaten vielfältige Agententraces, die wiederum eine große Anzahl von Ausführungsumgebungen benötigen. Ausführungsumgebungen, die wir nun effizient speichern und an eine große Anzahl von flüchtigen Maschinen verteilen können, um eine große Anzahl von Traces zu generieren… Bleiben Sie dran, um mehr über die nächsten Schritte zu erfahren.
Hinterlasse einen Kommentar
An der Diskussion beteiligen?Hinterlasse uns deinen Kommentar!