Immersione nell'IA: una serie di 3 episodi
Salve, sono Kevin Séjourné, dottore di ricerca in informatica e ingegnere senior di R&D presso Cloud Temple. Come potete immaginare, ho scritto molto codice negli ultimi 20 anni. Da appassionato esploratore di LLM, mi sono reso conto che ora possono scrivere codice per me. Tanto meglio! Ma poiché sono abituato a basarmi su osservazioni scientifiche, ho deciso di testare la qualità del loro lavoro.
Guarda i 3 episodi del mio studio:
- Episodio 1: Automatizzare il mio lavoro con gli LLM
- Episodio 2: Utilizziamo LLM per correggere il codice e ricostruire gli algoritmi dimenticati
- Episodio 3: Completamento del programma iniziale, test, test più severi
Episodio 3: Completamento del programma iniziale, test, test più severi
In questo articolo esploreremo come abbiamo completato il programma di backend generato con LLM, lo abbiamo testato e ne abbiamo ottimizzato la distribuzione con Docker. Vedremo anche come utilizzare GPT4o e altri strumenti per generare codice in modo più efficiente.
CI/CD : Utilizzo di Docker con GPT4o
È un programma backenddistribuzione basata su Docker è lo standard, prima di passare a devops. Chiediamo a GPT4o di creare un file Profilo Docker adattato al nostro programma.
Un'immagine basata su alpino:ultimo. Perché no? Tuttavia, la compilazione di codice nativo su alpino richiede musl come libreria standard invece di glibcassente su alpino.
musl non pone alcun problema particolare, ma il GPT-4 ha omesso l'opzione -obiettivo x86_64-unknown-linux-musl nel nave da carico. Senza questa opzione, sarebbe stato impossibile compilare il codice nell'immagine. alpino. Se non avessimo riscontrato questo problema, ci saremmo garantiti molte ore di scervellamento prima di capire perché il contenitore veniva utilizzato. docker non funziona anche se il programma è in esecuzione.
Perché evitare le distribuzioni Linux complete?
L'uso di una distribuzione Linux completa per un programma standalone è spesso eccessivo. Un'immagine da zero è più leggero e si adatta meglio alle nostre esigenze specifiche. Tuttavia, è essenziale comprendere il termine da zero per evitare fraintendimenti. GPT4o interpreta questa richiesta come la ricreazione di un nuovo file Profilo Docker da zero, invece di creare un file immagine a partire da zero.
Per ovviare a questo problema, diamo un'opzione di tipo Profilo Docker minimo a GPT4o con il solo costruire e il corsa e gli chiediamo di completarla in modo che la Profilo Docker funziona per lanciare il programma compilato con carico. File : Dockerfile Da clux/muslrust:stable come costruttore Da zero
Prompt : Potete correggere il mio file Docker in modo che compili il mio programma con cargo e lo lanci correttamente?
Questa operazione può essere eseguita in una nuova discussione con GPT4o, se necessario. Non è necessario conservare tutto il contesto, il Profilo Docker sarà sufficientemente standard da chiedere di modificarlo con i nomi reali in una fase di seconda generazione.
Problemi e soluzioni per i certificati SSL
Alcuni docker build / docker e scambi con GPT4o in seguito, ed eccoci con un prodotto completo. Si noti che si è perso molto tempo per un piccolo dettaglio di Certificati SSL che GPT4o non era in grado di risolvere. La seguente riga ci ha aiutato: COPY -from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ Ricerca con Google sono stati necessari per trovarlo. Ciò è probabilmente dovuto al fatto che Ruggine non è così diffuso nelle basi di apprendimento GPT4o e le immagini da zero o.
L'LLM ha fallito su un dettaglio fondamentale.
Funzionale, unitario, di integrazione
Per funzionamento correttopossiamo chiedere al modello di generare json normalmente inserito nel corpo della richiesta http. A tal fine, stiamo costruendo un tempestivamente costituito da un file di esempio json e dirgli quali variabili modificare. Questa parte non può essere eseguita da GPT4o per motivi di riservatezza, ma è abbastanza semplice da essere eseguita da llama3 7b Q4.
Possiamo quindi avere rapidamente json esempi. È possibile variare il formato dei numeri e il loro incapsulamento in Cifra doppia/doppia citazioneL'unico limite è la vostra immaginazione. L'unico limite è la vostra immaginazione.
L'unico problema dei test funzionali è che molto spesso dobbiamo tornare alla fase di correzione del codice dopo un test. Con questo metodo, in cui il codice viene generato o semi-generato, non si eseguono test unitari sulle funzioni in un file quadro integrato, siamo passati direttamente al test di integrazione. Nel nostro programma non c'è nessun algoritmo abbastanza complicato da avere comportamenti contorti.
Prestazioni
Il modo più semplice per verificare le prestazioni di un programma veloce è chiamarlo più volte e calcolare la sua velocità media di esecuzione. Il problema è che le chiamate con ricciolo sono così: mentre è vero; do time -p curl -i -X POST -data “@testfile.json” -H “User-Agent: mentre“ -H “autorizzazione: X-API-KEY tokensecret“ -H “content-type: application/json" -H "Accept-Encoding: gzip, deflate, br“ <http://localhost:8000/grafana/alert>; ; fatto lancia un processo ad ogni esecuzione, e il tempo totale di esecuzione del programma è inferiore a questo. Il mockup ha già spostato parte del problema della lentezza dal nostro programma principale. mockup non interagisce con un oggetto DB o con la rete.
È quindi necessario sostituire l'elemento mentre vero arricciare da un programma più veloce. Chiediamo a GPT4o questa generazione dalla linea di comando di prova che abbiamo; e diciamo a GPT4o che non vogliamo che usi il comando ricciolo. Otteniamo questo programma:
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();
ciclo{
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!("Stato: {} ", response.status());
}
}```
Una volta che il programma è stato compilato e testato per assicurarsi che funzioni correttamente, chiediamo a GPT4o di aggiungere, passo dopo passo, un conteggio del numero di iterazioni, un calcolo del tempo di esecuzione di ogni posta, un tempo medio di esecuzione e una rotazione sui file di prova:
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("Impossibile leggere il file"); serde_json::from_str(&data).expect("Impossibile analizzare JSON") }
[async_std::main]
async fn main() { let mut iteration = 0; let mut total_duration = 0; let client = Client::new();
ciclo {
iterazione += 1;
let json_index = (iterazione - 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("risposta fallita");
let duration = Instant::now() - start;
total_duration += duration.as_nanos();
println!(
"Iterazione: {} Stato: {} Durata: {:.2?} \tmed:{}",
iterazione,
response.status(),
duration.as_nanos(),
durata_totale / iterazione
);
}
}```
Abbiamo chiesto a GPT4o di aggiungere la rotazione ai file di test molto tempo dopo avergli chiesto di contare le iterazioni. Siamo lieti di vedere che non ha ricreato una variabile, ma ha utilizzato la variabile iterazione per creare la rotazione applicando un modulo.
La formattazione del codice è un artefatto del copia e incolla in VSCode (_Rust-analyser_) e poi nell'editor markdown.
Ora possiamo effettuare test di performance tra la versione originale e il nostro programma finale. I test di prestazione sono stati eseguiti su un portatile con un _I7_12g. Ogni test è stato eseguito per 10.000 iterazioni. Il tempo medio di esecuzione con il nostro Pitone è di 3.791.274 ns (nanosecondi). Mentre il tempo medio di esecuzione del nostro nuovo Ruggine è di 3.791.274 ns, 3,9 volte più veloce.
Sospettiamo che gran parte della differenza di prestazioni sia dovuta alla limitata capacità del programma di mockup di rispondere abbastanza rapidamente al nostro nuovo programma. Trasformiamolo in Ruggine. Abbiamo provato con lama3 70b su groq.com ma è stato molto difficile ottenere un semplice programma funzionale. Lo stesso prompt offerto a GPT4o ci ha permesso di ottenere un programma corretto al primo tentativo con solo qualche Importazione non utilizzato :
Riscrivere il seguente programma in Rust
Utilizza "actix
Non usa TcpListener::bind
Non crea una nuova "struct
Sostituisce gli id statici 666 e 999 con numeri generati casualmente
da 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"}]}
Ecco il testo corretto:
Si tratta di un approccio in cui si lascia il prompt nel programma Python originale per documentare ciò che accade a quel programma. Il programma generato viene quindi inserito in una cartella accanto ad esso.
I dettagli forniti su "Utilizza actix" o non utilizza "TcpListener::bind" sono il risultato dell'esperienza con lama3 70b.
Il programma risultante soddisfa le specifiche e non è più lungo della sua controparte. Pitone.
Possiamo quindi eseguire nuovamente i nostri test. Il tempo medio di esecuzione del nostro programma Pitone è di 2916122 ns. Mentre il tempo medio di esecuzione del nostro nuovo programma Ruggine è di 250992 ns, 11,6 volte più veloce. La dimensione dell'immagine Docker è stata aumentata da 195 MB per la versione Pitonen sulla base di un'immagine python:alpinela dimensione dell'immagine del programma standalone è di 13,4 MB sulla base di un'immagine graffio.
Più leggero, più veloce, con una superficie di attacco ridotta. Siamo stati in grado di trasformare e testare un programma più facilmente di quanto avremmo fatto a mano.
Conclusioni
Non tutti i corsi di laurea magistrale sono uguali. Tra velocità, prestazioni e riservatezza, bisogna sempre porsi alcune domande: Cosa stiamo facendo? Perché?
Velocità : groq.com 1200 gettoni/s con lama3 7b
Prestazioni : GPT4o sul portale AI sviluppato da Cloud Temple, le risposte richiedono una minore post-elaborazione.
Riservatezza : localmente in base alla vostra lama3 o il maestrale/_mixtral_.
La trasformazione ha richiesto dai 3 ai 4 giorni lavorativi, a seconda di come si contano le interruzioni.
Tempo di sviluppo stimato
Senza LLM: 12 giorni
Con LLM: 4 giorni
Non avremmo mai intrapreso una tale trasformazione in un linguaggio con così poche competenze se non avessimo avuto a disposizione potenti modelli linguistici per aiutarci. Assumere uno sviluppatore in base ai linguaggi di programmazione che padroneggia non sembra più rilevante.
Limiti e lezioni apprese
Sebbene gli LLM offrano vantaggi significativi, non sono infallibili. Le revisioni manuali restano essenziali per garantire la qualità e la sicurezza del codice prodotto.
Abbiamo una buona esperienza in molti linguaggi di programmazione, ma non in Rust. Possiamo dire che se dovessimo riscrivere questo programma in Java o Javascriptci sarebbe voluto almeno 3 volte di più.
L'uso di LLM triplica la produttività del codice. La produzione di codice non è l'unica attività degli sviluppatori, ma è comunque un buon affare. E in termini di impronta ambientale, il consumo di energia generato da questi LLM che scrivono codice per noi è molto inferiore al consumo di caffè che si sarebbe ottenuto triplicando il tempo di lavoro.