From 6fac8775e12f4500dbcbb2116c3831ab259dd34e Mon Sep 17 00:00:00 2001 From: Brummel Date: Sun, 3 May 2026 16:20:56 +0200 Subject: [PATCH] Update API documentation with Llama 3.1 quirks --- docs/ionos-llm-api.md | 142 ++++++++++++++++++++++++++++++++---------- 1 file changed, 108 insertions(+), 34 deletions(-) diff --git a/docs/ionos-llm-api.md b/docs/ionos-llm-api.md index cf8378d..1a5f7e4 100644 --- a/docs/ionos-llm-api.md +++ b/docs/ionos-llm-api.md @@ -1,7 +1,7 @@ # Ionos LLM API: Quirks und Workarounds -**Stand:** 2026-05-03 — empirisch ermittelt gegen -`https://openai.inference.de-txl.ionos.com/v1/chat/completions`. +**Stand:** 2026-05-03 (zweite Session, in Doctate umgesetzt) — empirisch +ermittelt gegen `https://openai.inference.de-txl.ionos.com/v1/chat/completions`. Verhalten kann sich aendern; Datum oben mit dem aktuellen Test-Befund abgleichen, bevor man auf diese Notizen baut. @@ -15,15 +15,22 @@ abgleichen, bevor man auf diese Notizen baut. `https://api.ionos.com/docs/inference-openai/v1/`. Das ist eine Redoc-Seite mit eingebetteter OpenAPI-3.0.3-Spec — die einzige verlaessliche Quelle fuer unterstuetzte Parameter, Defaults und Limits. -- Llama 3.1 405B FP8 hat einen **vLLM-Tokenizer-Bug**: das - End-of-Turn-Token `<|eot_id|>` wird als Klartext-String - `assistant\n\n` ausgegeben statt als Special-Token. Folge: der - offizielle `stop`-Parameter mit `<|eot_id|>` feuert nie, das Modell - laeuft bis `max_tokens` voll. -- **Empfohlene Loesung fuer Doctate**: `response_format: json_schema`. - Schema-erzwungenes Decoding stoppt natuerlich am schliessenden `}`, - funktioniert modell-agnostisch und braucht keine Provider-spezifischen - Workarounds. +- Llama 3.1 405B FP8 hat ein bekanntes **End-of-Turn-Termination-Problem**: + in der Sandbox kam `<|eot_id|>` als ASCII-`assistant\n\n` zurueck, was + Quelle der ersten Diagnose war. Aber das eigentliche Problem ist breiter + und modell-inhaerent: Llama 3.1 verfaellt mit Greedy-Decoding und/oder + konflikthaltigen Prompts in Endlos-Schleifen (bestaetigt von Ionos selbst, + HuggingFace-Discussion #32, vLLM-Issues #13530, #13828). +- **Empfohlene Loesung fuer Doctate (Drei-Komponenten-Kombi):** + `temperature: 0.6 + top_p: 0.9` (Sampling) + `response_format: + json_schema` (Form-Garantie) + expliziter Format-Hinweis als zweite + `system`-Message (sagt dem Modell WAS in `document` zu schreiben ist). + Alle drei sind notwendig; jede einzelne weggelassen kippt in einen + anderen Failure-Mode (Timeout, Endlos-Schleife, oder silent + `{"document": ""}`). +- **Wichtig**: gpt-oss-120b braucht weder Sampling noch Format-Hinweis — + es ist schema-aware trainiert und laeuft mit dem Default-Body sauber. + Die Drei-Komponenten-Loesung ist Llama-spezifisch, nicht universell. ## So kommt man an die richtige API-Info @@ -176,6 +183,8 @@ Gateway-Timeout. ### Empirische Datentabelle +#### Session 1 (Diagnose, Sandbox-Inputs) + Alle Tests mit `meta-llama/Meta-Llama-3.1-405B-Instruct-FP8`, Input `"Sag hallo."` (ausser T22 = realer Doctate-Body, ~1.5k Prompt-Tokens): @@ -193,8 +202,41 @@ Input `"Sag hallo."` (ausser T22 = realer Doctate-Body, ~1.5k Prompt-Tokens): | T22 | T21 + realer Doctate-Body (1539 Prompt-Tokens) | **22.77 s, finish=stop, 482 Tokens** | 22.7 | Konsistenz: **`finish_reason: "length"` heisst Modell wollte -weiter** — kein sauberer Stop. Nur die letzten vier Loesungswege -bekommen sauberes `"stop"`. +weiter** — kein sauberer Stop. + +#### Session 2 (Reproduktion gegen Production-Pipeline, 30 s Timeout) + +Alle Tests gegen den realen Doctate-Body von Case `aecf5890` (236 +char user_content, ~1040-1115 Prompt-Tokens). Skripte unter +`/tmp/doctate-llama-test/run{1..6}.py`. Production-`LLAMA_SYSTEM_PROMPT` +verbatim (2819 chars). + +| Test | Sampling | json_schema | Format-Hinweis | Wallclock | finish | Output | +|---|---|---|---|---|---|---| +| T1 | nein (`temp=0.5`) | ja | nein | **30 s Timeout** | – | nichts geliefert | +| T2 | nein (`temp=0.5`) | ja | im Prompt inline | 3.26 s | stop | sauber, 61 Tokens | +| T3 | ja (`0.6 + 0.9 + freq=0.1 + stop`) | nein (Ionos-Doku verbatim) | nein | **30 s Timeout** | – | nichts geliefert | +| T4 | ja (`0.6 + 0.9`) | ja | nein | 0.98 s | stop | **`{"document":""}` (silent deletion)** | +| T5 | ja (`0.6 + 0.9`) | ja | im Prompt inline | 3× ⌀ 3.05 s | stop | sauber, 61 Tokens, 3× stabil | +| T6 | ja (`0.6 + 0.9`) | ja | als zweite `system`-Message | 3× ⌀ 3.13 s | stop | sauber, 61 Tokens, 3× stabil — **Production-Konfig** | + +**Wichtige Befunde aus Session 2:** + +1. **json_schema allein reicht nicht (T1)**: Production-Pfad mit + `temperature: 0.5` ohne `top_p` timeoutet, weil Llama Schema-Resampling + als Endlos-Schleife verkraftet. +2. **Sampling allein reicht nicht (T3)**: Selbst die exakte Ionos-Doku- + Empfehlung mit `stop: ["<|eot_id|>", "<|end_of_text|>"]` timeoutet + gegen unseren 2.8k-Char-Prompt. Die Doku-Empfehlung ist fuer kuerzere + Prompts validiert. +3. **Sampling + Schema ohne Hinweis kippt in silent deletion (T4)**: + Modell respektiert die Form, weiss aber inhaltlich nicht was tun. + `{"document": ""}` waere fuer Doctate als leere Notiz im UI gelandet + — gefaehrlicher als Timeout, weil der Worker `remove_failure_marker` + aufruft. **Worker.rs hat seither einen Empty-Guard.** +4. **Drei-Komponenten-Kombi laeuft 3x stabil (T5, T6)**: Sampling + + Schema + Format-Hinweis. T6 (separate `system`-Message) ist + architektonisch sauberer und gewaehlt fuer die Doctate-Implementation. ## Loesungswege im Vergleich @@ -202,33 +244,60 @@ bekommen sauberes `"stop"`. |---|---|---|---|---| | `stop: ["assistant"]` | sehr schnell | nein | nein | Patch fuer Provider-Bug; bricht im englischen Text | | Eigener End-Marker via System-Prompt | sehr schnell | mittel | ja | Modell kann Marker bei langem Output vergessen | -| `response_format: json_schema` | schnell | ja | **ja** | Server erzwingt Stop ueber Grammar-Decoding | -| Modellwechsel auf `gpt-oss-120b` / `Llama-3.3-70B` | sehr schnell | ja | – | Quirks A+B betreffen diese Modelle nicht | +| `response_format: json_schema` allein | – | nein | nein | **In Production NICHT ausreichend** (T1, T4) — kippt entweder in Timeout oder silent deletion | +| **Drei-Komponenten-Kombi** (Sampling + Schema + Format-Hinweis) | schnell (3 s) | ja | – | **In Doctate umgesetzt fuer Llama** (T5, T6) | +| Modellwechsel auf `gpt-oss-120b` / `Llama-3.3-70B` | sehr schnell | ja | – | Schema-aware, brauchen die Drei-Komponenten-Kombi nicht | -Empfehlung: **`response_format: json_schema`** als Default-Pfad fuer -Doctate, weil derselbe Code-Pfad auf gpt-oss-120b und Llama 3.3 70B -unveraendert weiter funktioniert. Wenn das Schema-Decoding bei -zukuenftigen, viel groesseren Outputs Latenzprobleme macht, wechselt -man auf einen schnelleren Modell-Endpoint, nicht zurueck auf -String-Hacks. +**Empfehlung pro Modell-Familie:** +- Schema-aware Modelle (gpt-oss-120b, vermutlich Llama 3.3 70B): + `temperature: 0.5 + response_format: json_schema`. Body bleibt klein + und stabil. +- Llama 3.1 405B FP8: nur als **Drei-Komponenten-Kombi** brauchbar — + `temperature: 0.6 + top_p: 0.9 + response_format: json_schema + + expliziter Format-Hinweis als zweite system-Message`. Wer eine + Komponente weglaesst, faengt sich Timeout (T1, T3) oder silent + deletion (T4) ein. + +## Doctate-Implementation (`server/src/analyze/backend.rs`) + +Die Drei-Komponenten-Kombi lebt als Per-Backend-Konfiguration: + +```rust +const LLAMA_TEMPERATURE: f32 = 0.6; +const LLAMA_TOP_P: f32 = 0.9; +const LLAMA_FORMAT_INSTRUCTION: &str = "AUSGABEFORMAT: ..."; + +ionos_backend("llama_3_1_405b", "Llama 3.1 405B", "...405B-Instruct-FP8") + .with_system_prompt(LLAMA_SYSTEM_PROMPT) + .with_temperature(LLAMA_TEMPERATURE) + .with_top_p(LLAMA_TOP_P) + .with_format_instruction(LLAMA_FORMAT_INSTRUCTION), +``` + +`chat_once` injiziert `format_instruction` als zweite `system`-Message +zwischen `system_prompt` und `user`-Content, falls `Some`. Backends +ohne `format_instruction` (gpt-oss) erzeugen den exakten Body wie vor +dem Llama-Fix — byte-stabil, gepinnt durch +`gpt_oss_has_no_format_instruction_and_no_top_p`-Test. + +`worker.rs` hat einen **Empty-Guard** nach `vocab.replace`: leere +`document`-Strings (Failure-Mode T4) loesen einen Failure-Marker statt +einer leeren `document.md` aus. ## Entscheidungsbaum ```mermaid graph TD - A[Neuer Aufruf gegen Ionos] --> B{Brauche ich strukturierten Output?} - B -->|Ja oder unsicher| C[response_format: json_schema setzen] - B -->|Nein, freier Text| D{Modell?} - D -->|gpt-oss-120b oder Llama 3.3 70B| E[Standard-Aufruf reicht] - D -->|Llama 3.1 405B FP8| F[max_tokens MUSS gesetzt sein] - F --> G{Wie soll gestoppt werden?} - G -->|Sauber via Schema| C - G -->|String-Hack ok| H["stop: array enthaelt 'assistant'"] - G -->|Eigener Marker| I[System-Prompt instruiert + stop matcht] - C --> J[Output ist JSON, server-seitig parsen] - E --> K[Output direkt nutzen] - H --> K - I --> K + A[Neuer Backend-Profil-Eintrag in backend.rs] --> B{Modell?} + B -->|gpt-oss-120b
schema-aware| C[Default: temp 0.5,
use_json_schema=true,
kein top_p, keine format_instruction] + B -->|Llama 3.1 405B FP8| D[Drei-Komponenten-Kombi:
temp 0.6 + top_p 0.9 + schema
+ format_instruction] + B -->|Llama 3.3 70B
noch nicht getestet| E[Erst gegen Doctate-Body testen,
vermutlich wie gpt-oss] + B -->|Anderes neues Modell| F{Schema-aware trainiert?} + F -->|Ja| C + F -->|Nein| D + C --> G[Body byte-stabil zu pre-Llama-Fix] + D --> H[Worker-Empty-Guard schuetzt vor silent deletion] + E --> G ``` ## Quellen und Verweise @@ -254,6 +323,11 @@ einem neuen Bug auf diese Workarounds setzt, immer zuerst pruefen, ob: `assistant\n\n` ankommt — dann ist Quirk A behoben und die String-Hacks koennen weg. 3. Die OpenAPI-Spec neue/geaenderte Parameter zeigt. +4. Die Drei-Komponenten-Kombi fuer Llama 3.1 weiterhin noetig ist: + schnellster Check ist, das `format_instruction`-Feld testweise auf + `None` zu setzen und einen Doctate-Case neu zu triggern. Wenn der + Output sauber kommt, kann der Format-Hinweis weg. Wenn er + `{"document":""}` ist, gilt diese Doku noch. Datum oben aktualisieren, wenn man verifiziert hat, dass diese Dokumentation noch dem realen Backend-Verhalten entspricht.