Eintauchen in die KI: Eine dreiteilige Serie
Hallo, ich bin Kevin Séjourné, Doktor der Informatik und Senior R&D Engineer bei Cloud Temple. Wie Sie sich vorstellen können, habe ich in den letzten 20 Jahren viel Code geschrieben. Als leidenschaftlicher Erforscher von LLMs stelle ich fest, dass sie nun Code an meiner Stelle schreiben können. Umso besser! Aber da ich mich gewöhnlich auf wissenschaftliche Beobachtungen stütze, habe ich beschlossen, die Qualität ihrer Arbeit zu testen.
Sehen Sie sich die drei Episoden meiner Studie an :
- Episode 1: Ich automatisiere meine Arbeit mit LLMs
- Episode 2: Lassen Sie uns mit Hilfe der LLM den Code korrigieren und vergessene Algorithmen rekonstruieren.
- Episode 3: Vervollständigen wir das ursprüngliche Programm, testen wir, testen wir härter
Episode 3: Vervollständigen wir das ursprüngliche Programm, testen wir, testen wir härter
In diesem Artikel werden wir erkunden, wie wir das mit der LLM erzeugte Backend-Programm vervollständigt, es getestet und seinen Einsatz mit Docker optimiert haben. Wir werden auch sehen, wie Sie GPT4o und andere Tools verwenden können, um Code effizienter zu generieren.
CI/CD : Nutzung von Docker mit GPT4o
Es ist ein Programm backendEin Einsatz auf der Grundlage von Docker ist der Standard, vor der Übergabe an die devops. Bitten wir GPT4o, uns einen Dockerfile an unser Programm angepasst.
Es wird uns ein Bild vorgeschlagen, das auf alpine:latest. Warum sollte das nicht möglich sein? Allerdings ist das Kompilieren von nativem Code auf alpine erfordert musl als Standardbibliothek anstelle von glibc, abwesend auf alpine.
musl stellt kein besonderes Problem dar, aber GPT-4 hat die Option -target x86_64-unknown-linux-musl in der Bestellung Frachtschiff. Ohne diese Option wäre es unmöglich gewesen, den Code in das Bild zu kompilieren alpine. Wenn wir nicht auf dieses Problem gestoßen wären, wäre uns ein stundenlanges Kopfzerbrechen garantiert gewesen, bevor wir herausgefunden hätten, warum der Container docker nicht funktioniert, obwohl das Programm läuft.
Warum sollten Sie vollständige Linux-Distributionen vermeiden?
Eine komplette Linux-Distribution für ein eigenständiges Programm zu verwenden, ist oft übertrieben. Ein Bild from scratch ist leichter und besser auf unsere speziellen Bedürfnisse zugeschnitten. Es ist jedoch von entscheidender Bedeutung, den Begriff from scratch um Missverständnisse zu vermeiden. GPT4o interpretiert diese Bitte so, dass sie ein neues Dockerfile von Null an, anstatt eine Bild die mit from scratch beginnt.
Um dieses Problem zu umgehen, geben wir eine Datei Dockerfile minimal zu GPT4o mit nur dem Teil build und der Teil run und wir bitten ihn zu ergänzen, damit der Dockerfile funktioniert, um das kompilierte Programm mit Frachter. File: Dockerfile FROM clux/muslrust:stable as builder FROM scratch
Prompt : kannst du mein Dockerfile so korrigieren, dass es mein Programm mit cargo kompiliert und korrekt startet?
Dies kann bei Bedarf in einem neuen Thread mit GPT4o durchgeführt werden. Es ist nicht nötig, den gesamten Kontext zu behalten, der Dockerfile ausreichend standardisiert sein wird, um ihn zu bitten, sie in einem zweiten Generierungsschritt mit den tatsächlichen Namen zu ändern.
Probleme mit SSL-Zertifikaten und deren Lösung
Einige docker build / docker run und Austausch mit GPT4o später und wir haben nun ein vollständiges Produkt. Beachten Sie, dass viel Zeit auf ein kleines Detail von SSL-Zertifikate die GPT4o nicht lösen konnte. Die folgende Zeile hat uns geholfen: COPY -from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ Recherchen mit Google waren nötig, um sie zu finden. Dies ist wahrscheinlich darauf zurückzuführen, dass Rust ist in den Lernbasen von GPT4o nicht so verbreitet und die Bilder from scratch auch nicht.
Bei einem blockierenden Detail scheitert die LLM.
Gut funktionierend, Einheitlich, Integration
Für die Tests von gute Funktionkönnen wir die Vorlage anweisen, Dateien zu erzeugen. json normalerweise eingefügt in den body der Anfrage http. Dazu bauen wir einen prompt bestehend aus einer Beispieldatei json gut ausgebildet und sagen ihm, welche Variablen er ändern muss. Dieser Teil kann aus Gründen der Vertraulichkeit nicht von GPT4o durchgeführt werden, aber es ist eine Arbeit, die einfach genug ist, um von llama3 7b Q4.
Dann können wir schnell über json von Beispielen zu verwenden. Es ist möglich, das Format von Zahlen und ihre Verkapselungen in String/doppelte QuoteSie können auch die Möglichkeit nutzen, um sich zu identifizieren, IDs zu ändern, Grenzfälle einfacher und schneller zu erstellen usw. Die einzige Grenze ist Ihre Vorstellungskraft.
Das einzige Problem mit Funktionstests ist, dass wir nach einem Test sehr oft zum Schritt der Codekorrektur zurückkehren müssen. Bei dieser Methode, bei der der Code generiert oder halb generiert wird, führen wir keine Unit-Tests der Funktionen in einem framework integriert haben, haben wir direkt mit den Integrationstests begonnen. In unserem Programm gibt es keinen Algorithmus, der kompliziert genug ist, um verdrehte Verhaltensweisen zu zeigen.
Leistung
Um die Leistung eines schnellen Programms zu testen, ist es am einfachsten, es mehrmals aufzurufen und seine durchschnittliche Arbeitsgeschwindigkeit zu berechnen. Das Problem ist, dass Aufrufe mit curl tun dies auf folgende Weise: while true; do time -p curl -i -X POST -data “@testfile.json” -H “User-Agent: while“ -H “authorization: X-API-KEY tokensecret“ -H “content-type: application/json" -H "Accept-Encoding: gzip, deflate, br“ <http://localhost:8000/grafana/alert>; ; done startet einen Prozess bei jeder Ausführung, und die gesamte Ausführungszeit des Programms ist kürzer als das. Das Programm von Mockup verlagert bereits einen Teil des Problems der Langsamkeit außerhalb unseres Hauptprogramms, denn das Mockup macht keine echte Interaktion mit einer DB noch mit dem Netzwerk.
Wir müssen also den while true curl durch ein schnelleres Programm zu ersetzen. Lassen Sie uns diese Generierung von GPT4o über die Testbefehlszeile, die wir haben, anfordern; und sagen Sie GPT4o, dass wir nicht wollen, dass es den Befehl curl. Wir erhalten dieses Programm :
async fn main() {
let data = include_str!("testfile.json");
let json_data : serde_json::Value = serde_json::from_str(data).unwrap();
let client = Client::new();
loop{
let start = Instant::now();
let response = client.post("http://localhost:8000/grafana/alert")
.header("User-Agent", "while")
.header("authorization", "X-API-KEY tokensecret")
.header("content-type", "application/json")
.header("Accept-Encoding", "gzip, deflate, br")
.body(json_data.to_string())
.send()
.await
.expect("failed to get response")
;
println!("Status: {} ", response.status());
}
}```
Nach einem Test der Kompilierung und der korrekten Funktion dieses Programms bitten wir GPT4o, Schritt für Schritt eine Zählung der Anzahl der Iterationen, eine Berechnung der Ausführungszeit für jede postSie können auch eine Liste der Testdateien, eine durchschnittliche Ausführungszeit und eine Rotation über die Testdateien erstellen:
```use request::Client; use serde_json::Value as JsonValue; use std::fs::read_to_string; use std::time::Instant;
async fn load_json_data(file_path: &str) -> JsonValue { let data = read_to_string(file_path).expect("Unable to read file"); serde_json::from_str(&data).expect("Unable to parse JSON") }
[async_std::main]
async fn main() { let mut iteration = 0; let mut total_duration = 0; let client = Client::new();
loop {
iteration += 1;
let json_index = (iteration - 1) % 9 + 1; // 1..9
let file_path = format!("testfile{}.json", json_index);
let json_data = load_json_data(&file_path).await;
let start = Instant::now();
let response = client
.post("http://localhost:8000/grafana/alert")
.header("User-Agent", "while")
.header("authorization", "X-API-KEY tokensecret")
.header("content-type", "application/json")
.header("Accept-Encoding", "gzip, deflate, br")
.body(json_data.to_string())
.send()
.await
.expect("failed to get response");
let duration = Instant::now() - start;
total_duration += duration.as_nanos();
println!((
"Iteration: {} Status: {} Duration: {:.2?} \tmed:{}",
iteration,
response.status(),
duration.as_nanos(),
total_duration / iteration
);
}
}```
Wir haben GPT4o gebeten, die Rotation zu den Testdateien hinzuzufügen, lange nachdem wir ihn gebeten hatten, die Iterationen zu zählen. Wir können uns freuen, dass er keine Variable neu erstellt, sondern die Variable ausgenutzt hat Iteration um die Rotation durch Anwendung eines Modulo zu erzeugen.
Die Formatierung des Codes selbst ist ein Artefakt des Kopierens und Einfügens in VSCode (_Rust-analyser_) und anschließend in den Markdown-Editor.
Wir können nun Leistungstests zwischen der ursprünglichen Version und unserem endgültigen Programm durchführen. Die Leistungstests wurden auf einem laptop ausgestattet mit einem _I7_12g. Jeder Test wurde über 10.000 Iterationen ausgeführt. Die durchschnittliche Ausführungszeit mit unserem Programm Python des Originals beträgt 3.791.274 ns (Nanosekunden). Während die durchschnittliche Ausführungszeit unseres neuen Programms Rust beträgt 3.791.274 ns und ist damit 3,9-mal schneller.
Wir vermuten, dass ein Großteil des Leistungsunterschieds auf die begrenzten Fähigkeiten des Mockup-Programms zurückzuführen ist, schnell genug auf unser neues Programm zu reagieren. Transformieren wir es ebenfalls in Rust. Wir haben es versucht mit llama3 70b auf groq.com aber es war sehr schwierig, ein einfaches funktionales Programm zu erhalten. Der gleiche Prompt, der in GPT4o vorgeschlagen wurde, ermöglichte es uns, auf Anhieb ein korrektes Programm mit nur wenigen import nicht verwendet :
Schreibt das folgende Programm in Rust um.
Verwendet "actix".
Verwendet nicht TcpListener::bind
Erstellt keine neue "struct".
Ersetzt die statischen 'id' 666 & 999 durch zufällig generierte Zahlen.
from fastapi import FastAPI, Request
app = FastAPI()
@app.post("/v1/monitoringServices/notifications")
async def monitoring_service():
return {"data": {"id": "999"}}
[...]
@app.get("/v1/hosts/{host_id}/services")
async def host_service(host_id: int, request: Request):
return {"data": [{"id": "666"}]}
Hier ist der korrigierte Text:
Dies ist ein Ansatz, bei dem wir im ursprünglichen Python-Programm den Prompt belassen, um zu dokumentieren, was mit diesem Programm geschieht. Das erzeugte Programm wird dann in einem Ordner daneben abgelegt.
Die Präzisierungen, die wir zu "Verwendet actix" oder "Verwendet nicht "TcpListener::bind"" machen, sind Rückmeldungen aus der Erfahrung mit llama3 70b.
Das resultierende Programm entspricht den Spezifikationen und ist nicht wirklich länger als sein Gegenstück Python.
Dann können wir unsere Tests erneut starten. Die durchschnittliche Ausführungszeit mit unserem Programm Python des Originals beträgt 2916122 ns. Während die durchschnittliche Ausführungszeit unseres neuen Programms Rust liegt bei 250992 ns und ist damit 11,6-mal schneller. Die Größe des Docker-Images wurde von 195 MB für das Programm Python basierend auf einem Bild python:alpinedie Größe des Programmbildes standalone ist 13,4 MB groß und basiert auf einem Bild scratch.
Leichter, schneller und mit einer kleineren Angriffsfläche. Wir konnten ein Programm leichter umwandeln und testen, als wir es von Hand getan hätten.
Schlussfolgerungen
Nicht alle LLMs sind gleich. Zwischen Geschwindigkeit, Leistung und Vertraulichkeit muss man sich immer die Frage stellen: was tun wir? warum?
Geschwindigkeit : groq.com 1200 Token/s mit llama3 7b
Leistung : GPT4o auf dem von Cloud Temple entwickelten KI-Portal, erfordern die Antworten weniger Nachbearbeitung.
Datenschutz : lokal je nach Ihrer Hardware vom llama3 oder des mistral/_mixtral_.
Die Umwandlung kostete uns zwischen drei und vier Arbeitstagen, je nachdem, wie man die Unterbrechungen zählt.
Geschätzte Entwicklungszeit
Ohne LLM: 12 Tage
Mit LLM: 4 Tage
Wir hätten eine solche Transformation in eine Sprache mit so wenig Fachwissen nie in Angriff genommen, wenn wir nicht leistungsfähige Sprachmodelle zur Unterstützung gehabt hätten. Einen Entwickler auf der Grundlage der beherrschten Programmiersprachen einzustellen, scheint überhaupt nicht mehr relevant zu sein.
Begrenzungen und Lehren
Obwohl LLMs erhebliche Gewinne bieten, sind sie nicht unfehlbar. Manuelle Prüfungen sind nach wie vor unerlässlich, um die Qualität und Sicherheit des Produktcodes zu gewährleisten.
Wir verfügen über gute Fachkenntnisse in vielen Programmiersprachen, aber nicht in Rust. Wir können behaupten, dass wir, wenn wir das Programm neu hätten schreiben müssen in Java oder Javascripthätte man mindestens dreimal so viel Zeit einkalkulieren müssen.
Die Verwendung von LLM verdreifacht unsere Codeproduktivität. Die Produktion von Code ist zwar nicht die einzige Tätigkeit von Entwicklern, aber es ist immer noch ein gutes Geschäft. Und was den ökologischen Fußabdruck angeht, ist der Energieverbrauch, den diese LLMs, die für uns Code schreiben, verursachen, viel geringer als der Kaffeeverbrauch, der bei einer Verdreifachung der Arbeitszeit entstanden wäre.