Kosten und Latenz: Budget, Cache, ein kleineres Modell
Kurz gesagt: Die Kosten eines Sprachmodells sind Tokens × Preis, getrennt für Eingabe und Ausgabe; ein Token-Budget, ein Antwort-Cache und ein kleineres Modell senken die Rechnung, und der Cache und das kleinere Modell senken auch die Antwortzeit.
Problem
Die Rechnung am Monatsende
Der Support-Assistent arbeitet seit einem Monat. Er antwortet richtig, und das Testset aus der Lektion über die Evaluation besteht. Dann kommen zwei Beschwerden: Die Rechnung für das Modell ist höher als erwartet, und Kunden warten etwa eine Sekunde auf eine Antwort.
Hier ist ein Aufruf, wie der Assistent ihn macht. Die Token-Preise sind Beispiele, in Euro pro Million Tokens:
const question = "Wie viel kostet die Lieferung?";const r = CLIENT.create({ model: LARGE, max_tokens: 1024, system: PROMPT + " " + ALL_DOCS, messages: [{ role: "user", content: question }] });const m = MODELS[LARGE];const euro = cost(m, r.usage);const input = r.usage.input_tokens * m.inPrice / 1_000_000;console.log(`Eingabe: ${r.usage.input_tokens} Tokens, Ausgabe: ${r.usage.output_tokens} Tokens`);console.log(`Kosten: ${euro.toFixed(5)} € (Anteil der Eingabe ${(100 * input / euro).toFixed(0)}%)`);console.log(`Beispielzeit: ${latency(m, r.usage)} ms`);Eingabe: 350 Tokens, Ausgabe: 40 TokensKosten: 0.00204 € (Anteil der Eingabe 69%)Beispielzeit: 1235 msDie Frage „Wie viel kostet die Lieferung?“ hat 10 Tokens, und die Eingabe hat 350. Ein Teil der Rechnung ist die Antwort, der größere Teil ist die Eingabe: Der System-Prompt und alle Dokumente des Shops reisen bei jeder Frage mit. Und im Log ist die Hälfte der Fragen gleich: wie viel die Lieferung kostet, wann der Support geöffnet ist.
Die Idee
Wohin das Geld und die Zeit gehen
Ein Modell wird nach Tokens bezahlt (die Lektion über Tokens), getrennt für die Eingabe und für die Ausgabe:
- die Eingabe ist alles, was du schickst: der System-Prompt, die Dokumente, der Verlauf des Gesprächs, die Frage. Die API hat kein Gedächtnis, deshalb wird das jedes Mal alles geschickt (die Lektion über den Kontext);
- die Ausgabe ist die Antwort. Ein Ausgabe-Token kostet meist ein Mehrfaches eines Eingabe-Tokens, hier das Vierfache.
Mit der Zeit ist es ähnlich, nur mit anderem Gewicht. Das Modell liest die Eingabe schnell und schreibt die Antwort Token für Token. Eine lange Antwort ist also langsam, und eine lange Eingabe ist vor allem teuer.
Daraus ergeben sich die drei Auswege, die die Lektion mit einem Programm misst:
- ein Token-Budget: weniger Eingabe (nur das, was nötig ist) und eine Obergrenze für die Ausgabe;
- ein Cache: Die gleiche Frage erreicht das Modell nicht;
- ein kleineres Modell für die einfachen Fragen: ein billigeres und schnelleres Token.
Ein Token-Budget ist eine im Voraus geplante Obergrenze für die Tokens eines Aufrufs, so wie du den Speicher oder die Antwortzeit einer Anfrage planst.
Sieh es dir an
Der Assistent im Code
Der falsche Client ist derselbe wie in der Lektion über die Evaluation: die Form der echten API, aufgezeichnete Antworten und grobe Tokens (jedes Wort sind 2 Tokens). Die Antworten sind nach einem Wort in der Frage aufgezeichnet:
// ein falscher Client: die Form der echten API, die Antworten sind aufgezeichnet// grob: jedes Wort sind 2 Tokensfunction tokens(text) { return 2 * text.trim().split(/\s+/).length;}
// max_tokens schneidet die Antwort abfunction reply(text, maxTokens, input) { const words = text.split(" "); if (2 * words.length <= maxTokens) return { text, stop_reason: "end_turn", usage: { input_tokens: input, output_tokens: 2 * words.length } }; const kept = Math.floor(maxTokens / 2); const cut = words.slice(0, kept).join(" "); return { text: cut, stop_reason: "max_tokens", usage: { input_tokens: input, output_tokens: 2 * kept } };}
class FakeLlm { // eine aufgezeichnete Antwort: Modell ("" = jedes), ein Wort in der Frage, Text, den der Prompt braucht constructor(recorded) { this.recorded = recorded; }
create({ model, max_tokens, system, messages }) { const question = messages[messages.length - 1].content; let input = tokens(system); for (const m of messages) input += tokens(m.content); for (const r of this.recorded) { const forModel = r.model === "" || r.model === model; if (forModel && question.includes(r.keyword) && system.includes(r.needs)) return reply(r.text, max_tokens, input); } return reply("(keine aufgezeichnete Antwort)", max_tokens, input); }}const RECORDED = [ { model: "", keyword: "kostet", needs: "", text: "Die Lieferung an eine Adresse kostet 4,90 €, an eine Filiale 3,50 €, und ab 50 € ist sie kostenlos." }, { model: "", keyword: "Nachnahme", needs: "", text: "Ja, per Nachnahme geht es. Die Gebühr beträgt 1,50 €." }, { model: "", keyword: "zurück", needs: "", text: "Du kannst die Ware bis 14 Tage nach der Lieferung zurückgeben, wenn sie unbenutzt ist und das Etikett hat." }, { model: "", keyword: "geöffnet", needs: "", text: "Der Support ist von Montag bis Freitag von 9 bis 18 Uhr geöffnet." }, { model: "", keyword: "1042", needs: "", text: "Bestellung 1042 ist unterwegs und kommt am Donnerstag an." }, { model: "", keyword: "1077", needs: "", text: "Bestellung 1077 wird gepackt und geht morgen raus." }, { model: "", keyword: "beschädigt", needs: "", text: "Das tut mir leid! Mach ein Foto vom Paket und vom Etikett und schreib uns mit der Bestellnummer. Wir schicken dir ein neues oder erstatten dir das Geld innerhalb von 5 Werktagen." }, { model: "", keyword: "Tage", needs: "", text: "Pakete kommen in 2 bis 4 Werktagen an." }, { model: "", keyword: "Großhandel", needs: "", text: "Ich weiß es nicht, ich verbinde dich mit einem Kollegen." }, { model: "", keyword: "Mastercard", needs: "", text: "Ja, wir akzeptieren Visa und Mastercard." },];Die Dokumente des Shops bestehen aus fünf Abschnitten. Jeder hat Wörter, an denen man ihn erkennt:
const PROMPT = "Du bist der Support-Assistent eines Online-Shops. Antworte kurz und nur aus den Dokumenten. Steht die Antwort nicht darin, sag: „Ich weiß es nicht, ich verbinde dich mit einem Kollegen.“";// ein Abschnitt der Dokumente und die Wörter, an denen man ihn erkenntconst DOCS = [ { triggers: ["lieferung", "paket"], text: "Lieferung: Pakete kommen in 2 bis 4 Werktagen an. Die Lieferung an eine Adresse kostet 4,90 €, an eine Filiale des Kuriers 3,50 €, und ab 50 € ist sie kostenlos. Ein Paket wird anhand seiner Nummer verfolgt." }, { triggers: ["zurück", "beschädigt"], text: "Rückgabe: Ware kann bis 14 Tage nach der Lieferung zurückgegeben werden, wenn sie unbenutzt ist und das Etikett hat. Ein beschädigtes Paket wird fotografiert und dem Support gemeldet. Das Geld geht innerhalb von 5 Werktagen auf die Karte zurück." }, { triggers: ["zahl", "karte", "mastercard"], text: "Zahlung: Wir akzeptieren Visa und Mastercard. Die Nachnahme kostet 1,50 € Gebühr. Die Kartenzahlung ist geschützt, und der Betrag wird abgebucht, wenn die Bestellung bestätigt wird." }, { triggers: ["bestellung"], text: "Bestellungen: Der Status einer Bestellung steht im Profil. Der Support prüft eine Bestellung nur anhand ihrer Nummer und fragt nie nach Kartendaten." }, { triggers: ["geöffnet", "erreichbar"], text: "Support: Wir arbeiten von Montag bis Freitag, von 9 bis 18 Uhr. Schreib uns an help@shop.example." },];const ALL_DOCS = DOCS.map((doc) => doc.text).join(" ");Die Modelle, die Preise und die Zeiten. Alle Zahlen hier sind Beispiele, und die Zeit kommt aus einer Formel, sie ist nicht gemessen:
// Beispielpreise in € pro 1.000.000 Tokens und Beispielzeit (nicht gemessen)const LARGE = "model-large", SMALL = "model-small";const MODELS = { [LARGE]: { inPrice: 4.00, outPrice: 16.00, baseMs: 400, inputPerMs: 10, msPerToken: 20 }, [SMALL]: { inPrice: 0.40, outPrice: 1.60, baseMs: 150, inputPerMs: 40, msPerToken: 5 },};const CACHE_MS = 5;// die Kosten eines Aufrufs in €function cost(m, u) { return (u.input_tokens * m.inPrice + u.output_tokens * m.outPrice) / 1_000_000;}
// Beispielzeit in ms: Start + Eingabe lesen + Erzeugenfunction latency(m, u) { return m.baseMs + Math.floor(u.input_tokens / m.inputPerMs) + u.output_tokens * m.msPerToken;}Die Antwort für Bestellung 1042 ist aufgezeichnet. Im echten Assistenten kommt der Status von einem Tool (die Lektion über Tools).
1_000_000 ist dasselbe wie 1000000. In JavaScript ergibt / immer einen Bruch, deshalb lässt Math.floor den Rest weg, wie die Ganzzahldivision in den anderen Sprachen.Probier es aus
Ergänze
Die Kosten sind die Eingabe-Tokens mal der Eingabepreis plus die Ausgabe-Tokens mal …?
return (u.input_tokens * m.inPrice + u.output_tokens * ) / 1_000_000;Die Ausgabe hat ihren eigenen Preis, hier 16 € pro Million gegenüber 4 € für die Eingabe. Wenn du die Ausgabe zum Preis der Eingabe rechnest, fällt die Rechnung für die Antworten viermal zu niedrig aus, und sie sind pro Token der teurere Teil.
Sieh es dir an
Ein Tag
Ein Tag im Support ist eine Stichprobe von 20 Fragen. Manche wiederholen sich, manchmal klein geschrieben oder mit einem Leerzeichen zu viel. Drei betreffen eine bestimmte Bestellung:
const questions = [ "Wie viel kostet die Lieferung?", "Wann ist der Support geöffnet?", "Wo ist Bestellung 1042?", "wie viel kostet die lieferung?", "Kann ich per Nachnahme zahlen?", "Bis wann kann ich die Ware zurückgeben?", "Wie viel kostet die Lieferung?", "Mein Paket kam beschädigt an, was soll ich tun?", "Wann ist der Support geöffnet?", "Wo ist Bestellung 1077?", "Akzeptiert ihr Mastercard?", "Wie viel kostet die Lieferung? ", "Wie viele Tage braucht ein Paket nach Hamburg?", "Kann ich per Nachnahme zahlen?", "Verkauft ihr auch im Großhandel?", "Bis wann kann ich die Ware zurückgeben?", "Wo ist Bestellung 1042?", "Wann ist der Support geöffnet?", "Wie viel kostet die Lieferung an eine Filiale?", "Wie gebe ich die Ware zurück?",];day geht die Fragen durch und addiert die Kosten und die Zeit. Die drei Flags schalten die drei Auswege ein, und im Moment sind sie aus:
function day(questions, budget, cache, route) { const answers = new Map(); let euro = 0; let ms = 0, hits = 0, onSmall = 0; for (const question of questions) { const k = key(question); // Treffer: kein Modell if (cache && answers.has(k)) { hits++; ms += CACHE_MS; continue; } const model = route ? pickModel(question) : LARGE; const docs = budget ? relevant(question) : ALL_DOCS; const maxTokens = budget ? 200 : 1024; const msg = { role: "user", content: question }; const r = CLIENT.create({ model, max_tokens: maxTokens, system: PROMPT + " " + docs, messages: [msg], }); const m = MODELS[model]; euro += cost(m, r.usage); ms += latency(m, r.usage); if (model === SMALL) onSmall++; // eine Bestellung gehört einem Kunden: kein Cache if (cache && !k.includes("bestellung")) answers.set(k, r.text); } return { euro, ms, hits, onSmall };}function row(name, t, n) { console.log(`${name}: ${(t.euro / n * 1000).toFixed(2)} € pro 1000 Fragen, im Schnitt ${Math.floor(t.ms / n)} ms`);}
function month(t, n) { console.log(`Monat bei 3000 Fragen am Tag: ${(t.euro / n * 3000 * 30).toFixed(2)} €`);}const all = day(questions, false, false, false);row("großes Modell, alle Dokumente", all, questions.length);month(all, questions.length);großes Modell, alle Dokumente: 1.88 € pro 1000 Fragen, im Schnitt 1030 msMonat bei 3000 Fragen am Tag: 169.27 €Die Kosten pro 1000 Fragen liegen unter 2 €. Bei 3000 Fragen am Tag sind es im Monat etwa 169 €, und eine Frage wartet im Schnitt etwa eine Sekunde.
Die Idee
Ein Token-Budget
Der erste Ausweg ist die Eingabe. Die Frage zur Lieferung braucht nur den Abschnitt zur Lieferung. relevant nimmt die Abschnitte, deren Wörter in der Frage vorkommen, und wenn es keinen gibt, schickt es alle (die Frage zum Großhandel muss alles sehen, um „Ich weiß es nicht“ zu sagen):
// Budget: nur die Abschnitte, deren Wörter in der Frage vorkommenfunction relevant(question) { const low = question.toLowerCase(); const parts = DOCS.filter((doc) => doc.triggers.some((word) => low.includes(word))).map((doc) => doc.text); return parts.length > 0 ? parts.join(" ") : ALL_DOCS;}Die zweite Obergrenze ist max_tokens: höchstens 200 Tokens pro Antwort. Sie macht die Antworten nicht kürzer. Sie sagt, wie viel ein Aufruf höchstens kosten kann.
const all = day(questions, false, false, false);row("großes Modell, alle Dokumente", all, questions.length);const budget = day(questions, true, false, false);row("+ Token-Budget", budget, questions.length);großes Modell, alle Dokumente: 1.88 € pro 1000 Fragen, im Schnitt 1030 ms+ Token-Budget: 1.05 € pro 1000 Fragen, im Schnitt 1010 msDie Kosten sinken um fast die Hälfte. Die Zeit bewegt sich kaum: Die Eingabe wird schnell gelesen, und die Antwort ist dieselbe.
Hier werden die Abschnitte nach Wörtern ausgewählt. Wie du sie nach Bedeutung mit Vektoren auswählst, steht im Modul über die semantische Suche (RAG).
doc.triggers.some((word) => low.includes(word)) ist wahr, wenn mindestens ein Wort des Abschnitts in der Frage vorkommt.Probier es aus
Sag die Ausgabe voraus
max_tokens ist 10, und die Frage betrifft ein beschädigtes Paket. Was wird ausgegeben?
const r = CLIENT.create({ model: LARGE, max_tokens: 10, system: PROMPT, messages: [{ role: "user", content: "Mein Paket kam beschädigt an, was soll ich tun?" }] });console.log(r.text);console.log(`${r.stop_reason}, ${r.usage.output_tokens}`);Die Obergrenze stoppt die Antwort bei 10 Tokens, hier 5 Wörter, mitten im Satz. stop_reason ist max_tokens, nicht end_turn. Die Obergrenze schützt die Rechnung, aber eine zu niedrige Obergrenze liefert eine halbe Antwort. Das Programm muss stop_reason prüfen, bevor es dem Kunden die Antwort zeigt.
Die Idee
Ein Antwort-Cache
Der zweite Ausweg ist die Wiederholung. Wurde dieselbe Frage vor Kurzem gestellt, ist die Antwort dieselbe: Du musst das Modell nicht noch einmal fragen. Das ist Cache-Aside aus der Lektion „Caching“: Du schaust im Cache nach, und bei einem Fehlschlag fragst du das Modell und behältst eine Kopie.
Der Schlüssel ist die Frage in Kleinbuchstaben mit je einem Leerzeichen, sodass „wie viel kostet die lieferung? “ dasselbe ist wie „Wie viel kostet die Lieferung?“:
// der Cache-Schlüssel: Kleinbuchstaben, je ein Leerzeichenfunction key(question) { return question.trim().toLowerCase().split(/\s+/).join(" ");}In day sind das die Zeilen mit answers: Ein Treffer gibt die gespeicherte Antwort in 5 ms und gratis zurück. Eine Frage zu einer Bestellung wird nicht gecacht. Die Antwort gehört einem Kunden, und der Status ändert sich.
const all = day(questions, false, false, false);row("großes Modell, alle Dokumente", all, questions.length);const budget = day(questions, true, false, false);row("+ Token-Budget", budget, questions.length);const cached = day(questions, true, true, false);row("+ Cache", cached, questions.length);console.log(`aus dem Cache: ${cached.hits} von ${questions.length}`);großes Modell, alle Dokumente: 1.88 € pro 1000 Fragen, im Schnitt 1030 ms+ Token-Budget: 1.05 € pro 1000 Fragen, im Schnitt 1010 ms+ Cache: 0.69 € pro 1000 Fragen, im Schnitt 637 msaus dem Cache: 7 von 207 von 20 Fragen kommen aus dem Cache. Jetzt sinkt auch die Zeit, weil ein Treffer nicht auf das Modell wartet.
Probier es aus
Finde den Fehler
Ein Kollege vereinfacht den Cache. Die Kostentests bestehen, und einen Tag später beschwert sich ein Kunde, der Assistent habe ihm den Status einer fremden Bestellung genannt. Welche Zeile hat den Fehler?
Wähle die Zeile mit dem Fehler.
Der Cache behält jede Antwort, auch die Antworten zu Bestellungen. Im echten Assistenten ist die Frage „Wo ist meine Bestellung?“ für alle Kunden dieselbe, und die Antwort gehört dem Ersten, der gefragt hat. Der nächste Kunde bekommt den Status eines anderen, und der erste bekommt einen Tag später einen alten. Eine Antwort zu einer Bestellung wird nicht gecacht: Das ist die Prüfung, die der Kollege entfernt hat.
Die Idee
Ein kleineres Modell für das Einfache
Der dritte Ausweg ist das Modell. Das kleine Modell ist pro Token 10-mal billiger und schreibt schneller. In der Lektion über die Evaluation hat das Testset gezeigt, wo es gut ist: beim Preis der Lieferung, bei der Zahlung, bei der Rückgabe. Und wo nicht: bei der Lieferzeit und bei der Frage außerhalb der Dokumente. Es bekommt also nur die freigegebenen Themen:
// die einfachen Themen: das Testset hat gezeigt, dass das kleine Modell sie schafftconst EASY = ["kostet", "Nachnahme", "zurück", "Mastercard"];
function pickModel(question) { return EASY.some((word) => question.includes(word)) ? SMALL : LARGE;}const all = day(questions, false, false, false);row("großes Modell, alle Dokumente", all, questions.length);const budget = day(questions, true, false, false);row("+ Token-Budget", budget, questions.length);const cached = day(questions, true, true, false);row("+ Cache", cached, questions.length);const routed = day(questions, true, true, true);row("+ kleines Modell für das Einfache", routed, questions.length);console.log(`beim kleinen Modell: ${routed.onSmall} von ${questions.length}`);month(all, questions.length);month(routed, questions.length);großes Modell, alle Dokumente: 1.88 € pro 1000 Fragen, im Schnitt 1030 ms+ Token-Budget: 1.05 € pro 1000 Fragen, im Schnitt 1010 ms+ Cache: 0.69 € pro 1000 Fragen, im Schnitt 637 ms+ kleines Modell für das Einfache: 0.41 € pro 1000 Fragen, im Schnitt 418 msbeim kleinen Modell: 6 von 20Monat bei 3000 Fragen am Tag: 169.27 €Monat bei 3000 Fragen am Tag: 36.54 €Der Monat sinkt von etwa 169 € auf etwa 37 €, und die durchschnittliche Zeit von 1030 auf 418 ms. Die Zahlen stammen aus Beispielpreisen und einer Beispielformel, aber die Reihenfolge der Auswege ist echt.
Sieh es dir an
Die Rechnung, Ausweg für Ausweg
function day(questions, budget, cache, route) { const answers = new Map(); let euro = 0; let ms = 0, hits = 0, onSmall = 0; for (const question of questions) { const k = key(question); // Treffer: kein Modell if (cache && answers.has(k)) { hits++; ms += CACHE_MS; continue; } const model = route ? pickModel(question) : LARGE; const docs = budget ? relevant(question) : ALL_DOCS; const maxTokens = budget ? 200 : 1024; const msg = { role: "user", content: question }; const r = CLIENT.create({ model, max_tokens: maxTokens, system: PROMPT + " " + docs, messages: [msg], }); const m = MODELS[model]; euro += cost(m, r.usage); ms += latency(m, r.usage); if (model === SMALL) onSmall++; // eine Bestellung gehört einem Kunden: kein Cache if (cache && !k.includes("bestellung")) answers.set(k, r.text); } return { euro, ms, hits, onSmall };}Schritt für Schritt, in Worten
- Ein Aufruf: Prompt, alle Dokumente und die Frage gehen hinein (350 Tokens), die Antwort kommt heraus (40). Die Dokumente machen den größten Teil der Rechnung aus: 0,00204 €.
- Das Token-Budget: nur der Abschnitt, nach dem die Frage fragt, und eine Obergrenze für die Antwort. 144 Tokens Eingabe, 0,00122 € für dieselbe Antwort.
- Ein Tag mit 20 Fragen, pro 1000 Fragen: 1,88 € mit allen Dokumenten, 1,05 € mit dem Budget. Die Zeit bewegt sich kaum: Sie besteht vor allem aus der Antwort.
- Der Cache: 7 von 20 Fragen sind Wiederholungen und erreichen das Modell nicht. Fragen zu einer Bestellung kommen nie in den Cache.
- Das kleine Modell übernimmt die einfachen Themen, die das Testset freigegeben hat: 6 von 20 Fragen. Es ist billiger und schneller.
- Von 1,88 € auf 0,41 € pro 1000 Fragen (78 % weniger) und von 1030 auf 418 ms. Die Preise und Zeiten sind Beispiele.
Die Idee
Bei dir ausführen
Die echte Antwort sagt, wie viele Tokens verbraucht wurden. Hier ist die Anfrage mit dem Budget für die Frage zur Lieferung und die Antwort. Der Text und die Zahlen stammen vom falschen Client:
{ "model": "<Name des Modells>", "max_tokens": 200, "system": "<der System-Prompt>", "messages": [ { "role": "user", "content": "Wie viel kostet die Lieferung?" } ]}{ "content": [ { "type": "text", "text": "Die Lieferung an eine Adresse kostet 4,90 €, an eine Filiale 3,50 €, und ab 50 € ist sie kostenlos." } ], "stop_reason": "end_turn", "usage": { "input_tokens": 144, "output_tokens": 40 }}Die Felder sind usage.input_tokens und usage.output_tokens, und stop_reason sagt, ob die Antwort vollständig ist (end_turn) oder die Obergrenze sie abgeschnitten hat (max_tokens). Damit berechnet das Programm dieselbe Rechnung wie hier, nur mit echten Zahlen.
Anbieter liefern offizielle SDKs für Java, C#, Python und JavaScript mit derselben Form: Nachrichten, Rollen, Tokens, Tools. Die genauen Namen der Klassen und Methoden stehen in der Dokumentation des Anbieters. Hier geht die Anfrage über den Standard-HTTP-Client der Sprache, ohne SDK. Die Adresse und das Modell sind Platzhalter:
// nur zur Ansicht: die Adresse und der Header für den Schlüssel stehen in der Doku des Anbietersconst ENDPOINT = "https://<anbieter>/...";
async function ask(system, question) { const response = await fetch(ENDPOINT, { method: "POST", headers: { "content-type": "application/json", authorization: "Bearer " + process.env.LLM_API_KEY, }, body: JSON.stringify({ model: "<Name des Modells>", max_tokens: 200, system, messages: [{ role: "user", content: question }], }), }); return response.json(); // JSON: content, stop_reason, usage}
const reply = await ask(PROMPT + " " + relevant(question), question);const { input_tokens, output_tokens } = reply.usage;console.log(reply.stop_reason, input_tokens, output_tokens);Dieser Code läuft nicht in der Codeprüfung der Seite. Der Schlüssel steht in der Umgebungsvariablen LLM_API_KEY, nicht im Code (die Lektion „Geheimnisse im Code und in Git“), du brauchst ein Konto bei einem Anbieter, und jeder Lauf kostet Geld.
fetch ist ab Node.js 18 eingebaut und gibt ein Promise zurück, daher await.Die Idee
Wann es weh tut
- Ein Budget, das abschneidet, was nötig ist. Wenn
relevantden Abschnitt mit der Antwort verfehlt, sagt das Modell „Ich weiß es nicht“ oder, schlimmer, erfindet etwas. Ein zu niedrigesmax_tokensliefert eine halbe Antwort. Jede Änderung am Budget läuft durch das Testset. - Ein Cache, der lügt. Der Preis der Lieferung ändert sich, und der Cache liefert den alten. Ein Cache hat ein Verfallsdatum (TTL) und wird geleert, wenn sich die Dokumente ändern. Antworten, die vom Kunden oder von der Zeit abhängen, kommen nicht hinein.
- Ein kleines Modell ohne Testset. „Es sieht einfach aus“ ist kein Argument. Das kleine Modell übernimmt ein Thema erst, nachdem das Testset es bestanden hat, und das Testset läuft bei jeder Änderung neu.
- Sparen, das sich nicht lohnt. Bekommt der Assistent 50 Fragen am Tag, sind es einige Euro im Monat. Die Komplexität von Cache und Router kann mehr kosten als das. Miss zuerst.
Genauer, für Neugierige
Den Anfang des Prompts cachen. Viele Anbieter behalten den wiederholten Anfang des Prompts (den System-Prompt und die Dokumente) und berechnen weniger, wenn er noch einmal gelesen wird. Das ist ein anderer Cache: Das Modell antwortet weiterhin, aber die Eingabe ist billiger. Das funktioniert, wenn der Anfang bis zum letzten Zeichen identisch ist, deshalb stehen die veränderlichen Teile (die Frage, der Name des Kunden) am Ende.
Ein Cache nach Bedeutung. „Wie gebe ich die Ware zurück?“ und „Bis wann kann ich die Ware zurückgeben?“ sind verschiedene Schlüssel. Ein Cache mit Vektoren (die Lektion über Vektoren) behandelt sie als eine Frage. Er findet mehr Treffer, und auch falsche: „Kann ich Schuhe zurückgeben?“ und „Kann ich ein Geschenk zurückgeben?“ liegen nah beieinander, und die Antworten können sich unterscheiden.
Eine gestreamte Antwort. Die Antwort kann Wort für Wort ankommen (Streaming). Die Gesamtzeit ist dieselbe, aber der Kunde sieht die ersten Wörter nach etwa hundert ms statt nach einer Sekunde. Das ist Tempo für den Menschen, nicht für die Rechnung.
Batches. Arbeit, auf die kein Kunde wartet (10.000 Bewertungen über Nacht zusammenfassen), kann über einen Batch-Modus laufen: langsamer und billiger.
Die Zeit in der Produktion. Der Durchschnitt versteckt das Ende der Verteilung: 95 % der Antworten können unter einer Sekunde liegen und 5 % über 5 Sekunden. Miss Perzentile, wie in der Lektion „Nichtfunktionale Anforderungen, die du messen kannst“ aus „Software-Engineering“. Kosten und Zeit in der Produktion zu beobachten gehört zu MLOps und wird in ZeroToOps unterrichtet.
[LARGE]: {...} in einem Objekt ist ein berechneter Schlüssel: Der Name der Eigenschaft ist der Wert von LARGE.Übung
Welcher Ausweg spart am meisten
Löse es in deinem Editor.
Das Programm wendet die Auswege kumulativ an, du siehst also nicht, wie viel jeder allein bringt. Starte day dreimal, jedes Mal mit nur einem Flag, und gib die Zeilen neben der Zeile ohne Auswege aus:
großes Modell, alle Dokumente: 1.88 € pro 1000 Fragen, im Schnitt 1030 msnur Budget: 1.05 € pro 1000 Fragen, im Schnitt 1010 msnur Cache: 1.21 € pro 1000 Fragen, im Schnitt 650 msnur kleines Modell: 0.92 € pro 1000 Fragen, im Schnitt 604 msAllein ist das kleine Modell am billigsten, und das Budget kommt danach nah heran. Das Budget spart kaum Zeit, und der Cache und das kleine Modell senken sie am stärksten, weil sie nicht darauf warten, dass das große Modell schreibt. Die drei sparen an verschiedenen Stellen, deshalb bringen sie zusammen am meisten.
Weitere Aufgaben
- Noch ein einfaches Thema. Nimm „geöffnet“ in die einfachen Themen auf. Um wie viel sinken die Kosten? Was musst du tun, bevor du es ausliefern kannst? (Hinweis: die Lektion über die Evaluation.)
- Eine Obergrenze für den Tag. Hör auf, das große Modell zu fragen, sobald die Kosten für den Tag 0,01 € überschreiten, und antworte „Ich verbinde dich mit einem Kollegen“. Bei wie vielen Fragen gibt es keine Antwort vom Modell?
Test
Teste dich
Verwandte Lektionen
Abschnitt „Verwandte Lektionen“- Baut auf: Text in Stücken: Tokens · Das Kontextfenster · Ein Modell über eine API aufrufen · Evaluation: ein Testset für den Assistenten
- Geht weiter in: Dokumente in Abschnitte teilen (Chunking) · RAG: Antworten aus den Dokumenten · RAG bewerten: Wird der Abschnitt gefunden, und ist die Antwort belegt · Der Agent-Loop: Plan, Aktion, Beobachtung
- Aufgaben zum Thema: Anwendungen mit LLMs