Kosten und Latenz messen, lokale Modelle, EU AI Act, M5-Abschluss
Track KI · M5 Betrieb, Bausteine 03 bis 05 und Abschluss-Check 06 · ca. 70 Min. plus Projektaufgabe
Worum es geht
Ein Kunde fragt dich nicht, ob dein System “gut funktioniert”. Er fragt: Was kostet ein Pilot, wie schnell antwortet es, und dürfen unsere Daten dafür das Haus verlassen? Die Quelle (M5, Bausteine 03 bis 05) beantwortet das mit drei Werkzeugen:
- Messen: Latenz als p50 und p95 (Perzentile,
percentiles) statt nur als Durchschnitt, Kosten für 100 Anfragen hochgerechnet. - Lokales Modell (Ollama): keine Daten verlassen das eigene System, keine Kosten pro Token, aber weniger Qualität bei schwierigen Aufgaben.
- EU AI Act: eine kurze Risikoeinstufung und Dokumentation als Teil eines seriösen Angebots.
Im Browser gibt es kein echtes Modell und keine verlässliche Stoppuhr. Darum rechnest du in dieser Lektion aus einem Log von Aufrufen: Jede Zeile hat Tokens und Latenz, alles Weitere ist Rechnen. Genau so wertest du später auch echte Messungen aus. Die echte Messung gegen Ollama ist eine Lokal-Übung. Am Ende steht die Projektaufgabe, die M5 abschließt.
Container und Tracing behandelt ki/16a und ki/16b (Docker, Observability), Spans kennst du aus ki/15b. Die Kostenrechnung je Aufruf (kosten_von, Preise je 1000 Token) kommt aus ki/07a. Hier setzt du sie auf viele Aufrufe an.
Von JS/TS her gedacht
| Idee | JS/TS | Python |
|---|---|---|
| Zeit messen | performance.now() |
time.perf_counter() (hier nicht im Browser, siehe unten) |
| Sortieren von Zahlen | [3, 10, 2].sort() sortiert als Text: [10, 2, 3] |
sorted([3, 10, 2]) sortiert Zahlen: [2, 3, 10] |
| Aufrufe parallel | await Promise.all(gruppe.map(...)) |
await asyncio.gather(*gruppe) (ki/03) |
HTTP an localhost |
fetch("http://localhost:11434/...") |
urllib.request oder openai.OpenAI(base_url=...) |
| Durchschnitt | arr.reduce((a, b) => a + b) / arr.length |
sum(werte) / len(werte) |
Das Perzentil in JS, mit Node 20 ausgeführt. Der Vergleich (a, b) => a - b ist wichtig, ohne ihn sortiert JS Zahlen alphabetisch:
const latenzen = [1.0, 2.0, 0.5, 1.5, 3.0, 0.5, 9.0, 1.0, 1.5, 0.8];
const sortiert = [...latenzen].sort((a, b) => a - b);
const perzentil = (p) => sortiert[Math.ceil(p * sortiert.length / 100) - 1];
console.log(perzentil(50), perzentil(95));Ausgabe: 1 9. Die Werte sind leicht anders als im Log gleich (letzter Wert 0.8 statt 0.5), der Rechenweg ist derselbe. Dieselbe Rechnung in Python bauen wir gleich.
Konzept
Schritt 1: Ein Log statt einer Stoppuhr
Die Quelle misst mit time.perf_counter() um jeden Aufruf herum und legt pro Aufruf latenz_s und kosten_eur in eine Liste. Das bleibt der Weg für echte Läufe. Im Browser ist die Zeit aber unzuverlässig, und es gibt kein Modell. Du arbeitest deshalb mit einem Log: eine Liste von dicts, eine Zeile pro Aufruf. Die Zahlen sind von Hand ausgedacht, aber die Rechnung ist dieselbe wie bei echten Daten.
Zehn Aufrufe, einer davon (Zeile 7) mit 9 Sekunden und vielen Output-Token. Er ist der Ausreißer, um den es gleich geht.
Schritt 2: p50 und p95 statt Durchschnitt
Der Durchschnitt (mean) versteckt Ausreißer. Ein Perzentil (percentile) beantwortet eine andere Frage: p50 ist die Zeit, die die Hälfte der Aufrufe unterbietet (der Median). p95 ist die Zeit, die 95 Prozent der Aufrufe unterbieten. Anders gesagt: So schlecht sind die langsamsten 5 Prozent, und genau die fallen einem Kunden zuerst auf.
Es gibt mehrere Verfahren, ein Perzentil zu berechnen. Die Quelle nimmt das einfachste, das Nearest-Rank-Verfahren: Sortiere die Werte. Berechne den Rang ceil(p * n / 100). Nimm den Wert an dieser Stelle (der Rang zählt ab 1, der Index ab 0, also rang - 1). Bei 100 Werten ergibt das genau sortiert[49] für p50 und sortiert[94] für p95, wie in der Quelle. Bibliotheken wie numpy interpolieren standardmäßig zwischen Werten und liefern darum leicht andere Zahlen. Wichtig ist nur: Verfahren festlegen und dokumentieren.
Ausgabe: die sortierte Liste [0.5, 0.5, 0.5, 1.0, 1.0, 1.5, 1.5, 2.0, 3.0, 9.0], dann p50: 1.0, p95: 9.0 und Durchschnitt: 2.05. Der Durchschnitt von 2,05 Sekunden klingt harmlos. Die Hälfte der Aufrufe ist aber in einer Sekunde fertig, und die langsamsten 5 Prozent brauchen 9 Sekunden. Beide Zahlen zusammen beschreiben das System ehrlich.
Zwei Dinge fallen auf. Erstens: Bei nur 10 Werten ist p95 schlicht der größte Wert (ceil(9.5) = 10). Ein aussagekräftiges p95 braucht deutlich mehr Aufrufe, darum die 100 in der Quelle. Zweitens steht im Kommentar “erst multiplizieren, dann teilen”. Das hat einen Grund: p / 100 * n rechnet mit Gleitkommazahlen und kann knapp über einer ganzen Zahl landen. math.ceil(55 / 100 * 100) ergibt 56 statt 55, weil 55 / 100 * 100 als 55.00000000000001 herauskommt. Mit p * n / 100 bleibt bei ganzzahligem p und n ein ganzzahliges Ergebnis exakt.
Schritt 3: Kosten für 100 Anfragen
Die Kosten je Aufruf kennst du aus ki/07a: input_tokens / 1000 * preis_input + output_tokens / 1000 * preis_output. Für den Kunden rechnest du hoch: Gesamtkosten des Logs, geteilt durch die Zahl der Aufrufe, mal 100.
Ausgabe: 0.0863 0.8625. Die zehn Aufrufe kosten zusammen 0,0863, hochgerechnet kosten 100 Aufrufe 0,8625 (in der Währung deines Anbieters, Preise nur Beispielwerte). Das ist die Zahl für das Angebot: Mal die erwartete Zahl der Anfragen pro Monat, und du hast eine erste Kostenschätzung. Sie ist nur so gut wie die Testfragen im Log. Sind die Testfragen kürzer als die echten, ist auch die Schätzung zu niedrig.
Schritt 4: Seriell oder parallel
Die Stolperfalle der Quelle: 100 Anfragen nacheinander (seriell) messen etwas anderes als 100 gleichzeitig (parallel). Die Quelle misst parallel in Fünfergruppen mit asyncio.gather. Was ändert sich dabei, wenn du aus einem Log rechnest?
- Die Gesamtkosten ändern sich nicht. Jeder Aufruf verbraucht dieselben Tokens, egal wann er läuft.
- Die Gesamtdauer ändert sich. Eine Gruppe läuft gleichzeitig, sie ist fertig, wenn ihr langsamster Aufruf fertig ist. Die nächste Gruppe startet erst danach.
Seriell ist die Dauer die Summe aller Latenzen. Parallel ist sie die Summe der Maxima je Gruppe:
Ausgabe: 8.5 5.0. Gruppe 1 sind [1.0, 2.0, 0.5] mit Maximum 2,0, Gruppe 2 sind [1.5, 3.0, 0.5] mit Maximum 3,0, zusammen 5,0. Dieselbe Rechnung für unser log mit Gruppen von 5 ergibt 12,0 Sekunden statt 20,5 seriell (der 9-Sekunden-Ausreißer bestimmt seine Gruppe allein).
Eine ehrliche Einschränkung: Das ist ein Rechenmodell. In echt werden einzelne Aufrufe unter Last oft langsamer (Rate-Limits, überlastete Server, ein einzelner Server für ein lokales Modell). Darum misst du für das Angebot beide Läufe wirklich und rechnest nicht nur. Das Modell hilft, die Zahlen zu lesen und Erwartungen zu setzen.
Schritt 5: Cloud-Modell oder lokales Modell (Ollama)
Die Quelle nennt zwei Gründe für ein lokal laufendes, kleineres Modell: besonders sensible Daten (siehe PII in M3), weil keine Daten das eigene System verlassen, und Kosten bei hohem Volumen, weil keine Kosten pro Token anfallen. Ollama ist ein Programm, das lokale Modelle bereitstellt und eine OpenAI-kompatible Schnittstelle anbietet. Das Laden eines Modells ist ein einmaliger Schritt, danach änderst du im Anwendungscode nur die Basis-URL und den Modellnamen:
# einmalig, im Terminal (großer Download, nicht in diesem Kurs automatisch):
# ollama pull llama3.1:8b
import openai
client = openai.OpenAI(base_url="http://localhost:11434/v1", api_key="ollama")
response = client.chat.completions.create(
model="llama3.1:8b",
messages=[{"role": "user", "content": frage}],
)(Code nach der Quelle, nicht ausführbar im Browser. Der Modellname llama3.1:8b steht in der Quelle, bitte prüfen, ob er noch aktuell ist. Der Schlüssel "ollama" ist nur ein Platzhalter, ein echter Schlüssel ist nicht nötig.)
Dazu die Stolperfalle: Ein kleines lokales Modell ist bei komplexem Tool-Use und bei Structured Outputs spürbar unzuverlässiger als ein großes API-Modell. Also nicht dieselbe Qualität erwarten. Darum gilt: Validierung (Pydantic, ki/02) und Retry (ki/07b) bleiben auch lokal Pflicht, und du misst die Qualität an einem Golden Set (ki/11), statt dich auf ein Gefühl zu verlassen.
Eine Entscheidung zwischen beiden Wegen hängt an drei Fragen:
| Frage | spricht für lokal | spricht für Cloud |
|---|---|---|
| Datenschutz: Dürfen die Daten das Haus verlassen? | nein, auch nicht maskiert | ja, oder sie sind unkritisch |
| Kosten: Wie hoch ist das Volumen? | sehr hoch, Kosten pro Token fallen weg | niedrig, kein eigener Betrieb nötig |
| Qualität: Wie schwierig ist die Aufgabe? | einfach, gut prüfbar | komplex, Tool-Use, strenges JSON |
Zwei Anmerkungen. Die Kosten lokal sind nicht null: Hardware, Strom und Betrieb kosten auch, die Quelle nennt dafür keine Zahlen, das rechnest du pro Fall selbst. Und der Speicherbedarf eines Modells und Verfahren wie Quantisierung (kleinere Zahlenformate, damit ein Modell auf schwächere Hardware passt) stehen nicht in der Quelle (bitte prüfen, bevor du dem Kunden Hardware zusagst).
Schritt 6: EU AI Act (nur das, was die Quelle sagt)
Das hier ist keine Rechtsberatung. Die Quelle sagt wenig, und das ist die Grundlage dieses Abschnitts:
- Je nach Risikoklasse der Anwendung gelten seit 2024/2025 gestaffelt Informations-, Kennzeichnungs- und Dokumentationspflichten. Das betrifft jedes Angebot, das bei einem Kunden produktiv eingesetzt wird, nicht nur Forschung. Genaue Fristen, Klassennamen und Strafen stehen nicht in der Quelle (bitte prüfen, im Gesetzestext oder bei Fachleuten).
- Für die meisten Anwendungen aus diesem Kurs (interne Assistenz, keine biometrische Erkennung, keine sicherheitskritische Automatisierung) greift laut Quelle die niedrigste Risikoklasse.
- Auch dort gilt die Transparenzpflicht: Nutzer müssen erkennen können, dass sie mit einem KI-System interagieren.
- Es lohnt sich, Einsatzzweck und die getroffenen Guardrails (M3) kurz zu dokumentieren. Das ist laut Quelle ein verkaufbarer Bestandteil eines seriösen Angebots, besonders bei Kunden aus regulierten Branchen wie der Energiewirtschaft.
Für Übung 5 brauchst du daraus eine grobe Faustregel in drei Fällen:
| Fall | Einordnung nach der Quelle |
|---|---|
| Biometrische Erkennung oder sicherheitskritische Automatisierung | nicht die niedrigste Klasse: genau prüfen lassen |
| Sonst, und Menschen reden mit dem System | niedrigste Klasse, Transparenzhinweis nötig, Kurzdokumentation sinnvoll |
| Sonst, und kein Mensch redet mit dem System (reine Hintergrundverarbeitung) | niedrigste Klasse, kein Hinweis an Nutzer nötig, Kurzdokumentation trotzdem sinnvoll (die Quelle nennt diesen Fall nicht ausdrücklich, bitte prüfen) |
Diese Tabelle ist eine Lernhilfe, kein Rechtsurteil. Im echten Projekt entscheidet der Gesetzestext, nicht eine Faustregel.
Falle
- Nur den Durchschnitt melden. Ein einzelner 9-Sekunden-Aufruf verschwindet im Mittelwert. Melde p50 und p95.
- p95 aus zu wenigen Werten. Bei 10 Aufrufen ist p95 der Maximalwert, bei 5 Aufrufen auch. Erst ab 20 Aufrufen ist p95 überhaupt etwas anderes als “der langsamste”, und belastbar wird es erst mit deutlich mehr (die Quelle nimmt 100).
p / 100 * nstattp * n / 100. Der Gleitkomma-Fehler verschiebt den Rang um eins, bei p = 55 und 100 Werten (siehe Schritt 2).- Seriell messen und parallele Last versprechen. Die Zahlen gelten für die Art, wie du gemessen hast.
- Kosten aus unrealistischen Testfragen hochrechnen. Kurze Testfragen ergeben zu niedrige Schätzungen.
- Lokal gleich Qualität gleich. Kleine Modelle patzen bei Tool-Use und strengem JSON. Validierung und Golden Set bleiben nötig.
- Lokal gleich kostenlos. Pro Token entfällt der Preis, Hardware und Betrieb nicht.
- AI Act ignorieren, weil “nur interne Assistenz”. Auch die niedrigste Klasse hat die Transparenzpflicht. Ob ein Fall wirklich dort liegt, ist eine Rechtsfrage (bitte prüfen).
Übungen
Übung 1: perzentil selbst schreiben (leicht bis mittel)
Schreibe perzentil(werte, p) nach dem Nearest-Rank-Verfahren aus Schritt 2, aber für Werte beliebiger Art (zum Beispiel ganze Millisekunden). Die Regeln:
- Sortiere eine Kopie: Die übergebene Liste darf sich nicht verändern.
- Der Rang ist
ceil(p * n / 100), das Ergebnis der Wert an dieser Stelle (Rang zählt ab 1). pist eine ganze Zahl (Typint, nichtbool) von 1 bis 100. Alles andere wirftValueError:0,101,50.5,True, der Text"50"undNone.- Eine leere Liste wirft
ValueError. - Abgebrochene Aufrufe stehen im Log mit dem Wert
Nonestatt einer Zahl. Sie zählen weder für den Rang noch fürn: Bestimme das Perzentil nur über die Einträge, die nichtNonesind. Bleibt keiner übrig (leere Liste oder nurNone), wirft die FunktionValueError.
Beispiele: Bei [40, 10, 30, 20] ist p50 der Wert 20 (Rang 2) und p100 der Wert 40. Bei einem einzigen Wert [7] ist jedes Perzentil 7. Bei [40, None, 10, 30, None, 20] zählen nur vier Werte, p50 ist wieder 20.
Vier Fragen: Wie sortierst du, ohne die Eingabe anzufassen? Welche Länge ist für den Rang die richtige, wenn None im Log steht? Wie wird aus dem Rang ein Index? Was passiert bei p = 0, wenn du die Randprüfung weglässt?
import math
def perzentil(werte, p):
gueltig = [w for w in werte if w is not None]
if not gueltig:
raise ValueError("keine Werte")
if not isinstance(p, int) or isinstance(p, bool) or not 1 <= p <= 100:
raise ValueError("p muss eine ganze Zahl von 1 bis 100 sein")
sortiert = sorted(gueltig)
rang = math.ceil(p * len(sortiert) / 100)
return sortiert[rang - 1]
perzentilÜbung 2: Report aus einem Log (leicht bis mittel)
Ein Kollege hat einen Lauf mit der Anwendung protokolliert. Schreibe report(log, preise_je_1k). Das log ist wie in Schritt 1 eine Liste von dicts mit input_tokens, output_tokens und latenz_s. Die Funktion perzentil aus Übung 1 steht (richtig) bereit. report liefert ein dict mit genau diesen Schlüsseln:
kosten_gesamt: Summe der Kosten aller Aufrufe (Preise je 1000 Token, Input und Output getrennt),kosten_je_100: Kosten hochgerechnet auf 100 Anfragen (egal, wie viele Zeilen das Log hat),p50undp95: Latenz-Perzentile über alle Zeilen.
Ein leeres Log wirft ValueError. Das Log darf nicht verändert werden.
Zerlege es in vier kleine Rechnungen. Welche Zahl teilst du für die Hochrechnung durch was, wenn das Log nicht genau 100 Zeilen hat? Welche Einheit haben die Preise?
def report(log, preise_je_1k):
if not log:
raise ValueError("leeres Log")
gesamt = sum(
e["input_tokens"] / 1000 * preise_je_1k["input"]
+ e["output_tokens"] / 1000 * preise_je_1k["output"]
for e in log
)
latenzen = [e["latenz_s"] for e in log]
return {
"kosten_gesamt": gesamt,
"kosten_je_100": gesamt / len(log) * 100,
"p50": perzentil(latenzen, 50),
"p95": perzentil(latenzen, 95),
}
reportÜbung 3: Seriell oder parallel vorhersagen (leicht)
Ein Lauf mit zehn Aufrufen. Latenz und Kosten je Aufruf stehen in Listen, in der Reihenfolge des Logs. Es laufen Gruppen von 3 Aufrufen parallel (Aufrufe 1 bis 3, 4 bis 6, 7 bis 9, danach der letzte Aufruf allein), jede Gruppe startet erst, wenn die vorige ganz fertig ist. Trage ein Tupel mit drei Teilen ein:
- Gesamtdauer des seriellen Laufs in Sekunden,
- Gesamtdauer des parallelen Laufs in Sekunden,
- Vergleich der Gesamtkosten parallel gegenüber seriell als Text:
"gleich","hoeher"oder"niedriger".
Wie lange braucht eine Gruppe, die gleichzeitig läuft, und was ändert sich an den Tokens, wenn Aufrufe gleichzeitig statt nacheinander laufen? Die letzte Gruppe hat nur einen Aufruf.
antwort = (15.7, 10.6, "gleich")
antwortSeriell: Summe aller Latenzen, 15,7. Parallel: Maxima der Gruppen 2,0 plus 3,5 plus 1,1 plus 4,0, zusammen 10,6. Kosten: Jeder Aufruf verbraucht dieselben Tokens, die Summe bleibt gleich.
Übung 4: Cloud-Modell oder lokales Modell (Fallstudie)
Randbedingungen: Ein Energieversorger will aus etwa 2000 Kundenschreiben pro Monat drei Felder ziehen (Kundennummer, Anliegen, Dringlichkeit) und als JSON weiterverarbeiten. Die Schreiben enthalten Namen, Adressen und Zählerstände. Vertraglich steht fest: Die Schreiben dürfen das eigene Netz nicht verlassen, auch nicht maskiert oder gekürzt. Der Kunde stellt dir einen eigenen Server bereit. Eine Fachkraft prüft jede Woche eine Stichprobe der Ergebnisse. Die Aufgabe selbst ist einfach, die Felder stehen meist klar im Text. Welche Entscheidung passt zu diesen Randbedingungen? Trage den Buchstaben als String ein.
Gehe die drei Fragen aus der Tabelle in Schritt 5 der Reihe nach durch: Was verbietet die Randbedingung zu Datenschutz sicher? Und was sagt die Stolperfalle zu kleinen Modellen, auch bei einfachen Aufgaben?
antwort = "D"
antwortDie Daten dürfen das Netz in keiner Form verlassen, also scheidet jede Cloud-Variante aus, auch zum Testen. Lokal bleibt Validierung, Retry und ein Golden Set Pflicht, weil kleine Modelle unzuverlässiger sind.
Übung 5: Anwendungsfälle einer Einordnung zuordnen (mittel)
Ordne jeden der fünf Fälle der Einordnung aus der Faustregel-Tabelle in Schritt 6 zu. Die Antwort ist eine Liste mit fünf Buchstaben in der Reihenfolge der Fälle. Das ist eine Lernübung nach der Quelle, keine Rechtsberatung, im echten Projekt zählt der Gesetzestext (bitte prüfen).
- A: niedrigste Klasse, Transparenzhinweis nötig, Kurzdokumentation sinnvoll
- B: nicht die niedrigste Klasse, genau prüfen lassen
- C: niedrigste Klasse, kein Hinweis an Nutzer nötig, Kurzdokumentation trotzdem sinnvoll
Die Fälle:
- Ein interner Assistent im Chat beantwortet Mitarbeitenden Fragen zu den Reisekosten-Regeln.
- Eine Kamera am Werkstor erkennt Mitarbeitende am Gesicht und öffnet die Tür.
- Ein nächtlicher Batch-Job ordnet Rechnungen Kostenstellen zu. Niemand schreibt mit ihm, die Ergebnisse landen in einer Tabelle für die Buchhaltung.
- Ein Chatbot auf der Kundenseite eines Stadtwerks beantwortet Fragen zur Abschlagszahlung.
- Ein Modell schaltet in einer Anlage automatisch Ventile, ohne dass ein Mensch freigibt.
Stelle für jeden Fall zwei Fragen in dieser Reihenfolge: Ist es biometrisch oder sicherheitskritisch? Falls nein: Redet ein Mensch mit dem System? Ob das System intern oder extern genutzt wird, ist keine dieser beiden Fragen.
antwort = ["A", "B", "C", "A", "B"]
antwortFall 1 und 4: Menschen reden mit dem System (A, auch intern). Fall 2: biometrisch (B). Fall 3: reine Hintergrundverarbeitung (C). Fall 5: sicherheitskritische Automatisierung ohne Mensch in der Schleife (B).
Lokal-Übung: dieselben Fragen gegen Ollama
Die Quelle verlangt, dieselben Testfragen einmal gegen die API und einmal gegen ein lokales Modell laufen zu lassen und Qualität, Latenz und die Zuverlässigkeit von Structured Outputs zu vergleichen. Das geht nur mit einem echten Ollama, also lokal. Die Datei lernlabor/uebung/ki/ki_17_ollama_vergleich.py macht den Lauf und rechnet dieselben p50 und p95 aus wie diese Lektion. Sie nutzt nur die Standardbibliothek, braucht keinen Schlüssel und installiert oder lädt nichts.
cd lernlabor && uv run python uebung/ki/ki_17_ollama_vergleich.pyOhne laufendes Ollama (oder ohne geladenes Modell) meldet sie das und macht einen Trockenlauf mit festen, ausgedachten Messwerten. Ausgabe dieses Trockenlaufs (ausgeführt mit Python 3.13.9, Ollama war nicht gestartet):
Ollama ist unter http://localhost:11434 nicht erreichbar. Ist es installiert und gestartet (ollama serve oder die Desktop-App)? Nichts wird von diesem Skript installiert.
Weiter mit dem Trockenlauf (feste, ausgedachte Messwerte).
Trockenlauf
Aufrufe: 5
p50 / p95 Latenz: 1.10 s / 7.50 s
Durchschnitt: 2.34 s
Gültiges JSON: 4 von 5
Kosten pro Token: 0 (Strom und Hardware nicht eingerechnet)
Hinweis: Bei weniger als 20 Aufrufen ist p95 einfach der langsamste Aufruf.
Für den echten Lauf brauchst du Ollama und ein geladenes Modell (Standard llama3.1:8b, Name aus der Quelle, bitte prüfen). Das Laden ist ein Download von mehreren Gigabyte (ollama pull llama3.1:8b), mache das bewusst und nicht nebenbei. Der echte Pfad des Skripts wurde nur gegen einen Ersatzserver mit festen Antworten getestet, nicht gegen echtes Ollama (bitte prüfen). Mit OLLAMA_MODEL wählst du ein anderes Modell.
Deine Aufgabe: Starte das Skript mit echtem Modell, notiere p50, p95 und wie viele Antworten gültiges JSON waren. Erweitere dann FRAGEN auf mindestens 20 Fragen, damit p95 mehr ist als der langsamste Aufruf. Würdest du dieses Modell für die Fallstudie aus Übung 4 ohne Validierung und Retry einsetzen?
Wenn nicht alle Antworten gültiges JSON sind: Was gehört nach der Antwort immer in deinen Code, und was bei einem Fehler?
aufgabe = "Nein: Validierung per Schema und Retry bleiben nötig, kleine lokale Modelle liefern strenges JSON unzuverlässiger."Messwerte hängen von deinem Rechner und Modell ab. Zählt das Skript auch nur eine kaputte Antwort bei fünf Fragen, ist Validierung ohne Diskussion Pflicht.
Projektaufgabe: M5-Abschluss-Check
Die Quelle (Baustein 06) fordert: Die Anwendung läuft containerisiert, Kosten und Latenz (p50/p95) für 100 Anfragen sind gemessen und dokumentiert, und sie ist mindestens einmal komplett gegen ein lokales Modell statt der API gelaufen. Die Aufgabenliste von M5 aus der Quelle mit Stundenangaben:
| Aufgabe | Stunden (Quelle) | Wo geübt |
|---|---|---|
| Anwendung containerisieren (Dockerfile, reproduzierbarer Build) | 5 bis 7 | ki/16a |
| Tracing mit verschachtelten Spans einbauen | 5 bis 7 | ki/15b, ki/16b |
| Last-/Latenztest mit 100 Anfragen (seriell und parallel) | 4 bis 6 | diese Lektion, Schritte 1 bis 4 |
| Lokales Modell über Ollama anbinden und vergleichen | 5 bis 7 | diese Lektion, Schritt 5 und Lokal-Übung |
| EU-AI-Act-Kurzdokumentation für ein Beispielsystem | 2 bis 3 | diese Lektion, Schritt 6 |
| Abschluss-Check: kompletter Lauf gegen lokales Modell, Kosten/Latenz-Report fertig | 2 bis 3 | diese Aufgabe |
Die Quelle nennt für M5 “ca. 28 bis 33 h”. Die Einzelangaben ergeben zusammen aber 23 bis 33 h (bitte prüfen, welche Zahl gilt).
Aufgabe
Nimm das RAG-System aus M2 (oder die Anwendung, die du in M5 containerisiert hast) und baue den Betriebsnachweis dafür:
- Messlauf. 100 Testfragen, einmal seriell, einmal parallel in Fünfergruppen mit
asyncio.gather. Pro Aufruf eine Log-Zeile mitinput_tokens,output_tokensundlatenz_s(gemessen mittime.perf_counter()). - Report. Aus dem Log mit
report(Übung 2): p50, p95, Gesamtdauer beider Läufe, Kosten je 100 Anfragen. Beide Läufe nebeneinander in einer Tabelle. - Lokaler Lauf. Dieselben Fragen komplett gegen ein lokales Modell über Ollama. Notiere p50, p95, Quote gültiger Structured Outputs, und wo die Qualität (Golden Set aus
ki/11) abfällt. - Vergleich und Empfehlung. Eine Tabelle Cloud gegen lokal mit Datenschutz, Kosten, Qualität, Latenz und eine begründete Empfehlung für einen fiktiven Kunden aus der Energiewirtschaft.
- AI-Act-Kurzdokumentation. Eine halbe Seite: Einsatzzweck, vermutete Risikoklasse (mit Hinweis “bitte prüfen”), welche Guardrails aus M3 bereits greifen, welche Transparenzhinweise noch fehlen.
- Container. Der ganze Lauf funktioniert aus dem Container (
ki/16a).
Fertig, wenn
- Die Anwendung läuft containerisiert und reproduzierbar.
- Ein Report mit p50, p95 und Kosten je 100 Anfragen für 100 Aufrufe liegt vor, getrennt für seriell und parallel.
- Mindestens ein kompletter Lauf gegen ein lokales Modell ist durch, mit Quote gültiger Antworten.
- Die Kurzdokumentation nennt Einsatzzweck, vermutete Risikoklasse, greifende Guardrails und fehlende Transparenzhinweise.
- Es steht in keiner Datei ein API-Schlüssel.
Selbstcheck
Merksatz
Ein Kunde glaubt Zahlen, nicht Gefühle: p50 und p95 aus einem Log, Kosten für 100 Anfragen, eine begründete Wahl zwischen Cloud und lokal und eine kurze Einordnung nach EU AI Act sind zusammen ein Angebot, das seriös wirkt.
Prüfstein
Ein Kunde sagt: “Im Test hat es im Schnitt 2 Sekunden gedauert, das ist doch schnell genug?” Welche zwei Zahlen und welche Frage zur Messweise (seriell oder parallel, wie viele Aufrufe) stellst du, bevor du zustimmst, und warum kann ein lokales Modell für denselben Kunden trotzdem die richtige Wahl sein?
Quelle: quellen/kursbuch-lerninhalte.md, Modul M5, Bausteine “03 Kosten & Latenz systematisch messen”, “04 Lokale Modelle (Ollama)”, “05 EU-AI-Act-Pflichten” und “06 Abschluss-Check” (Warum, Kernidee, Stolperfalle, Merksatz, Übungen, Aufgabenliste mit Stundenangaben). Aus der Quelle stammen: p50 als sortiert[49] und p95 als sortiert[94] bei 100 Werten, die Stolperfalle seriell gegen parallel, llama3.1:8b und der Aufruf über die OpenAI-kompatible Schnittstelle, die Stolperfalle kleiner lokaler Modelle bei Tool-Use und Structured Outputs, die Aussagen zum EU AI Act (gestaffelte Pflichten seit 2024/2025, niedrigste Risikoklasse für die meisten Anwendungen, Transparenzpflicht, Kurzdokumentation als Verkaufsargument), die Aufgabenliste mit den Stundenangaben. Über die Quelle hinaus (allgemeines Fachwissen, bitte prüfen): das Nearest-Rank-Verfahren als Perzentil-Definition und der Hinweis auf interpolierende Verfahren, die Gleitkomma-Falle bei p / 100 * n, das Rechenmodell “Gruppendauer ist das Maximum” (ein Modell, das Rate-Limits und Last nicht abbildet), die Tabelle “Cloud oder lokal” mit drei Fragen, der Hinweis auf Hardware- und Betriebskosten, die Erwähnung von Quantisierung und Speicherbedarf (die Quelle nennt beides nicht), die Faustregel-Tabelle zum AI Act (keine Rechtsberatung, Klassennamen, Fristen und Strafen stehen nicht in der Quelle), der Fall “reine Hintergrundverarbeitung ohne Nutzerkontakt”. Die Preise 0,003 und 0,015 je 1000 Token sind Beispielwerte aus ki/07a. Alle Logs, Fälle und Messwerte sind ausgedacht. Der Hinweis “28 bis 33 h” gegenüber der Summe 23 bis 33 h ist eine Auffälligkeit in der Quelle, bitte prüfen. Der echte Ollama-Pfad des Lokal-Skripts ist nur gegen einen Ersatzserver getestet.